diff --git a/api/controllers/console/explore/trial.py b/api/controllers/console/explore/trial.py index 9cf9aee66d9..8b40ce43814 100644 --- a/api/controllers/console/explore/trial.py +++ b/api/controllers/console/explore/trial.py @@ -1,9 +1,10 @@ import logging +from datetime import datetime from typing import Any, Literal, cast from flask import request from flask_restx import Resource, fields, marshal, marshal_with -from pydantic import BaseModel, Field +from pydantic import AliasChoices, BaseModel, Field, field_validator from sqlalchemy import select from werkzeug.exceptions import Forbidden, InternalServerError, NotFound @@ -63,6 +64,7 @@ from fields.app_fields import ( site_fields, tag_fields, ) +from fields.base import ResponseModel from fields.dataset_fields import dataset_fields from fields.member_fields import simple_account_fields from fields.message_fields import SuggestedQuestionsResponse @@ -75,7 +77,7 @@ from fields.workflow_fields import ( from graphon.graph_engine.manager import GraphEngineManager from graphon.model_runtime.errors.invoke import InvokeError from libs import helper -from libs.helper import uuid_value +from libs.helper import to_timestamp, uuid_value from models import Account from models.account import TenantStatus from models.model import AppMode, Site @@ -180,6 +182,254 @@ class TrialDatasetListQuery(BaseModel): ids: list[str] = Field(default_factory=list, description="Dataset IDs") +type TrialAppMode = Literal["chat", "agent-chat", "advanced-chat", "workflow", "completion"] +type TrialIconType = Literal["emoji", "image", "link"] +type JsonObject = dict[str, Any] + + +class TrialAppModel(ResponseModel): + provider: str + name: str + mode: str | None = None + completion_params: JsonObject = Field(default_factory=dict) + + +class TrialAppAgentMode(ResponseModel): + enabled: bool | None = None + strategy: str | None = None + tools: list[JsonObject] = Field(default_factory=list) + + +class TrialAppModelConfigResponse(ResponseModel): + opening_statement: str | None = None + suggested_questions: list[str] = Field( + default_factory=list, + validation_alias=AliasChoices("suggested_questions_list", "suggested_questions"), + ) + suggested_questions_after_answer: JsonObject | None = Field( + default=None, + validation_alias=AliasChoices("suggested_questions_after_answer_dict", "suggested_questions_after_answer"), + ) + speech_to_text: JsonObject | None = Field( + default=None, validation_alias=AliasChoices("speech_to_text_dict", "speech_to_text") + ) + text_to_speech: JsonObject | None = Field( + default=None, validation_alias=AliasChoices("text_to_speech_dict", "text_to_speech") + ) + retriever_resource: JsonObject | None = Field( + default=None, validation_alias=AliasChoices("retriever_resource_dict", "retriever_resource") + ) + annotation_reply: JsonObject | None = Field( + default=None, validation_alias=AliasChoices("annotation_reply_dict", "annotation_reply") + ) + more_like_this: JsonObject | None = Field( + default=None, validation_alias=AliasChoices("more_like_this_dict", "more_like_this") + ) + sensitive_word_avoidance: JsonObject | None = Field( + default=None, validation_alias=AliasChoices("sensitive_word_avoidance_dict", "sensitive_word_avoidance") + ) + external_data_tools: list[JsonObject] = Field( + default_factory=list, validation_alias=AliasChoices("external_data_tools_list", "external_data_tools") + ) + model: TrialAppModel | None = Field(default=None, validation_alias=AliasChoices("model_dict", "model")) + user_input_form: list[JsonObject] = Field( + default_factory=list, validation_alias=AliasChoices("user_input_form_list", "user_input_form") + ) + dataset_query_variable: str | None = None + pre_prompt: str | None = None + agent_mode: TrialAppAgentMode | None = Field( + default=None, + validation_alias=AliasChoices("agent_mode_dict", "agent_mode"), + ) + prompt_type: str | None = None + chat_prompt_config: JsonObject | None = Field( + default=None, validation_alias=AliasChoices("chat_prompt_config_dict", "chat_prompt_config") + ) + completion_prompt_config: JsonObject | None = Field( + default=None, validation_alias=AliasChoices("completion_prompt_config_dict", "completion_prompt_config") + ) + dataset_configs: JsonObject | None = Field( + default=None, + validation_alias=AliasChoices("dataset_configs_dict", "dataset_configs"), + ) + file_upload: JsonObject | None = Field( + default=None, + validation_alias=AliasChoices("file_upload_dict", "file_upload"), + ) + created_by: str | None = None + created_at: int | None = None + updated_by: str | None = None + updated_at: int | None = None + + @field_validator("created_at", "updated_at", mode="before") + @classmethod + def _normalize_timestamp(cls, value: datetime | int | None) -> int | None: + return to_timestamp(value) + + +class TrialDeletedToolResponse(ResponseModel): + type: str + tool_name: str + provider_id: str + + +class TrialTagResponse(ResponseModel): + id: str + name: str + type: str + + +class TrialSiteResponse(ResponseModel): + access_token: str | None = Field(default=None, validation_alias="code") + code: str | None = None + title: str + icon_type: TrialIconType | None = None + icon: str | None = None + icon_background: str | None = None + description: str | None = None + default_language: str + chat_color_theme: str | None = None + chat_color_theme_inverted: bool | None = None + customize_domain: str | None = None + copyright: str | None = None + privacy_policy: str | None = None + input_placeholder: str | None = None + custom_disclaimer: str | None = None + customize_token_strategy: str | None = None + prompt_public: bool | None = None + app_base_url: str | None = None + show_workflow_steps: bool | None = None + use_icon_as_answer_icon: bool | None = None + created_by: str | None = None + created_at: int | None = None + updated_by: str | None = None + updated_at: int | None = None + icon_url: str | None = None + + @field_validator("icon_type", mode="before") + @classmethod + def _normalize_icon_type(cls, value: Any) -> str | None: + if hasattr(value, "value"): + return value.value + return value + + @field_validator("created_at", "updated_at", mode="before") + @classmethod + def _normalize_timestamp(cls, value: datetime | int | None) -> int | None: + return to_timestamp(value) + + +class TrialWorkflowPartialResponse(ResponseModel): + id: str + created_by: str | None = None + created_at: int | None = None + updated_by: str | None = None + updated_at: int | None = None + + @field_validator("created_at", "updated_at", mode="before") + @classmethod + def _normalize_timestamp(cls, value: datetime | int | None) -> int | None: + return to_timestamp(value) + + +class TrialAppDetailResponse(ResponseModel): + id: str + name: str + description: str | None = None + mode: TrialAppMode = Field(validation_alias="mode_compatible_with_agent") + icon_type: TrialIconType | None = None + icon: str | None = None + icon_background: str | None = None + icon_url: str | None = None + enable_site: bool + enable_api: bool + model_config_: TrialAppModelConfigResponse | None = Field( + default=None, + validation_alias=AliasChoices("app_model_config", "model_config"), + alias="model_config", + ) + workflow: TrialWorkflowPartialResponse | None = None + api_base_url: str | None = None + use_icon_as_answer_icon: bool | None = None + max_active_requests: int | None = None + created_by: str | None = None + created_at: int | None = None + updated_by: str | None = None + updated_at: int | None = None + deleted_tools: list[TrialDeletedToolResponse] = Field(default_factory=list) + access_mode: str | None = None + tags: list[TrialTagResponse] = Field(default_factory=list) + permission_keys: list[str] = Field(default_factory=list) + site: TrialSiteResponse + + @field_validator("icon_type", mode="before") + @classmethod + def _normalize_icon_type(cls, value: Any) -> str | None: + if hasattr(value, "value"): + return value.value + return value + + @field_validator("created_at", "updated_at", mode="before") + @classmethod + def _normalize_timestamp(cls, value: datetime | int | None) -> int | None: + return to_timestamp(value) + + +class TrialDatasetResponse(ResponseModel): + id: str + name: str + description: str | None = None + permission: str | None = None + data_source_type: str | None = None + indexing_technique: str | None = None + created_by: str | None = None + created_at: int | None = None + permission_keys: list[str] = Field(default_factory=list) + + +class TrialDatasetListResponse(ResponseModel): + data: list[TrialDatasetResponse] + has_more: bool + limit: int + total: int + page: int + + +class TrialWorkflowAccount(ResponseModel): + id: str + name: str | None = None + email: str | None = None + + +class TrialWorkflowResponse(ResponseModel): + id: str + graph: JsonObject = Field(validation_alias=AliasChoices("graph_dict", "graph")) + features: JsonObject = Field(default_factory=dict, validation_alias=AliasChoices("features_dict", "features")) + hash: str | None = Field(default=None, validation_alias=AliasChoices("unique_hash", "hash")) + version: str | None = None + marked_name: str | None = None + marked_comment: str | None = None + created_by: TrialWorkflowAccount | None = Field( + default=None, + validation_alias=AliasChoices("created_by_account", "created_by"), + ) + created_at: int | None = None + updated_by: TrialWorkflowAccount | None = Field( + default=None, + validation_alias=AliasChoices("updated_by_account", "updated_by"), + ) + updated_at: int | None = None + tool_published: bool | None = None + environment_variables: list[JsonObject] = Field(default_factory=list) + conversation_variables: list[JsonObject] = Field(default_factory=list) + rag_pipeline_variables: list[JsonObject] = Field(default_factory=list) + + @field_validator("created_at", "updated_at", mode="before") + @classmethod + def _normalize_timestamp(cls, value: datetime | int | None) -> int | None: + return to_timestamp(value) + + register_schema_models( console_ns, WorkflowRunRequest, @@ -197,6 +447,9 @@ register_response_schema_models( SimpleResultResponse, SiteResponse, SuggestedQuestionsResponse, + TrialAppDetailResponse, + TrialDatasetListResponse, + TrialWorkflowResponse, ) @@ -566,7 +819,7 @@ class TrialAppParameterApi(Resource): class AppApi(Resource): - @console_ns.response(200, "Success", app_detail_with_site_model) + @console_ns.response(200, "Success", console_ns.models[TrialAppDetailResponse.__name__]) @get_app_model_with_trial(None) @marshal_with(app_detail_with_site_model) def get(self, app_model): @@ -579,7 +832,7 @@ class AppApi(Resource): class AppWorkflowApi(Resource): - @console_ns.response(200, "Success", workflow_model) + @console_ns.response(200, "Success", console_ns.models[TrialWorkflowResponse.__name__]) @get_app_model_with_trial(None) @marshal_with(workflow_model) def get(self, app_model): @@ -593,7 +846,7 @@ class AppWorkflowApi(Resource): class DatasetListApi(Resource): @console_ns.doc(params=query_params_from_model(TrialDatasetListQuery)) - @console_ns.response(200, "Success", dataset_list_model) + @console_ns.response(200, "Success", console_ns.models[TrialDatasetListResponse.__name__]) @get_app_model_with_trial(None) def get(self, app_model): page = request.args.get("page", default=1, type=int) diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md index 43451095ab7..a11351fc69f 100644 --- a/api/openapi/markdown/console-openapi.md +++ b/api/openapi/markdown/console-openapi.md @@ -9385,7 +9385,7 @@ Bedrock retrieval test (internal use only) | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Success | **application/json**: [TrialAppDetailWithSite](#trialappdetailwithsite)
| +| 200 | Success | **application/json**: [TrialAppDetailResponse](#trialappdetailresponse)
| ### [POST] /trial-apps/{app_id}/audio-to-text #### Parameters @@ -9452,7 +9452,7 @@ Bedrock retrieval test (internal use only) | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Success | **application/json**: [TrialDatasetList](#trialdatasetlist)
| +| 200 | Success | **application/json**: [TrialDatasetListResponse](#trialdatasetlistresponse)
| ### [GET] /trial-apps/{app_id}/messages/{message_id}/suggested-questions #### Parameters @@ -9532,7 +9532,7 @@ Returns the site configuration for the application including theme, icons, and t | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Success | **application/json**: [TrialWorkflow](#trialworkflow)
| +| 200 | Success | **application/json**: [TrialWorkflowResponse](#trialworkflowresponse)
| ### [POST] /trial-apps/{app_id}/workflows/run **Run workflow** @@ -18048,6 +18048,12 @@ Input field definition for snippet parameters. | ---- | ---- | ----------- | -------- | | JSONValueType | | | | +#### JsonObject + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| JsonObject | object | | | + #### JsonValue | Name | Type | Description | Required | @@ -21306,6 +21312,43 @@ Enum class for tool provider | ---- | ---- | ----------- | -------- | | tracing_provider | string | Tracing provider name | Yes | +#### TrialAppAgentMode + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| enabled | boolean | | No | +| strategy | string | | No | +| tools | [ [JsonObject](#jsonobject) ] | | No | + +#### TrialAppDetailResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| access_mode | string | | No | +| api_base_url | string | | No | +| created_at | integer | | No | +| created_by | string | | No | +| deleted_tools | [ [TrialDeletedToolResponse](#trialdeletedtoolresponse) ] | | No | +| description | string | | No | +| enable_api | boolean | | Yes | +| enable_site | boolean | | Yes | +| icon | string | | No | +| icon_background | string | | No | +| icon_type | [TrialIconType](#trialicontype) | | No | +| icon_url | string | | No | +| id | string | | Yes | +| max_active_requests | integer | | No | +| mode | [TrialAppMode](#trialappmode) | | Yes | +| model_config | [TrialAppModelConfigResponse](#trialappmodelconfigresponse) | | No | +| name | string | | Yes | +| permission_keys | [ string ] | | No | +| site | [TrialSiteResponse](#trialsiteresponse) | | Yes | +| tags | [ [TrialTagResponse](#trialtagresponse) ] | | No | +| updated_at | integer | | No | +| updated_by | string | | No | +| use_icon_as_answer_icon | boolean | | No | +| workflow | [TrialWorkflowPartialResponse](#trialworkflowpartialresponse) | | No | + #### TrialAppDetailWithSite | Name | Type | Description | Required | @@ -21335,6 +21378,21 @@ Enum class for tool provider | use_icon_as_answer_icon | boolean | | No | | workflow | [TrialWorkflowPartial](#trialworkflowpartial) | | No | +#### TrialAppMode + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| TrialAppMode | string | | | + +#### TrialAppModel + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| completion_params | [JsonObject](#jsonobject) | | No | +| mode | string | | No | +| name | string | | Yes | +| provider | string | | Yes | + #### TrialAppModelConfig | Name | Type | Description | Required | @@ -21364,6 +21422,35 @@ Enum class for tool provider | updated_by | string | | No | | user_input_form | [ object ] | | No | +#### TrialAppModelConfigResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| agent_mode | [TrialAppAgentMode](#trialappagentmode) | | No | +| annotation_reply | [JsonObject](#jsonobject) | | No | +| chat_prompt_config | [JsonObject](#jsonobject) | | No | +| completion_prompt_config | [JsonObject](#jsonobject) | | No | +| created_at | integer | | No | +| created_by | string | | No | +| dataset_configs | [JsonObject](#jsonobject) | | No | +| dataset_query_variable | string | | No | +| external_data_tools | [ [JsonObject](#jsonobject) ] | | No | +| file_upload | [JsonObject](#jsonobject) | | No | +| model | [TrialAppModel](#trialappmodel) | | No | +| more_like_this | [JsonObject](#jsonobject) | | No | +| opening_statement | string | | No | +| pre_prompt | string | | No | +| prompt_type | string | | No | +| retriever_resource | [JsonObject](#jsonobject) | | No | +| sensitive_word_avoidance | [JsonObject](#jsonobject) | | No | +| speech_to_text | [JsonObject](#jsonobject) | | No | +| suggested_questions | [ string ] | | No | +| suggested_questions_after_answer | [JsonObject](#jsonobject) | | No | +| text_to_speech | [JsonObject](#jsonobject) | | No | +| updated_at | integer | | No | +| updated_by | string | | No | +| user_input_form | [ [JsonObject](#jsonobject) ] | | No | + #### TrialConversationVariable | Name | Type | Description | Required | @@ -21406,6 +21493,30 @@ Enum class for tool provider | limit | integer,
**Default:** 20 | Number of items per page | No | | page | integer,
**Default:** 1 | Page number | No | +#### TrialDatasetListResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| data | [ [TrialDatasetResponse](#trialdatasetresponse) ] | | Yes | +| has_more | boolean | | Yes | +| limit | integer | | Yes | +| page | integer | | Yes | +| total | integer | | Yes | + +#### TrialDatasetResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| created_at | integer | | No | +| created_by | string | | No | +| data_source_type | string | | No | +| description | string | | No | +| id | string | | Yes | +| indexing_technique | string | | No | +| name | string | | Yes | +| permission | string | | No | +| permission_keys | [ string ] | | No | + #### TrialDeletedTool | Name | Type | Description | Required | @@ -21414,6 +21525,20 @@ Enum class for tool provider | tool_name | string | | No | | type | string | | No | +#### TrialDeletedToolResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| provider_id | string | | Yes | +| tool_name | string | | Yes | +| type | string | | Yes | + +#### TrialIconType + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| TrialIconType | string | | | + #### TrialModelsResponse | Name | Type | Description | Required | @@ -21476,6 +21601,36 @@ Enum class for tool provider | updated_by | string | | No | | use_icon_as_answer_icon | boolean | | No | +#### TrialSiteResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| access_token | string | | No | +| app_base_url | string | | No | +| chat_color_theme | string | | No | +| chat_color_theme_inverted | boolean | | No | +| code | string | | No | +| copyright | string | | No | +| created_at | integer | | No | +| created_by | string | | No | +| custom_disclaimer | string | | No | +| customize_domain | string | | No | +| customize_token_strategy | string | | No | +| default_language | string | | Yes | +| description | string | | No | +| icon | string | | No | +| icon_background | string | | No | +| icon_type | [TrialIconType](#trialicontype) | | No | +| icon_url | string | | No | +| input_placeholder | string | | No | +| privacy_policy | string | | No | +| prompt_public | boolean | | No | +| show_workflow_steps | boolean | | No | +| title | string | | Yes | +| updated_at | integer | | No | +| updated_by | string | | No | +| use_icon_as_answer_icon | boolean | | No | + #### TrialTag | Name | Type | Description | Required | @@ -21484,6 +21639,14 @@ Enum class for tool provider | name | string | | No | | type | string | | No | +#### TrialTagResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| id | string | | Yes | +| name | string | | Yes | +| type | string | | Yes | + #### TrialWorkflow | Name | Type | Description | Required | @@ -21504,6 +21667,14 @@ Enum class for tool provider | updated_by | [TrialSimpleAccount](#trialsimpleaccount) | | No | | version | string | | No | +#### TrialWorkflowAccount + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| email | string | | No | +| id | string | | Yes | +| name | string | | No | + #### TrialWorkflowPartial | Name | Type | Description | Required | @@ -21514,6 +21685,36 @@ Enum class for tool provider | updated_at | long | | No | | updated_by | string | | No | +#### TrialWorkflowPartialResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| created_at | integer | | No | +| created_by | string | | No | +| id | string | | Yes | +| updated_at | integer | | No | +| updated_by | string | | No | + +#### TrialWorkflowResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| conversation_variables | [ [JsonObject](#jsonobject) ] | | No | +| created_at | integer | | No | +| created_by | [TrialWorkflowAccount](#trialworkflowaccount) | | No | +| environment_variables | [ [JsonObject](#jsonobject) ] | | No | +| features | [JsonObject](#jsonobject) | | No | +| graph | [JsonObject](#jsonobject) | | Yes | +| hash | string | | No | +| id | string | | Yes | +| marked_comment | string | | No | +| marked_name | string | | No | +| rag_pipeline_variables | [ [JsonObject](#jsonobject) ] | | No | +| tool_published | boolean | | No | +| updated_at | integer | | No | +| updated_by | [TrialWorkflowAccount](#trialworkflowaccount) | | No | +| version | string | | No | + #### TriggerCreationMethod | Name | Type | Description | Required | diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 3b59147205e..11b9f3ee0cc 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -7274,19 +7274,6 @@ "count": 1 } }, - "web/service/try-app.spec.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/service/try-app.ts": { - "no-barrel-files/no-barrel-files": { - "count": 2 - }, - "no-restricted-imports": { - "count": 1 - } - }, "web/service/use-apps.ts": { "no-restricted-imports": { "count": 1 @@ -7396,11 +7383,6 @@ "count": 1 } }, - "web/types/app.ts": { - "ts/no-explicit-any": { - "count": 1 - } - }, "web/types/assets.d.ts": { "ts/no-explicit-any": { "count": 5 @@ -7472,16 +7454,6 @@ "count": 1 } }, - "web/utils/model-config.spec.ts": { - "ts/no-explicit-any": { - "count": 13 - } - }, - "web/utils/model-config.ts": { - "ts/no-explicit-any": { - "count": 6 - } - }, "web/utils/tool-call.spec.ts": { "ts/no-explicit-any": { "count": 1 diff --git a/packages/contracts/generated/api/console/trial-apps/types.gen.ts b/packages/contracts/generated/api/console/trial-apps/types.gen.ts index 93eb2362d59..9e8f01540be 100644 --- a/packages/contracts/generated/api/console/trial-apps/types.gen.ts +++ b/packages/contracts/generated/api/console/trial-apps/types.gen.ts @@ -4,31 +4,31 @@ export type ClientOptions = { baseUrl: `${string}://${string}/console/api` | (string & {}) } -export type TrialAppDetailWithSite = { - access_mode?: string - api_base_url?: string - created_at?: number - created_by?: string - deleted_tools?: Array - description?: string - enable_api?: boolean - enable_site?: boolean - icon?: string - icon_background?: string - icon_type?: string - icon_url?: string - id?: string - max_active_requests?: number - mode?: string - model_config?: TrialAppModelConfig - name?: string +export type TrialAppDetailResponse = { + access_mode?: string | null + api_base_url?: string | null + created_at?: number | null + created_by?: string | null + deleted_tools?: Array + description?: string | null + enable_api: boolean + enable_site: boolean + icon?: string | null + icon_background?: string | null + icon_type?: TrialIconType | null + icon_url?: string | null + id: string + max_active_requests?: number | null + mode: TrialAppMode + model_config?: TrialAppModelConfigResponse | null + name: string permission_keys?: Array - site?: TrialSite - tags?: Array - updated_at?: number - updated_by?: string - use_icon_as_answer_icon?: boolean - workflow?: TrialWorkflowPartial + site: TrialSiteResponse + tags?: Array + updated_at?: number | null + updated_by?: string | null + use_icon_as_answer_icon?: boolean | null + workflow?: TrialWorkflowPartialResponse | null } export type AudioTranscriptResponse = { @@ -58,12 +58,12 @@ export type CompletionRequest = { retriever_from?: string } -export type TrialDatasetList = { - data?: Array - has_more?: boolean - limit?: number - page?: number - total?: number +export type TrialDatasetListResponse = { + data: Array + has_more: boolean + limit: number + page: number + total: number } export type SuggestedQuestionsResponse = { @@ -112,28 +112,22 @@ export type TextToSpeechRequest = { export type AudioBinaryResponse = Blob | File -export type TrialWorkflow = { - conversation_variables?: Array - created_at?: number - created_by?: TrialSimpleAccount - environment_variables?: Array<{ - [key: string]: unknown - }> - features?: { - [key: string]: unknown - } - graph?: { - [key: string]: unknown - } - hash?: string - id?: string - marked_comment?: string - marked_name?: string - rag_pipeline_variables?: Array - tool_published?: boolean - updated_at?: number - updated_by?: TrialSimpleAccount - version?: string +export type TrialWorkflowResponse = { + conversation_variables?: Array + created_at?: number | null + created_by?: TrialWorkflowAccount | null + environment_variables?: Array + features?: JsonObject2 + graph: JsonObject2 + hash?: string | null + id: string + marked_comment?: string | null + marked_name?: string | null + rag_pipeline_variables?: Array + tool_published?: boolean | null + updated_at?: number | null + updated_by?: TrialWorkflowAccount | null + version?: string | null } export type WorkflowRunRequest = { @@ -147,108 +141,83 @@ export type SimpleResultResponse = { result: string } -export type TrialDeletedTool = { - provider_id?: string - tool_name?: string - type?: string +export type TrialDeletedToolResponse = { + provider_id: string + tool_name: string + type: string } -export type TrialAppModelConfig = { - agent_mode?: { - [key: string]: unknown - } - annotation_reply?: { - [key: string]: unknown - } - chat_prompt_config?: { - [key: string]: unknown - } - completion_prompt_config?: { - [key: string]: unknown - } - created_at?: number - created_by?: string - dataset_configs?: { - [key: string]: unknown - } - dataset_query_variable?: string - external_data_tools?: Array<{ - [key: string]: unknown - }> - file_upload?: { - [key: string]: unknown - } - model?: { - [key: string]: unknown - } - more_like_this?: { - [key: string]: unknown - } - opening_statement?: string - pre_prompt?: string - prompt_type?: string - retriever_resource?: { - [key: string]: unknown - } - sensitive_word_avoidance?: { - [key: string]: unknown - } - speech_to_text?: { - [key: string]: unknown - } +export type TrialIconType = 'emoji' | 'image' | 'link' + +export type TrialAppMode = 'advanced-chat' | 'agent-chat' | 'chat' | 'completion' | 'workflow' + +export type TrialAppModelConfigResponse = { + agent_mode?: TrialAppAgentMode | null + annotation_reply?: JsonObject2 | null + chat_prompt_config?: JsonObject2 | null + completion_prompt_config?: JsonObject2 | null + created_at?: number | null + created_by?: string | null + dataset_configs?: JsonObject2 | null + dataset_query_variable?: string | null + external_data_tools?: Array + file_upload?: JsonObject2 | null + model?: TrialAppModel | null + more_like_this?: JsonObject2 | null + opening_statement?: string | null + pre_prompt?: string | null + prompt_type?: string | null + retriever_resource?: JsonObject2 | null + sensitive_word_avoidance?: JsonObject2 | null + speech_to_text?: JsonObject2 | null suggested_questions?: Array - suggested_questions_after_answer?: { - [key: string]: unknown - } - text_to_speech?: { - [key: string]: unknown - } - updated_at?: number - updated_by?: string - user_input_form?: Array<{ - [key: string]: unknown - }> + suggested_questions_after_answer?: JsonObject2 | null + text_to_speech?: JsonObject2 | null + updated_at?: number | null + updated_by?: string | null + user_input_form?: Array } -export type TrialSite = { - access_token?: string - app_base_url?: string - chat_color_theme?: string - chat_color_theme_inverted?: boolean - code?: string - copyright?: string - created_at?: number - created_by?: string - custom_disclaimer?: string - customize_domain?: string - customize_token_strategy?: string - default_language?: string - description?: string - icon?: string - icon_background?: string - icon_type?: string - icon_url?: string - privacy_policy?: string - prompt_public?: boolean - show_workflow_steps?: boolean - title?: string - updated_at?: number - updated_by?: string - use_icon_as_answer_icon?: boolean +export type TrialSiteResponse = { + access_token?: string | null + app_base_url?: string | null + chat_color_theme?: string | null + chat_color_theme_inverted?: boolean | null + code?: string | null + copyright?: string | null + created_at?: number | null + created_by?: string | null + custom_disclaimer?: string | null + customize_domain?: string | null + customize_token_strategy?: string | null + default_language: string + description?: string | null + icon?: string | null + icon_background?: string | null + icon_type?: TrialIconType | null + icon_url?: string | null + input_placeholder?: string | null + privacy_policy?: string | null + prompt_public?: boolean | null + show_workflow_steps?: boolean | null + title: string + updated_at?: number | null + updated_by?: string | null + use_icon_as_answer_icon?: boolean | null } -export type TrialTag = { - id?: string - name?: string - type?: string +export type TrialTagResponse = { + id: string + name: string + type: string } -export type TrialWorkflowPartial = { - created_at?: number - created_by?: string - id?: string - updated_at?: number - updated_by?: string +export type TrialWorkflowPartialResponse = { + created_at?: number | null + created_by?: string | null + id: string + updated_at?: number | null + updated_by?: string | null } export type JsonValue @@ -262,15 +231,15 @@ export type JsonValue | Array | null -export type TrialDataset = { - created_at?: number - created_by?: string - data_source_type?: string - description?: string - id?: string - indexing_technique?: string - name?: string - permission?: string +export type TrialDatasetResponse = { + created_at?: number | null + created_by?: string | null + data_source_type?: string | null + description?: string | null + id: string + indexing_technique?: string | null + name: string + permission?: string | null permission_keys?: Array } @@ -286,53 +255,27 @@ export type SystemParameters = { workflow_file_upload_limit: number } -export type TrialConversationVariable = { - description?: string - id?: string - name?: string - value?: - | string - | number - | number - | boolean - | { - [key: string]: unknown - } - | Array - | null - value_type?: string +export type JsonObject2 = { + [key: string]: unknown } -export type TrialSimpleAccount = { - email?: string - id?: string - name?: string +export type TrialWorkflowAccount = { + email?: string | null + id: string + name?: string | null } -export type TrialPipelineVariable = { - allow_file_extension?: Array - allow_file_upload_methods?: Array - allowed_file_types?: Array - belong_to_node_id?: string - default_value?: - | string - | number - | number - | boolean - | { - [key: string]: unknown - } - | Array - | null - label?: string - max_length?: number - options?: Array - placeholder?: string - required?: boolean - tooltips?: string - type?: string - unit?: string - variable?: string +export type TrialAppAgentMode = { + enabled?: boolean | null + strategy?: string | null + tools?: Array +} + +export type TrialAppModel = { + completion_params?: JsonObject2 + mode?: string | null + name: string + provider: string } export type GeneratedAppResponseWritable = JsonValue @@ -364,7 +307,7 @@ export type GetTrialAppsByAppIdData = { } export type GetTrialAppsByAppIdResponses = { - 200: TrialAppDetailWithSite + 200: TrialAppDetailResponse } export type GetTrialAppsByAppIdResponse @@ -432,7 +375,7 @@ export type GetTrialAppsByAppIdDatasetsData = { } export type GetTrialAppsByAppIdDatasetsResponses = { - 200: TrialDatasetList + 200: TrialDatasetListResponse } export type GetTrialAppsByAppIdDatasetsResponse @@ -513,7 +456,7 @@ export type GetTrialAppsByAppIdWorkflowsData = { } export type GetTrialAppsByAppIdWorkflowsResponses = { - 200: TrialWorkflow + 200: TrialWorkflowResponse } export type GetTrialAppsByAppIdWorkflowsResponse diff --git a/packages/contracts/generated/api/console/trial-apps/zod.gen.ts b/packages/contracts/generated/api/console/trial-apps/zod.gen.ts index 7d8a11abe0b..0a7be25c6e3 100644 --- a/packages/contracts/generated/api/console/trial-apps/zod.gen.ts +++ b/packages/contracts/generated/api/console/trial-apps/zod.gen.ts @@ -90,169 +90,74 @@ export const zSimpleResultResponse = z.object({ result: z.string(), }) -export const zTrialDeletedTool = z.object({ - provider_id: z.string().optional(), - tool_name: z.string().optional(), - type: z.string().optional(), +/** + * TrialDeletedToolResponse + */ +export const zTrialDeletedToolResponse = z.object({ + provider_id: z.string(), + tool_name: z.string(), + type: z.string(), }) -export const zTrialAppModelConfig = z.object({ - agent_mode: z.record(z.string(), z.unknown()).optional(), - annotation_reply: z.record(z.string(), z.unknown()).optional(), - chat_prompt_config: z.record(z.string(), z.unknown()).optional(), - completion_prompt_config: z.record(z.string(), z.unknown()).optional(), - created_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - created_by: z.string().optional(), - dataset_configs: z.record(z.string(), z.unknown()).optional(), - dataset_query_variable: z.string().optional(), - external_data_tools: z.array(z.record(z.string(), z.unknown())).optional(), - file_upload: z.record(z.string(), z.unknown()).optional(), - model: z.record(z.string(), z.unknown()).optional(), - more_like_this: z.record(z.string(), z.unknown()).optional(), - opening_statement: z.string().optional(), - pre_prompt: z.string().optional(), - prompt_type: z.string().optional(), - retriever_resource: z.record(z.string(), z.unknown()).optional(), - sensitive_word_avoidance: z.record(z.string(), z.unknown()).optional(), - speech_to_text: z.record(z.string(), z.unknown()).optional(), - suggested_questions: z.array(z.string()).optional(), - suggested_questions_after_answer: z.record(z.string(), z.unknown()).optional(), - text_to_speech: z.record(z.string(), z.unknown()).optional(), - updated_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - updated_by: z.string().optional(), - user_input_form: z.array(z.record(z.string(), z.unknown())).optional(), +export const zTrialIconType = z.enum(['emoji', 'image', 'link']) + +export const zTrialAppMode = z.enum([ + 'advanced-chat', + 'agent-chat', + 'chat', + 'completion', + 'workflow', +]) + +/** + * TrialSiteResponse + */ +export const zTrialSiteResponse = z.object({ + access_token: z.string().nullish(), + app_base_url: z.string().nullish(), + chat_color_theme: z.string().nullish(), + chat_color_theme_inverted: z.boolean().nullish(), + code: z.string().nullish(), + copyright: z.string().nullish(), + created_at: z.int().nullish(), + created_by: z.string().nullish(), + custom_disclaimer: z.string().nullish(), + customize_domain: z.string().nullish(), + customize_token_strategy: z.string().nullish(), + default_language: z.string(), + description: z.string().nullish(), + icon: z.string().nullish(), + icon_background: z.string().nullish(), + icon_type: zTrialIconType.nullish(), + icon_url: z.string().nullish(), + input_placeholder: z.string().nullish(), + privacy_policy: z.string().nullish(), + prompt_public: z.boolean().nullish(), + show_workflow_steps: z.boolean().nullish(), + title: z.string(), + updated_at: z.int().nullish(), + updated_by: z.string().nullish(), + use_icon_as_answer_icon: z.boolean().nullish(), }) -export const zTrialSite = z.object({ - access_token: z.string().optional(), - app_base_url: z.string().optional(), - chat_color_theme: z.string().optional(), - chat_color_theme_inverted: z.boolean().optional(), - code: z.string().optional(), - copyright: z.string().optional(), - created_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - created_by: z.string().optional(), - custom_disclaimer: z.string().optional(), - customize_domain: z.string().optional(), - customize_token_strategy: z.string().optional(), - default_language: z.string().optional(), - description: z.string().optional(), - icon: z.string().optional(), - icon_background: z.string().optional(), - icon_type: z.string().optional(), - icon_url: z.string().optional(), - privacy_policy: z.string().optional(), - prompt_public: z.boolean().optional(), - show_workflow_steps: z.boolean().optional(), - title: z.string().optional(), - updated_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - updated_by: z.string().optional(), - use_icon_as_answer_icon: z.boolean().optional(), +/** + * TrialTagResponse + */ +export const zTrialTagResponse = z.object({ + id: z.string(), + name: z.string(), + type: z.string(), }) -export const zTrialTag = z.object({ - id: z.string().optional(), - name: z.string().optional(), - type: z.string().optional(), -}) - -export const zTrialWorkflowPartial = z.object({ - created_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - created_by: z.string().optional(), - id: z.string().optional(), - updated_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - updated_by: z.string().optional(), -}) - -export const zTrialAppDetailWithSite = z.object({ - access_mode: z.string().optional(), - api_base_url: z.string().optional(), - created_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - created_by: z.string().optional(), - deleted_tools: z.array(zTrialDeletedTool).optional(), - description: z.string().optional(), - enable_api: z.boolean().optional(), - enable_site: z.boolean().optional(), - icon: z.string().optional(), - icon_background: z.string().optional(), - icon_type: z.string().optional(), - icon_url: z.string().optional(), - id: z.string().optional(), - max_active_requests: z.int().optional(), - mode: z.string().optional(), - model_config: zTrialAppModelConfig.optional(), - name: z.string().optional(), - permission_keys: z.array(z.string()).optional(), - site: zTrialSite.optional(), - tags: z.array(zTrialTag).optional(), - updated_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - updated_by: z.string().optional(), - use_icon_as_answer_icon: z.boolean().optional(), - workflow: zTrialWorkflowPartial.optional(), +/** + * TrialWorkflowPartialResponse + */ +export const zTrialWorkflowPartialResponse = z.object({ + created_at: z.int().nullish(), + created_by: z.string().nullish(), + id: z.string(), + updated_at: z.int().nullish(), + updated_by: z.string().nullish(), }) export const zJsonValue = z @@ -271,32 +176,30 @@ export const zJsonValue = z */ export const zGeneratedAppResponse = zJsonValue -export const zTrialDataset = z.object({ - created_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - created_by: z.string().optional(), - data_source_type: z.string().optional(), - description: z.string().optional(), - id: z.string().optional(), - indexing_technique: z.string().optional(), - name: z.string().optional(), - permission: z.string().optional(), +/** + * TrialDatasetResponse + */ +export const zTrialDatasetResponse = z.object({ + created_at: z.int().nullish(), + created_by: z.string().nullish(), + data_source_type: z.string().nullish(), + description: z.string().nullish(), + id: z.string(), + indexing_technique: z.string().nullish(), + name: z.string(), + permission: z.string().nullish(), permission_keys: z.array(z.string()).optional(), }) -export const zTrialDatasetList = z.object({ - data: z.array(zTrialDataset).optional(), - has_more: z.boolean().optional(), - limit: z.int().optional(), - page: z.int().optional(), - total: z.int().optional(), +/** + * TrialDatasetListResponse + */ +export const zTrialDatasetListResponse = z.object({ + data: z.array(zTrialDatasetResponse), + has_more: z.boolean(), + limit: z.int(), + page: z.int(), + total: z.int(), }) export const zJsonObject = z.record(z.string(), z.unknown()) @@ -330,87 +233,115 @@ export const zParameters = z.object({ user_input_form: z.array(zJsonObject), }) -export const zTrialConversationVariable = z.object({ - description: z.string().optional(), - id: z.string().optional(), - name: z.string().optional(), - value: z - .union([ - z.string(), - z.int(), - z.number(), - z.boolean(), - z.record(z.string(), z.unknown()), - z.array(z.unknown()), - ]) - .nullish(), - value_type: z.string().optional(), +export const zJsonObject2 = z.record(z.string(), z.unknown()) + +/** + * TrialWorkflowAccount + */ +export const zTrialWorkflowAccount = z.object({ + email: z.string().nullish(), + id: z.string(), + name: z.string().nullish(), }) -export const zTrialSimpleAccount = z.object({ - email: z.string().optional(), - id: z.string().optional(), - name: z.string().optional(), +/** + * TrialWorkflowResponse + */ +export const zTrialWorkflowResponse = z.object({ + conversation_variables: z.array(zJsonObject2).optional(), + created_at: z.int().nullish(), + created_by: zTrialWorkflowAccount.nullish(), + environment_variables: z.array(zJsonObject2).optional(), + features: zJsonObject2.optional(), + graph: zJsonObject2, + hash: z.string().nullish(), + id: z.string(), + marked_comment: z.string().nullish(), + marked_name: z.string().nullish(), + rag_pipeline_variables: z.array(zJsonObject2).optional(), + tool_published: z.boolean().nullish(), + updated_at: z.int().nullish(), + updated_by: zTrialWorkflowAccount.nullish(), + version: z.string().nullish(), }) -export const zTrialPipelineVariable = z.object({ - allow_file_extension: z.array(z.string()).optional(), - allow_file_upload_methods: z.array(z.string()).optional(), - allowed_file_types: z.array(z.string()).optional(), - belong_to_node_id: z.string().optional(), - default_value: z - .union([ - z.string(), - z.int(), - z.number(), - z.boolean(), - z.record(z.string(), z.unknown()), - z.array(z.unknown()), - ]) - .nullish(), - label: z.string().optional(), - max_length: z.int().optional(), - options: z.array(z.string()).optional(), - placeholder: z.string().optional(), - required: z.boolean().optional(), - tooltips: z.string().optional(), - type: z.string().optional(), - unit: z.string().optional(), - variable: z.string().optional(), +/** + * TrialAppAgentMode + */ +export const zTrialAppAgentMode = z.object({ + enabled: z.boolean().nullish(), + strategy: z.string().nullish(), + tools: z.array(zJsonObject2).optional(), }) -export const zTrialWorkflow = z.object({ - conversation_variables: z.array(zTrialConversationVariable).optional(), - created_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - created_by: zTrialSimpleAccount.optional(), - environment_variables: z.array(z.record(z.string(), z.unknown())).optional(), - features: z.record(z.string(), z.unknown()).optional(), - graph: z.record(z.string(), z.unknown()).optional(), - hash: z.string().optional(), - id: z.string().optional(), - marked_comment: z.string().optional(), - marked_name: z.string().optional(), - rag_pipeline_variables: z.array(zTrialPipelineVariable).optional(), - tool_published: z.boolean().optional(), - updated_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - updated_by: zTrialSimpleAccount.optional(), - version: z.string().optional(), +/** + * TrialAppModel + */ +export const zTrialAppModel = z.object({ + completion_params: zJsonObject2.optional(), + mode: z.string().nullish(), + name: z.string(), + provider: z.string(), +}) + +/** + * TrialAppModelConfigResponse + */ +export const zTrialAppModelConfigResponse = z.object({ + agent_mode: zTrialAppAgentMode.nullish(), + annotation_reply: zJsonObject2.nullish(), + chat_prompt_config: zJsonObject2.nullish(), + completion_prompt_config: zJsonObject2.nullish(), + created_at: z.int().nullish(), + created_by: z.string().nullish(), + dataset_configs: zJsonObject2.nullish(), + dataset_query_variable: z.string().nullish(), + external_data_tools: z.array(zJsonObject2).optional(), + file_upload: zJsonObject2.nullish(), + model: zTrialAppModel.nullish(), + more_like_this: zJsonObject2.nullish(), + opening_statement: z.string().nullish(), + pre_prompt: z.string().nullish(), + prompt_type: z.string().nullish(), + retriever_resource: zJsonObject2.nullish(), + sensitive_word_avoidance: zJsonObject2.nullish(), + speech_to_text: zJsonObject2.nullish(), + suggested_questions: z.array(z.string()).optional(), + suggested_questions_after_answer: zJsonObject2.nullish(), + text_to_speech: zJsonObject2.nullish(), + updated_at: z.int().nullish(), + updated_by: z.string().nullish(), + user_input_form: z.array(zJsonObject2).optional(), +}) + +/** + * TrialAppDetailResponse + */ +export const zTrialAppDetailResponse = z.object({ + access_mode: z.string().nullish(), + api_base_url: z.string().nullish(), + created_at: z.int().nullish(), + created_by: z.string().nullish(), + deleted_tools: z.array(zTrialDeletedToolResponse).optional(), + description: z.string().nullish(), + enable_api: z.boolean(), + enable_site: z.boolean(), + icon: z.string().nullish(), + icon_background: z.string().nullish(), + icon_type: zTrialIconType.nullish(), + icon_url: z.string().nullish(), + id: z.string(), + max_active_requests: z.int().nullish(), + mode: zTrialAppMode, + model_config: zTrialAppModelConfigResponse.nullish(), + name: z.string(), + permission_keys: z.array(z.string()).optional(), + site: zTrialSiteResponse, + tags: z.array(zTrialTagResponse).optional(), + updated_at: z.int().nullish(), + updated_by: z.string().nullish(), + use_icon_as_answer_icon: z.boolean().nullish(), + workflow: zTrialWorkflowPartialResponse.nullish(), }) /** @@ -445,7 +376,7 @@ export const zGetTrialAppsByAppIdPath = z.object({ /** * Success */ -export const zGetTrialAppsByAppIdResponse = zTrialAppDetailWithSite +export const zGetTrialAppsByAppIdResponse = zTrialAppDetailResponse export const zPostTrialAppsByAppIdAudioToTextPath = z.object({ app_id: z.uuid(), @@ -491,7 +422,7 @@ export const zGetTrialAppsByAppIdDatasetsQuery = z.object({ /** * Success */ -export const zGetTrialAppsByAppIdDatasetsResponse = zTrialDatasetList +export const zGetTrialAppsByAppIdDatasetsResponse = zTrialDatasetListResponse export const zGetTrialAppsByAppIdMessagesByMessageIdSuggestedQuestionsPath = z.object({ app_id: z.uuid(), @@ -540,7 +471,7 @@ export const zGetTrialAppsByAppIdWorkflowsPath = z.object({ /** * Success */ -export const zGetTrialAppsByAppIdWorkflowsResponse = zTrialWorkflow +export const zGetTrialAppsByAppIdWorkflowsResponse = zTrialWorkflowResponse export const zPostTrialAppsByAppIdWorkflowsRunBody = zWorkflowRunRequest diff --git a/web/app/components/explore/try-app/app-info/index.tsx b/web/app/components/explore/try-app/app-info/index.tsx index e7e6e5edde7..f2f9452adba 100644 --- a/web/app/components/explore/try-app/app-info/index.tsx +++ b/web/app/components/explore/try-app/app-info/index.tsx @@ -68,9 +68,9 @@ const AppInfo: FC = ({ { return `${MARKETPLACE_API_PREFIX}/plugins/${organization}/${pluginName}/icon` } +const isRecord = (value: unknown): value is Record => { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +const getGraphNodes = (graph: unknown): unknown[] => { + if (!isRecord(graph) || !Array.isArray(graph.nodes)) + return [] + + return graph.nodes +} + +const isAgentTool = (value: unknown): value is AgentTool => { + if (!isRecord(value)) + return false + + return typeof value.provider_id === 'string' + && typeof value.tool_label === 'string' + && value.enabled === true +} + +const hasLLMRequirementData = (value: unknown): value is { model: { name: string, provider: string } } => { + if (!isRecord(value) || !isRecord(value.model)) + return false + + return typeof value.model.name === 'string' && typeof value.model.provider === 'string' +} + +const hasToolRequirementData = (value: unknown): value is { provider_id: string, tool_label: string } => { + if (!isRecord(value)) + return false + + return typeof value.provider_id === 'string' && typeof value.tool_label === 'string' +} + const useGetRequirements = ({ appDetail, appId }: Params) => { const isBasic = ['chat', 'completion', 'agent-chat'].includes(appDetail.mode) const isAgent = appDetail.mode === 'agent-chat' @@ -75,41 +107,44 @@ const useGetRequirements = ({ appDetail, appId }: Params) => { const { data: flowData } = useGetTryAppFlowPreview(appId, isBasic) const requirements: RequirementItem[] = [] - if (isBasic) { - const modelProvider = appDetail.model_config.model.provider - const name = appDetail.model_config.model.provider.split('/').pop() || '' + const modelConfig = appDetail.model_config + const model = modelConfig?.model + if (isBasic && model) { + const modelProvider = model.provider + const name = model.provider.split('/').pop() || '' requirements.push({ name, iconUrl: getIconUrl(modelProvider, 'model'), }) } - if (isAgent) { - requirements.push(...appDetail.model_config.agent_mode.tools.filter(data => (data as AgentTool).enabled).map((data) => { - const tool = data as AgentTool - return { - name: tool.tool_label, - iconUrl: getIconUrl(tool.provider_id, 'tool'), - } - })) + if (isAgent && modelConfig?.agent_mode?.tools) { + requirements.push(...modelConfig.agent_mode.tools.filter(isAgentTool).map(tool => ({ + name: tool.tool_label, + iconUrl: getIconUrl(tool.provider_id, 'tool'), + }))) } - if (isAdvanced && flowData && flowData?.graph?.nodes?.length > 0) { - const nodes = flowData.graph.nodes - const llmNodes = nodes.filter(node => node.data.type === BlockEnum.LLM) - requirements.push(...llmNodes.map((node) => { - const data = node.data as LLMNodeType - return { + const nodes = getGraphNodes(flowData?.graph) + if (isAdvanced && nodes.length > 0) { + requirements.push(...nodes.flatMap((node) => { + const data = isRecord(node) && isRecord(node.data) ? node.data : null + if (data?.type !== BlockEnum.LLM || !hasLLMRequirementData(data)) + return [] + + return [{ name: data.model.name, iconUrl: getIconUrl(data.model.provider, 'model'), - } + }] })) - const toolNodes = nodes.filter(node => node.data.type === BlockEnum.Tool) - requirements.push(...toolNodes.map((node) => { - const data = node.data as ToolNodeType - return { + requirements.push(...nodes.flatMap((node) => { + const data = isRecord(node) && isRecord(node.data) ? node.data : null + if (data?.type !== BlockEnum.Tool || !hasToolRequirementData(data)) + return [] + + return [{ name: data.tool_label, iconUrl: getIconUrl(data.provider_id, 'tool'), - } + }] })) } diff --git a/web/app/components/explore/try-app/app/chat.tsx b/web/app/components/explore/try-app/app/chat.tsx index 0078a3b22d7..99b38632289 100644 --- a/web/app/components/explore/try-app/app/chat.tsx +++ b/web/app/components/explore/try-app/app/chat.tsx @@ -70,9 +70,9 @@ const TryApp: FC = ({
{appDetail.name}
diff --git a/web/app/components/explore/try-app/preview/__tests__/basic-app-preview.spec.tsx b/web/app/components/explore/try-app/preview/__tests__/basic-app-preview.spec.tsx index 1cd7b7c2817..2ef74301d0e 100644 --- a/web/app/components/explore/try-app/preview/__tests__/basic-app-preview.spec.tsx +++ b/web/app/components/explore/try-app/preview/__tests__/basic-app-preview.spec.tsx @@ -29,9 +29,20 @@ vi.mock('@/hooks/use-breakpoints', () => ({ }, })) -vi.mock('@/app/components/app/configuration/config', () => ({ - default: () =>
Config
, -})) +vi.mock('@/app/components/app/configuration/config', async () => { + const { useDebugConfigurationContext } = await import('@/context/debug-configuration') + + function MockConfig() { + const { modelConfig } = useDebugConfigurationContext() + const hasDeletedTool = modelConfig.agentConfig.tools.some(tool => 'tool_name' in tool && tool.isDeleted === true) + + return
Config
+ } + + return { + default: MockConfig, + } +}) vi.mock('@/app/components/app/configuration/debug', () => ({ default: () =>
Debug
, @@ -471,7 +482,8 @@ describe('BasicAppPreview', () => { } agentAppDetail.deleted_tools = [ { - id: 'tool-1', + type: 'builtin', + provider_id: 'test-provider', tool_name: 'test-tool', }, ] @@ -495,7 +507,7 @@ describe('BasicAppPreview', () => { render() await waitFor(() => { - expect(screen.getByTestId('config-component')).toBeInTheDocument() + expect(screen.getByTestId('config-component')).toHaveAttribute('data-has-deleted-tool', 'true') }) }) }) diff --git a/web/app/components/explore/try-app/preview/__tests__/flow-app-preview.spec.tsx b/web/app/components/explore/try-app/preview/__tests__/flow-app-preview.spec.tsx index 29ac8ce8a48..9332042d03e 100644 --- a/web/app/components/explore/try-app/preview/__tests__/flow-app-preview.spec.tsx +++ b/web/app/components/explore/try-app/preview/__tests__/flow-app-preview.spec.tsx @@ -14,13 +14,15 @@ vi.mock('@/app/components/workflow/workflow-preview', () => ({ miniMapToRight, nodes, edges, - }: { className?: string, miniMapToRight?: boolean, nodes?: unknown[], edges?: unknown[] }) => ( + viewport, + }: { className?: string, miniMapToRight?: boolean, nodes?: unknown[], edges?: unknown[], viewport?: unknown }) => (
WorkflowPreview
@@ -110,6 +112,25 @@ describe('FlowAppPreview', () => { expect(workflowPreview).toHaveAttribute('data-edges-count', '2') }) + it('uses a default viewport when graph viewport is missing', () => { + mockUseGetTryAppFlowPreview.mockReturnValue({ + data: { + graph: { + nodes: [], + edges: [], + }, + }, + isLoading: false, + }) + + render() + + expect(screen.getByTestId('workflow-preview')).toHaveAttribute( + 'data-viewport', + JSON.stringify({ x: 0, y: 0, zoom: 1 }), + ) + }) + it('passes miniMapToRight=true to WorkflowPreview', () => { mockUseGetTryAppFlowPreview.mockReturnValue({ data: { diff --git a/web/app/components/explore/try-app/preview/basic-app-preview.tsx b/web/app/components/explore/try-app/preview/basic-app-preview.tsx index c118b8e51e5..d45149ff86e 100644 --- a/web/app/components/explore/try-app/preview/basic-app-preview.tsx +++ b/web/app/components/explore/try-app/preview/basic-app-preview.tsx @@ -4,7 +4,8 @@ import type { FC } from 'react' import type { Features as FeaturesData, FileUpload } from '@/app/components/base/features/types' import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { ModelConfig } from '@/models/debug' -import type { ModelConfig as BackendModelConfig, PromptVariable } from '@/types/app' +import type { TryAppInfo } from '@/service/try-app' +import type { PromptVariable } from '@/types/app' import { noop } from 'es-toolkit/function' import { clone } from 'es-toolkit/object' import * as React from 'react' @@ -15,6 +16,7 @@ import { FeaturesProvider } from '@/app/components/base/features' import Loading from '@/app/components/base/loading' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { CollectionType } from '@/app/components/tools/types' import { SupportUploadFileTypes } from '@/app/components/workflow/types' import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' import ConfigContext from '@/context/debug-configuration' @@ -22,7 +24,7 @@ import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { PromptMode } from '@/models/debug' import { useAllToolProviders } from '@/service/use-tools' import { useGetTryAppDataSets, useGetTryAppInfo } from '@/service/use-try-app' -import { ModelModeType, Resolution, TransferMethod } from '@/types/app' +import { AgentStrategy, ModelModeType, Resolution, TransferMethod, TtsAutoPlay } from '@/types/app' import { correctModelProvider, correctToolProvider } from '@/utils' import { userInputsFormToPromptVariables } from '@/utils/model-config' import { basePath } from '@/utils/var' @@ -32,7 +34,17 @@ type Props = { readonly appId: string } -const defaultModelConfig = { +type AgentToolItem = Extract + +const DEFAULT_SYSTEM_PARAMETERS: ModelConfig['system_parameters'] = { + audio_file_size_limit: 0, + file_size_limit: 0, + image_file_size_limit: 0, + video_file_size_limit: 0, + workflow_file_upload_limit: 0, +} + +const defaultModelConfig: ModelConfig = { provider: 'langgenius/openai/openai', model_id: 'gpt-3.5-turbo', mode: ModelModeType.unset, @@ -50,9 +62,249 @@ const defaultModelConfig = { suggested_questions_after_answer: null, retriever_resource: null, annotation_reply: null, + system_parameters: DEFAULT_SYSTEM_PARAMETERS, dataSets: [], agentConfig: DEFAULT_AGENT_SETTING, } + +const isRecord = (value: unknown): value is Record => { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +const getString = (value: unknown) => { + return typeof value === 'string' ? value : '' +} + +const getBoolean = (value: unknown) => { + return typeof value === 'boolean' ? value : false +} + +const getNumber = (value: unknown, fallback: number) => { + return typeof value === 'number' ? value : fallback +} + +const getOptionalString = (value: unknown) => { + return typeof value === 'string' ? value : undefined +} + +const getStringArray = (value: unknown) => { + return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : undefined +} + +const getTransferMethods = (value: unknown) => { + if (!Array.isArray(value)) + return undefined + + const transferMethods = new Set(Object.values(TransferMethod)) + return value.filter((item): item is TransferMethod => typeof item === 'string' && transferMethods.has(item)) +} + +const getResolution = (value: unknown) => { + return value === Resolution.low ? Resolution.low : Resolution.high +} + +const normalizeEnabledConfig = (value: unknown): { enabled: boolean } | null => { + if (!isRecord(value)) + return null + + return { + ...value, + enabled: getBoolean(value.enabled), + } as { enabled: boolean } +} + +const normalizeTextToSpeech = (value: unknown): ModelConfig['text_to_speech'] => { + if (!isRecord(value)) + return null + + const autoPlayValue = getString(value.autoPlay) + const autoPlay = autoPlayValue === TtsAutoPlay.enabled || autoPlayValue === TtsAutoPlay.disabled + ? autoPlayValue + : undefined + + return { + ...value, + enabled: getBoolean(value.enabled), + voice: getOptionalString(value.voice), + language: getOptionalString(value.language), + ...(autoPlay ? { autoPlay } : {}), + } as ModelConfig['text_to_speech'] +} + +const normalizeAnnotationReply = (value: unknown): ModelConfig['annotation_reply'] => { + if (!isRecord(value)) + return null + + const embeddingModel = isRecord(value.embedding_model) ? value.embedding_model : {} + return { + ...value, + id: getString(value.id), + enabled: getBoolean(value.enabled), + score_threshold: getNumber(value.score_threshold, ANNOTATION_DEFAULT.score_threshold), + embedding_model: { + embedding_provider_name: getString(embeddingModel.embedding_provider_name), + embedding_model_name: getString(embeddingModel.embedding_model_name), + }, + } as ModelConfig['annotation_reply'] +} + +const normalizeModeration = (value: unknown): ModelConfig['sensitive_word_avoidance'] => { + if (!isRecord(value)) + return null + + return { + ...value, + enabled: getBoolean(value.enabled), + type: getOptionalString(value.type), + config: isRecord(value.config) ? value.config : undefined, + } as ModelConfig['sensitive_word_avoidance'] +} + +const normalizeSuggestedQuestionsAfterAnswer = (value: unknown): ModelConfig['suggested_questions_after_answer'] => { + if (!isRecord(value)) + return null + + return { + ...value, + enabled: getBoolean(value.enabled), + prompt: getOptionalString(value.prompt), + } as ModelConfig['suggested_questions_after_answer'] +} + +const normalizeFileUploadSection = (value: unknown, includeDetail = false) => { + if (!isRecord(value)) + return undefined + + return { + ...value, + enabled: getBoolean(value.enabled), + number_limits: getNumber(value.number_limits, 0), + transfer_methods: getTransferMethods(value.transfer_methods), + ...(includeDetail ? { detail: getResolution(value.detail) } : {}), + } +} + +const normalizeFileUpload = (value: unknown): ModelConfig['file_upload'] => { + if (!isRecord(value)) + return null + + return { + ...value, + enabled: getBoolean(value.enabled), + image: normalizeFileUploadSection(value.image, true), + document: normalizeFileUploadSection(value.document), + audio: normalizeFileUploadSection(value.audio), + video: normalizeFileUploadSection(value.video), + custom: normalizeFileUploadSection(value.custom), + allowed_file_types: getStringArray(value.allowed_file_types), + allowed_file_extensions: getStringArray(value.allowed_file_extensions), + allowed_file_upload_methods: getTransferMethods(value.allowed_file_upload_methods), + number_limits: getNumber(value.number_limits, 0), + } as ModelConfig['file_upload'] +} + +const normalizeExternalDataTools = (items?: Record[]): NonNullable => { + return items?.map(item => ({ + ...item, + type: getOptionalString(item.type), + label: getOptionalString(item.label), + icon: getOptionalString(item.icon), + icon_background: getOptionalString(item.icon_background), + variable: getOptionalString(item.variable), + enabled: getBoolean(item.enabled), + config: isRecord(item.config) + ? { + ...item.config, + api_based_extension_id: getOptionalString(item.config.api_based_extension_id), + } + : undefined, + } as NonNullable[number])) ?? [] +} + +const normalizeCollectionType = (value: unknown): AgentToolItem['provider_type'] => { + const type = getString(value) + const collectionTypes = new Set(Object.values(CollectionType)) + return collectionTypes.has(type) ? type as AgentToolItem['provider_type'] : CollectionType.builtIn +} + +const normalizeAgentStrategy = (value: unknown): AgentStrategy => { + const strategy = getString(value) + return strategy === AgentStrategy.react || strategy === AgentStrategy.functionCall ? strategy : DEFAULT_AGENT_SETTING.strategy +} + +const getAgentTools = (agentMode: unknown) => { + if (!isRecord(agentMode) || !Array.isArray(agentMode.tools)) + return [] + + return agentMode.tools.filter(isRecord) +} + +const isEnabledDatasetTool = (tool: Record) => { + return isRecord(tool.dataset) && tool.dataset.enabled === true +} + +const getDatasetConfigItems = (datasetConfigs: unknown) => { + if (!isRecord(datasetConfigs) || !isRecord(datasetConfigs.datasets) || !Array.isArray(datasetConfigs.datasets.datasets)) + return [] + + return datasetConfigs.datasets.datasets.filter(isRecord) +} + +const getDatasetId = (value: Record) => { + if (typeof value.id === 'string') + return value.id + + if (isRecord(value.dataset) && typeof value.dataset.id === 'string') + return value.dataset.id + + return null +} + +const normalizeExternalDataToolFormItem = (item: Record) => { + return { + external_data_tool: { + variable: getString(item.variable), + label: getString(item.label), + enabled: getBoolean(item.enabled), + type: getString(item.type), + config: isRecord(item.config) ? item.config : undefined, + required: true, + icon: getString(item.icon), + icon_background: getString(item.icon_background), + }, + } +} + +const normalizeAgentTool = ( + tool: Record, + deletedTools: TryAppInfo['deleted_tools'] | undefined, + collectionList: ReturnType['data'] | undefined, +): AgentToolItem => { + const providerId = getString(tool.provider_id) + const providerName = getString(tool.provider_name) + const providerType = normalizeCollectionType(tool.provider_type) + const toolName = getString(tool.tool_name) + const toolInCollectionList = collectionList?.find(c => providerId === c.id) + + return { + ...tool, + provider_id: providerType === CollectionType.builtIn + ? correctToolProvider(providerName, !!toolInCollectionList) + : providerId, + provider_name: providerType === CollectionType.builtIn + ? correctToolProvider(providerName, !!toolInCollectionList) + : providerName, + provider_type: providerType, + tool_name: toolName, + tool_label: getString(tool.tool_label) || toolName, + tool_parameters: isRecord(tool.tool_parameters) ? tool.tool_parameters : {}, + enabled: getBoolean(tool.enabled), + isDeleted: deletedTools?.some(deletedTool => deletedTool.provider_id === providerId && deletedTool.tool_name === toolName), + notAuthor: toolInCollectionList?.is_team_authorization === false, + credential_id: getOptionalString(tool.credential_id), + } as AgentToolItem +} + const BasicAppPreview: FC = ({ appId, }) => { @@ -73,16 +325,13 @@ const BasicAppPreview: FC = ({ const modelConfig = appDetail?.model_config if (!modelConfig) return [] - let datasets: any = null - if (modelConfig.agent_mode?.tools?.find(({ dataset }: any) => dataset?.enabled)) - datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled) - // new dataset struct - else if (modelConfig.dataset_configs.datasets?.datasets?.length > 0) - datasets = modelConfig.dataset_configs?.datasets?.datasets + const agentDatasetTools = getAgentTools(modelConfig.agent_mode).filter(isEnabledDatasetTool) + const datasetConfigItems = getDatasetConfigItems(modelConfig.dataset_configs) + const datasets = agentDatasetTools.length > 0 ? agentDatasetTools : datasetConfigItems if (datasets?.length && datasets?.length > 0) - return datasets.map(({ dataset }: any) => dataset.id) + return datasets.map(getDatasetId).filter((id): id is string => !!id) return [] })() @@ -90,81 +339,58 @@ const BasicAppPreview: FC = ({ const dataSets = dataSetData?.data || [] const isLoading = isLoadingAppDetail || isLoadingDatasets || isLoadingToolProviders - const modelConfig: ModelConfig = ((modelConfig?: BackendModelConfig) => { - if (isLoading || !modelConfig) + const modelConfig: ModelConfig = ((modelConfig?: TryAppInfo['model_config']) => { + if (isLoading || !modelConfig?.model) return defaultModelConfig const model = modelConfig.model + const mode = model.mode === ModelModeType.chat || model.mode === ModelModeType.completion ? model.mode : ModelModeType.unset - const newModelConfig = { + const agentMode = isRecord(modelConfig.agent_mode) ? modelConfig.agent_mode : {} + const newModelConfig: ModelConfig = { provider: correctModelProvider(model.provider), model_id: model.name, - mode: model.mode, + mode, configs: { prompt_template: modelConfig.pre_prompt || '', prompt_variables: userInputsFormToPromptVariables( [ - ...(modelConfig.user_input_form as any), + ...(modelConfig.user_input_form || []), ...( modelConfig.external_data_tools?.length - ? modelConfig.external_data_tools.map((item) => { - return { - external_data_tool: { - variable: item.variable as string, - label: item.label as string, - enabled: item.enabled, - type: item.type as string, - config: item.config, - required: true, - icon: item.icon, - icon_background: item.icon_background, - }, - } - }) + ? modelConfig.external_data_tools.map(normalizeExternalDataToolFormItem) : [] ), ], - modelConfig.dataset_query_variable, + modelConfig.dataset_query_variable ?? undefined, ), }, - more_like_this: modelConfig.more_like_this, - opening_statement: modelConfig.opening_statement, - suggested_questions: modelConfig.suggested_questions, - sensitive_word_avoidance: modelConfig.sensitive_word_avoidance, - speech_to_text: modelConfig.speech_to_text, - text_to_speech: modelConfig.text_to_speech, - file_upload: modelConfig.file_upload, - suggested_questions_after_answer: modelConfig.suggested_questions_after_answer, - retriever_resource: modelConfig.retriever_resource, - annotation_reply: modelConfig.annotation_reply, - external_data_tools: modelConfig.external_data_tools, + more_like_this: normalizeEnabledConfig(modelConfig.more_like_this), + opening_statement: modelConfig.opening_statement ?? '', + suggested_questions: modelConfig.suggested_questions ?? [], + sensitive_word_avoidance: normalizeModeration(modelConfig.sensitive_word_avoidance), + speech_to_text: normalizeEnabledConfig(modelConfig.speech_to_text), + text_to_speech: normalizeTextToSpeech(modelConfig.text_to_speech), + file_upload: normalizeFileUpload(modelConfig.file_upload), + suggested_questions_after_answer: normalizeSuggestedQuestionsAfterAnswer(modelConfig.suggested_questions_after_answer), + retriever_resource: normalizeEnabledConfig(modelConfig.retriever_resource), + annotation_reply: normalizeAnnotationReply(modelConfig.annotation_reply), + external_data_tools: normalizeExternalDataTools(modelConfig.external_data_tools), + system_parameters: DEFAULT_SYSTEM_PARAMETERS, dataSets, agentConfig: appDetail?.mode === 'agent-chat' // eslint-disable-next-line style/multiline-ternary ? ({ max_iteration: DEFAULT_AGENT_SETTING.max_iteration, - ...modelConfig.agent_mode, // remove dataset enabled: true, // modelConfig.agent_mode?.enabled is not correct. old app: the value of app with dataset's is always true - tools: modelConfig.agent_mode?.tools.filter((tool: any) => { + strategy: normalizeAgentStrategy(agentMode.strategy), + tools: getAgentTools(modelConfig.agent_mode).filter((tool) => { return !tool.dataset - }).map((tool: any) => { - const toolInCollectionList = collectionList?.find(c => tool.provider_id === c.id) - return { - ...tool, - isDeleted: appDetail?.deleted_tools?.some((deletedTool: any) => deletedTool.id === tool.id && deletedTool.tool_name === tool.tool_name), - notAuthor: toolInCollectionList?.is_team_authorization === false, - ...(tool.provider_type === 'builtin' - ? { - provider_id: correctToolProvider(tool.provider_name, !!toolInCollectionList), - provider_name: correctToolProvider(tool.provider_name, !!toolInCollectionList), - } - : {}), - } - }), + }).map(tool => normalizeAgentTool(tool, appDetail?.deleted_tools, collectionList)), }) : DEFAULT_AGENT_SETTING, } - return (newModelConfig as any) + return newModelConfig })(appDetail?.model_config) const mode = appDetail?.mode // const isChatApp = ['chat', 'advanced-chat', 'agent-chat'].includes(mode!) diff --git a/web/app/components/explore/try-app/preview/flow-app-preview.tsx b/web/app/components/explore/try-app/preview/flow-app-preview.tsx index fa7232e930d..d761849a67d 100644 --- a/web/app/components/explore/try-app/preview/flow-app-preview.tsx +++ b/web/app/components/explore/try-app/preview/flow-app-preview.tsx @@ -1,8 +1,11 @@ 'use client' +import type { JsonObject2 } from '@dify/contracts/api/console/trial-apps/types.gen' import type { FC } from 'react' +import type { Edge, Node } from '@/app/components/workflow/types' import { cn } from '@langgenius/dify-ui/cn' import * as React from 'react' import Loading from '@/app/components/base/loading' +import { BlockEnum } from '@/app/components/workflow/types' import WorkflowPreview from '@/app/components/workflow/workflow-preview' import { useGetTryAppFlowPreview } from '@/service/use-try-app' @@ -11,6 +14,145 @@ type Props = { readonly className?: string } +const blockTypeMap: Record = { + 'agent': BlockEnum.Agent, + 'agent-v2': BlockEnum.AgentV2, + 'answer': BlockEnum.Answer, + 'assigner': BlockEnum.Assigner, + 'code': BlockEnum.Code, + 'datasource': BlockEnum.DataSource, + 'datasource-empty': BlockEnum.DataSourceEmpty, + 'document-extractor': BlockEnum.DocExtractor, + 'end': BlockEnum.End, + 'http-request': BlockEnum.HttpRequest, + 'human-input': BlockEnum.HumanInput, + 'if-else': BlockEnum.IfElse, + 'iteration': BlockEnum.Iteration, + 'iteration-start': BlockEnum.IterationStart, + 'knowledge-index': BlockEnum.KnowledgeBase, + 'knowledge-retrieval': BlockEnum.KnowledgeRetrieval, + 'list-operator': BlockEnum.ListFilter, + 'llm': BlockEnum.LLM, + 'loop': BlockEnum.Loop, + 'loop-end': BlockEnum.LoopEnd, + 'loop-start': BlockEnum.LoopStart, + 'parameter-extractor': BlockEnum.ParameterExtractor, + 'question-classifier': BlockEnum.QuestionClassifier, + 'start': BlockEnum.Start, + 'start-placeholder': BlockEnum.StartPlaceholder, + 'template-transform': BlockEnum.TemplateTransform, + 'tool': BlockEnum.Tool, + 'trigger-plugin': BlockEnum.TriggerPlugin, + 'trigger-schedule': BlockEnum.TriggerSchedule, + 'trigger-webhook': BlockEnum.TriggerWebhook, + 'variable-aggregator': BlockEnum.VariableAggregator, + 'variable-assigner': BlockEnum.VariableAssigner, +} + +const isRecord = (value: unknown): value is Record => { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +const getString = (value: unknown) => { + return typeof value === 'string' ? value : undefined +} + +const getPosition = (value: unknown) => { + if (!isRecord(value) || typeof value.x !== 'number' || typeof value.y !== 'number') + return { x: 0, y: 0 } + + return { + x: value.x, + y: value.y, + } +} + +const getViewport = (value: unknown) => { + if ( + !isRecord(value) + || typeof value.x !== 'number' + || typeof value.y !== 'number' + || typeof value.zoom !== 'number' + ) { + return { x: 0, y: 0, zoom: 1 } + } + + return { + x: value.x, + y: value.y, + zoom: value.zoom, + } +} + +const getBlockType = (value: unknown) => { + if (typeof value !== 'string') + return null + + return blockTypeMap[value] || null +} + +const normalizeWorkflowPreviewGraph = (graph: JsonObject2) => { + const nodesData = Array.isArray(graph.nodes) ? graph.nodes : [] + const edgesData = Array.isArray(graph.edges) ? graph.edges : [] + + const nodes: Node[] = nodesData.flatMap((node) => { + if (!isRecord(node)) + return [] + + const id = getString(node.id) + if (!id) + return [] + + const data = isRecord(node.data) ? node.data : {} + const type = getBlockType(data.type) || BlockEnum.Start + const title = getString(data.title) || '' + + return [{ + id, + position: getPosition(node.position), + ...(getString(node.type) ? { type: getString(node.type) } : {}), + data: { + ...data, + desc: getString(data.desc) || '', + title, + type, + }, + }] + }) + const edges: Edge[] = edgesData.flatMap((edge) => { + if (!isRecord(edge)) + return [] + + const id = getString(edge.id) + if (!id) + return [] + + const data = isRecord(edge.data) ? edge.data : {} + const sourceType = getBlockType(data.sourceType) || BlockEnum.Start + const targetType = getBlockType(data.targetType) || BlockEnum.Start + + return [{ + id, + source: getString(edge.source) || '', + target: getString(edge.target) || '', + ...(getString(edge.type) ? { type: getString(edge.type) } : {}), + ...(getString(edge.sourceHandle) ? { sourceHandle: getString(edge.sourceHandle) } : {}), + ...(getString(edge.targetHandle) ? { targetHandle: getString(edge.targetHandle) } : {}), + data: { + ...data, + sourceType, + targetType, + }, + }] + }) + + return { + nodes, + edges, + viewport: getViewport(graph.viewport), + } +} + const FlowAppPreview: FC = ({ appId, className, @@ -26,10 +168,11 @@ const FlowAppPreview: FC = ({ } if (!data) return null + const previewGraph = normalizeWorkflowPreviewGraph(data.graph) return (
diff --git a/web/contract/console/try-app.ts b/web/contract/console/try-app.ts index 76e01ca12c3..a824b04b2da 100644 --- a/web/contract/console/try-app.ts +++ b/web/contract/console/try-app.ts @@ -1,69 +1 @@ -import type { ChatConfig } from '@/app/components/base/chat/types' -import type { DataSetListResponse } from '@/models/datasets' -import type { TryAppFlowPreview, TryAppInfo } from '@/models/try-app' -import { trialApps } from '@dify/contracts/api/console/trial-apps/orpc.gen' -import { type } from '@orpc/contract' -import { base } from '../base' - -export const trialAppInfoContract = base - .route({ - path: '/trial-apps/{appId}', - method: 'GET', - }) - .input(type<{ - params: { - appId: string - } - }>()) - .output(type()) - -export const trialAppDatasetsContract = base - .route({ - path: '/trial-apps/{appId}/datasets', - method: 'GET', - }) - .input(type<{ - params: { - appId: string - } - query: { - ids: string[] - } - }>()) - .output(type()) - -export const trialAppWorkflowsContract = base - .route({ - path: '/trial-apps/{appId}/workflows', - method: 'GET', - }) - .input(type<{ - params: { - appId: string - } - }>()) - .output(type()) - -export const trialAppParametersContract = base - .route({ - path: '/trial-apps/{appId}/parameters', - method: 'GET', - }) - .input(type<{ - params: { - appId: string - } - }>()) - .output(type()) - -export const trialAppsRouterContract = { - ...trialApps, - info: trialAppInfoContract, - datasets: trialAppDatasetsContract, - parameters: trialAppParametersContract, - workflows: trialAppWorkflowsContract, -} - -export const trialAppsConsoleRouterContract = { - trialApps: trialAppsRouterContract, -} +export const trialAppsConsoleRouterContract = {} diff --git a/web/contract/router.ts b/web/contract/router.ts index efb24c3057c..c72b56b8fcf 100644 --- a/web/contract/router.ts +++ b/web/contract/router.ts @@ -40,6 +40,7 @@ import { systemFeatures } from '@dify/contracts/api/console/system-features/orpc 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' @@ -94,6 +95,7 @@ const communityContract = { tagBindings, tags, test, + trialApps, trialModels, website, workflow, diff --git a/web/models/try-app.ts b/web/models/try-app.ts deleted file mode 100644 index eb9369d7f4a..00000000000 --- a/web/models/try-app.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { Viewport } from 'reactflow' -import type { Edge, Node } from '@/app/components/workflow/types' -import type { SiteInfo } from '@/models/share' -import type { AppModeEnum, ModelConfig } from '@/types/app' - -export type TryAppInfo = { - name: string - description: string - mode: AppModeEnum - site: SiteInfo - model_config: ModelConfig - deleted_tools: { id: string, tool_name: string }[] -} - -export type TryAppFlowPreview = { - graph: { - nodes: Node[] - edges: Edge[] - viewport: Viewport - } -} diff --git a/web/service/client.spec.ts b/web/service/client.spec.ts index 57aedf9f97c..5eedf391176 100644 --- a/web/service/client.spec.ts +++ b/web/service/client.spec.ts @@ -255,6 +255,43 @@ describe('consoleQuery transport context', () => { }), ) }) + + it('should serialize trial app dataset ids as repeated query params', async () => { + const request = vi.fn().mockResolvedValue(new Response(JSON.stringify({ + data: [], + has_more: false, + limit: 20, + page: 1, + total: 0, + }), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + })) + const consoleQuery = await loadConsoleQueryWithRequest(request) + const queryOptions = consoleQuery.trialApps.byAppId.datasets.get.queryOptions({ + input: { + params: { + app_id: 'app-1', + }, + query: { + ids: ['id-1', 'id-2'], + }, + }, + }) + + await queryOptions.queryFn({ signal: new AbortController().signal } as QueryFunctionContext) + + expect(request).toHaveBeenCalledWith( + expect.stringContaining('/trial-apps/app-1/datasets?ids=id-1&ids=id-2'), + expect.any(Object), + expect.objectContaining({ + fetchCompat: true, + }), + ) + expect(request.mock.calls[0]![0]).not.toContain('ids%5B0%5D') + }) }) // Scenario: oRPC mutation defaults own shared Agent roster cache behavior. diff --git a/web/service/client.ts b/web/service/client.ts index eb9d9bbffc8..37072779e57 100644 --- a/web/service/client.ts +++ b/web/service/client.ts @@ -44,6 +44,38 @@ function isURL(path: string) { } } +const trialAppDatasetsPathPattern = /\/trial-apps\/[^/]+\/datasets$/ +const indexedIdsQueryParamPattern = /^ids\[(\d+)\]$/ + +function normalizeConsoleOpenAPIURL(url: string | URL) { + const normalizedUrl = new URL(url) + + if (!trialAppDatasetsPathPattern.test(normalizedUrl.pathname)) + return normalizedUrl.href + + const ids: Array<{ index: number, value: string }> = [] + const indexedKeys = new Set() + + normalizedUrl.searchParams.forEach((value, key) => { + const match = indexedIdsQueryParamPattern.exec(key) + if (!match) + return + + indexedKeys.add(key) + ids.push({ index: Number(match[1]), value }) + }) + + if (!ids.length) + return normalizedUrl.href + + indexedKeys.forEach(key => normalizedUrl.searchParams.delete(key)) + ids + .sort((a, b) => a.index - b.index) + .forEach(({ value }) => normalizedUrl.searchParams.append('ids', value)) + + return normalizedUrl.href +} + export function getBaseURL(path: string) { const url = new URL(path, isURL(path) ? undefined : isClient ? window.location.origin : 'http://localhost') @@ -69,7 +101,7 @@ function createConsoleOpenAPILink(contract: AnyContractRouter): ConsoleClientLin url: getBaseURL(API_PREFIX), fetch: (input, init, options) => { return request( - input.url, + normalizeConsoleOpenAPIURL(input.url), init, { fetchCompat: true, diff --git a/web/service/try-app.spec.ts b/web/service/try-app.spec.ts index 9e9cd8d04af..d0d18cc4d72 100644 --- a/web/service/try-app.spec.ts +++ b/web/service/try-app.spec.ts @@ -1,26 +1,33 @@ -import { get } from './base' +import { consoleClient } from '@/service/client' import { fetchTryAppDatasets } from './try-app' -vi.mock('./base', () => ({ - get: vi.fn(), -})) - vi.mock('@/service/client', () => ({ consoleClient: { trialApps: { - info: vi.fn(), - workflows: vi.fn(), - parameters: vi.fn(), + byAppId: { + datasets: { + get: vi.fn(), + }, + }, }, }, })) describe('fetchTryAppDatasets', () => { - it('serializes ids as repeated query params', async () => { - vi.mocked(get).mockResolvedValue({ data: [] }) + it('passes ids to the generated client', async () => { + vi.mocked(consoleClient.trialApps.byAppId.datasets.get).mockResolvedValue({ + data: [], + has_more: false, + limit: 20, + page: 1, + total: 0, + }) await fetchTryAppDatasets('app-1', ['id-1', 'id-2']) - expect(get).toHaveBeenCalledWith('/trial-apps/app-1/datasets?ids=id-1&ids=id-2') + expect(consoleClient.trialApps.byAppId.datasets.get).toHaveBeenCalledWith({ + params: { app_id: 'app-1' }, + query: { ids: ['id-1', 'id-2'] }, + }) }) }) diff --git a/web/service/try-app.ts b/web/service/try-app.ts index 7b81e7c4faa..99a2f46aa7c 100644 --- a/web/service/try-app.ts +++ b/web/service/try-app.ts @@ -1,28 +1,292 @@ import type { ChatConfig } from '@/app/components/base/chat/types' -import type { DataSetListResponse } from '@/models/datasets' -import type { TryAppFlowPreview, TryAppInfo } from '@/models/try-app' -import qs from 'qs' +import { SupportUploadFileTypes } from '@/app/components/workflow/types' +import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING } from '@/config' +import { PromptMode } from '@/models/debug' import { consoleClient } from '@/service/client' -import { get } from './base' +import { Resolution, RETRIEVE_TYPE, TransferMethod, TtsAutoPlay } from '@/types/app' -export const fetchTryAppInfo = (appId: string): Promise => { - return consoleClient.trialApps.info({ params: { appId } }) +type TryAppParameters = import('@dify/contracts/api/console/trial-apps/types.gen').Parameters + +const transferMethodValues = new Set(Object.values(TransferMethod)) +const supportUploadFileTypeValues = new Set(Object.values(SupportUploadFileTypes)) + +const isRecord = (value: unknown): value is Record => { + return typeof value === 'object' && value !== null && !Array.isArray(value) } -export const fetchTryAppDatasets = (appId: string, ids: string[]): Promise => { - const queryString = qs.stringify({ ids }, { indices: false }) - const url = `/trial-apps/${encodeURIComponent(appId)}/datasets${queryString ? `?${queryString}` : ''}` - - return get(url) +const getString = (value: unknown, fallback = '') => { + return typeof value === 'string' ? value : fallback } -export const fetchTryAppFlowPreview = (appId: string): Promise => { - return consoleClient.trialApps.workflows({ params: { appId } }) - .then(res => res as TryAppFlowPreview) +const getOptionalString = (value: unknown) => { + return typeof value === 'string' ? value : undefined } -export const fetchTryAppParams = (appId: string): Promise => { - return consoleClient.trialApps.parameters({ params: { appId } }) +const getBoolean = (value: unknown, fallback = false) => { + return typeof value === 'boolean' ? value : fallback } -export type { TryAppInfo } from '@/models/try-app' +const getNumber = (value: unknown, fallback: number) => { + return typeof value === 'number' ? value : fallback +} + +const getStringArray = (value: unknown) => { + return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [] +} + +const getStringRecord = (value: unknown) => { + if (!isRecord(value)) + return undefined + + const record: Record = {} + Object.entries(value).forEach(([key, item]) => { + if (typeof item === 'string') + record[key] = item + }) + return record +} + +const normalizeEnabledConfig = (value: Record, fallback = false) => { + return { + ...value, + enabled: getBoolean(value.enabled, fallback), + } +} + +const normalizeTextToSpeechConfig = (value: Record): ChatConfig['text_to_speech'] => { + const autoPlay = getString(value.autoPlay) + const config = { ...value } + delete config.autoPlay + return { + ...normalizeEnabledConfig(config), + voice: getOptionalString(value.voice), + language: getOptionalString(value.language), + ...(autoPlay === TtsAutoPlay.enabled || autoPlay === TtsAutoPlay.disabled ? { autoPlay } : {}), + } +} + +const normalizeAnnotationReplyConfig = (value: Record): ChatConfig['annotation_reply'] => { + const embeddingModel = isRecord(value.embedding_model) ? value.embedding_model : {} + return { + id: getString(value.id), + enabled: getBoolean(value.enabled), + score_threshold: getNumber(value.score_threshold, ANNOTATION_DEFAULT.score_threshold), + embedding_model: { + embedding_provider_name: getString(embeddingModel.embedding_provider_name), + embedding_model_name: getString(embeddingModel.embedding_model_name), + }, + } +} + +const getTransferMethods = (value: unknown, fallback: TransferMethod[]) => { + if (!Array.isArray(value)) + return fallback + + const methods = value.filter((item): item is TransferMethod => { + return typeof item === 'string' && transferMethodValues.has(item) + }) + return methods.length > 0 ? methods : fallback +} + +const getSupportUploadFileTypes = (value: unknown): SupportUploadFileTypes[] => { + if (!Array.isArray(value)) + return [] + + return value.filter((item): item is SupportUploadFileTypes => { + return typeof item === 'string' && supportUploadFileTypeValues.has(item) + }) +} + +const normalizeVisionSettings = (value: unknown): NonNullable['image'] => { + const image = isRecord(value) ? value : {} + return { + enabled: getBoolean(image.enabled), + number_limits: getNumber(image.number_limits, 3), + detail: getString(image.detail) === Resolution.low ? Resolution.low : Resolution.high, + transfer_methods: getTransferMethods(image.transfer_methods, [TransferMethod.local_file, TransferMethod.remote_url]), + } +} + +const normalizeFileUploadConfig = (value: Record): ChatConfig['file_upload'] => { + const allowedUploadMethods = getTransferMethods(value.allowed_upload_methods, [TransferMethod.local_file, TransferMethod.remote_url]) + const allowedFileUploadMethods = getTransferMethods(value.allowed_file_upload_methods, allowedUploadMethods) + + return { + image: normalizeVisionSettings(value.image), + allowed_file_upload_methods: allowedFileUploadMethods, + allowed_upload_methods: allowedUploadMethods, + allowed_file_types: getSupportUploadFileTypes(value.allowed_file_types), + allowed_file_extensions: getStringArray(value.allowed_file_extensions), + max_length: getNumber(value.max_length, 1), + number_limits: getNumber(value.number_limits, 1), + } +} + +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 normalizeBaseInputForm = (value: Record) => { + return { + default: getString(value.default), + label: getString(value.label), + variable: getString(value.variable), + required: getBoolean(value.required, true), + hide: getBoolean(value.hide), + } +} + +const normalizeTextInputForm = (value: Record) => { + return { + ...normalizeBaseInputForm(value), + max_length: getNumber(value.max_length, 0), + } +} + +const normalizeFileInputForm = (value: Record) => { + return { + ...normalizeBaseInputForm(value), + max_length: getNumber(value.max_length, 1), + allowed_file_upload_methods: getTransferMethods(value.allowed_file_upload_methods, [ + TransferMethod.local_file, + TransferMethod.remote_url, + ]), + allowed_upload_methods: getTransferMethods(value.allowed_upload_methods, [ + TransferMethod.local_file, + TransferMethod.remote_url, + ]), + allowed_file_types: getSupportUploadFileTypes(value.allowed_file_types), + allowed_file_extensions: getStringArray(value.allowed_file_extensions), + } +} + +const normalizeUserInputFormItem = (item: Record): ChatConfig['user_input_form'][number] | null => { + if (isRecord(item['text-input'])) + return { 'text-input': normalizeTextInputForm(item['text-input']) } + + if (isRecord(item.paragraph)) + return { paragraph: normalizeTextInputForm(item.paragraph) } + + if (isRecord(item.select)) { + return { + select: { + ...normalizeBaseInputForm(item.select), + options: getStringArray(item.select.options), + }, + } + } + + if (isRecord(item.number)) { + return { + number: { + ...normalizeBaseInputForm(item.number), + max_length: getNumber(item.number.max_length, 0), + }, + } + } + + if (isRecord(item.checkbox)) { + return { + checkbox: { + ...normalizeBaseInputForm(item.checkbox), + default: getBoolean(item.checkbox.default), + }, + } + } + + if (isRecord(item.file)) + return { file: normalizeFileInputForm(item.file) } + + if (isRecord(item['file-list'])) + return { 'file-list': normalizeFileInputForm(item['file-list']) } + + if (isRecord(item.external_data_tool)) { + return { + external_data_tool: { + label: getString(item.external_data_tool.label), + variable: getString(item.external_data_tool.variable), + required: getBoolean(item.external_data_tool.required, true), + hide: getBoolean(item.external_data_tool.hide), + type: getOptionalString(item.external_data_tool.type), + enabled: getBoolean(item.external_data_tool.enabled), + icon: getOptionalString(item.external_data_tool.icon), + icon_background: getOptionalString(item.external_data_tool.icon_background), + config: getStringRecord(item.external_data_tool.config), + }, + } + } + + if (isRecord(item.json_object)) { + const jsonSchema = item.json_object.json_schema + return { + json_object: { + ...normalizeBaseInputForm(item.json_object), + json_schema: typeof jsonSchema === 'string' || isRecord(jsonSchema) ? jsonSchema : undefined, + }, + } + } + + return null +} + +const normalizeUserInputForm = (items: TryAppParameters['user_input_form']): ChatConfig['user_input_form'] => { + return items.reduce((result, item) => { + const normalized = normalizeUserInputFormItem(item) + if (normalized) + result.push(normalized) + return result + }, []) +} + +const normalizeTryAppParams = (params: TryAppParameters): ChatConfig => { + return { + opening_statement: params.opening_statement ?? '', + suggested_questions: params.suggested_questions, + suggested_questions_after_answer: normalizeEnabledConfig(params.suggested_questions_after_answer), + speech_to_text: normalizeEnabledConfig(params.speech_to_text), + text_to_speech: normalizeTextToSpeechConfig(params.text_to_speech), + retriever_resource: normalizeEnabledConfig(params.retriever_resource), + annotation_reply: normalizeAnnotationReplyConfig(params.annotation_reply), + more_like_this: normalizeEnabledConfig(params.more_like_this), + sensitive_word_avoidance: normalizeEnabledConfig(params.sensitive_word_avoidance), + file_upload: normalizeFileUploadConfig(params.file_upload), + user_input_form: normalizeUserInputForm(params.user_input_form), + system_parameters: params.system_parameters, + pre_prompt: '', + prompt_type: PromptMode.simple, + agent_mode: DEFAULT_AGENT_SETTING, + dataset_configs: defaultDatasetConfigs, + } +} + +export const fetchTryAppInfo = (appId: string) => { + return consoleClient.trialApps.byAppId.get({ params: { app_id: appId } }) +} + +export const fetchTryAppDatasets = (appId: string, ids: string[]) => { + return consoleClient.trialApps.byAppId.datasets.get({ + params: { app_id: appId }, + query: { ids }, + }) +} + +export const fetchTryAppFlowPreview = (appId: string) => { + return consoleClient.trialApps.byAppId.workflows.get({ params: { app_id: appId } }) +} + +export const fetchTryAppParams = (appId: string) => { + return consoleClient.trialApps.byAppId.parameters.get({ params: { app_id: appId } }) + .then(normalizeTryAppParams) +} + +export type TryAppInfo = Awaited> diff --git a/web/service/use-try-app.ts b/web/service/use-try-app.ts index b82290594c9..856e17812f5 100644 --- a/web/service/use-try-app.ts +++ b/web/service/use-try-app.ts @@ -1,11 +1,10 @@ -import type { DataSetListResponse } from '@/models/datasets' import { useQuery } from '@tanstack/react-query' import { consoleQuery } from '@/service/client' import { fetchTryAppDatasets, fetchTryAppFlowPreview, fetchTryAppInfo, fetchTryAppParams } from './try-app' export const useGetTryAppInfo = (appId: string) => { return useQuery({ - queryKey: consoleQuery.trialApps.info.queryKey({ input: { params: { appId } } }), + queryKey: consoleQuery.trialApps.byAppId.get.queryKey({ input: { params: { app_id: appId } } }), queryFn: () => { return fetchTryAppInfo(appId) }, @@ -15,7 +14,7 @@ export const useGetTryAppInfo = (appId: string) => { export const useGetTryAppParams = (appId: string) => { return useQuery({ - queryKey: consoleQuery.trialApps.parameters.queryKey({ input: { params: { appId } } }), + queryKey: consoleQuery.trialApps.byAppId.parameters.get.queryKey({ input: { params: { app_id: appId } } }), queryFn: () => { return fetchTryAppParams(appId) }, @@ -24,8 +23,8 @@ export const useGetTryAppParams = (appId: string) => { } export const useGetTryAppDataSets = (appId: string, ids: string[]) => { - return useQuery({ - queryKey: consoleQuery.trialApps.datasets.queryKey({ input: { params: { appId }, query: { ids } } }), + return useQuery({ + queryKey: consoleQuery.trialApps.byAppId.datasets.get.queryKey({ input: { params: { app_id: appId }, query: { ids } } }), queryFn: () => { return fetchTryAppDatasets(appId, ids) }, @@ -35,7 +34,7 @@ export const useGetTryAppDataSets = (appId: string, ids: string[]) => { export const useGetTryAppFlowPreview = (appId: string, disabled?: boolean) => { return useQuery({ - queryKey: consoleQuery.trialApps.workflows.queryKey({ input: { params: { appId } } }), + queryKey: consoleQuery.trialApps.byAppId.workflows.get.queryKey({ input: { params: { app_id: appId } } }), enabled: !disabled, queryFn: () => { return fetchTryAppFlowPreview(appId) diff --git a/web/types/app.ts b/web/types/app.ts index 1804acbfaca..c55745e76f1 100644 --- a/web/types/app.ts +++ b/web/types/app.ts @@ -85,22 +85,47 @@ export type PromptVariable = { } type TextTypeFormItem = { - default: string + default?: string label: string variable: string required: boolean - max_length: number - hide: boolean + max_length?: number + hide?: boolean } type SelectTypeFormItem = { - default: string + default?: string label: string variable: string required: boolean - options: string[] - hide: boolean + options?: string[] + hide?: boolean } + +type NumberTypeFormItem = Omit & { + default?: string | number + max_length?: number +} + +type CheckboxTypeFormItem = Omit & { + default?: string | boolean +} + +type FileTypeFormItem = Omit & Partial & { + max_length?: number +} + +type ExternalDataToolFormItem = ExternalDataTool & { + label: string + variable: string + required?: boolean + hide?: boolean +} + +type JsonObjectFormItem = Omit & { + json_schema?: string | Record +} + /** * User Input Form Item */ @@ -110,6 +135,18 @@ export type UserInputFormItem = { select: SelectTypeFormItem } | { paragraph: TextTypeFormItem +} | { + number: NumberTypeFormItem +} | { + checkbox: CheckboxTypeFormItem +} | { + file: FileTypeFormItem +} | { + 'file-list': FileTypeFormItem +} | { + external_data_tool: ExternalDataToolFormItem +} | { + json_object: JsonObjectFormItem } export type AgentTool = { @@ -118,7 +155,7 @@ export type AgentTool = { provider_name: string tool_name: string tool_label: string - tool_parameters: Record + tool_parameters: Record enabled: boolean isDeleted?: boolean notAuthor?: boolean @@ -380,7 +417,7 @@ export type App = { updated_at: number updated_by?: string } - deleted_tools?: Array<{ id: string, tool_name: string }> + deleted_tools?: Array<{ type: string, provider_id: string, tool_name: string }> /** access control */ access_mode: AccessMode max_active_requests?: number | null diff --git a/web/utils/model-config.spec.ts b/web/utils/model-config.spec.ts index f4b6b642784..de3a3ea5913 100644 --- a/web/utils/model-config.spec.ts +++ b/web/utils/model-config.spec.ts @@ -1,5 +1,6 @@ import type { PromptVariable } from '@/models/debug' import type { UserInputFormItem } from '@/types/app' +import { SupportUploadFileTypes } from '@/app/components/workflow/types' /** * Test suite for model configuration transformation utilities * @@ -18,6 +19,20 @@ import { userInputsFormToPromptVariables, } from './model-config' +const getTextInput = (item: UserInputFormItem | undefined) => { + if (!item || !('text-input' in item)) + throw new Error('Expected text-input user input form item') + + return item['text-input'] +} + +const getSelectInput = (item: UserInputFormItem | undefined) => { + if (!item || !('select' in item)) + throw new Error('Expected select user input form item') + + return item.select +} + describe('Model Config Utilities', () => { describe('userInputsFormToPromptVariables', () => { /** @@ -110,7 +125,7 @@ describe('Model Config Utilities', () => { default: '', hide: false, }, - } as any, + }, ] const result = userInputsFormToPromptVariables(userInputs) @@ -140,7 +155,7 @@ describe('Model Config Utilities', () => { default: '', hide: false, }, - } as any, + }, ] const result = userInputsFormToPromptVariables(userInputs) @@ -199,13 +214,13 @@ describe('Model Config Utilities', () => { label: 'Profile Picture', variable: 'profile_pic', required: false, - allowed_file_types: ['image'], + allowed_file_types: [SupportUploadFileTypes.image], allowed_file_extensions: ['.jpg', '.png'], allowed_file_upload_methods: ['local_file', 'remote_url'], default: '', hide: false, }, - } as any, + }, ] const result = userInputsFormToPromptVariables(userInputs) @@ -237,14 +252,14 @@ describe('Model Config Utilities', () => { label: 'Documents', variable: 'documents', required: true, - allowed_file_types: ['document'], + allowed_file_types: [SupportUploadFileTypes.document], allowed_file_extensions: ['.pdf', '.docx'], allowed_file_upload_methods: ['local_file'], max_length: 5, default: '', hide: false, }, - } as any, + }, ] const result = userInputsFormToPromptVariables(userInputs) @@ -283,7 +298,7 @@ describe('Model Config Utilities', () => { icon_background: '#FF5733', hide: false, }, - } as any, + }, ] const result = userInputsFormToPromptVariables(userInputs) @@ -349,7 +364,7 @@ describe('Model Config Utilities', () => { default: '', hide: false, }, - } as any, + }, { select: { label: 'Gender', @@ -553,7 +568,7 @@ describe('Model Config Utilities', () => { const result = promptVariablesToUserInputsForm(promptVariables) expect(result).toHaveLength(1) - expect((result[0] as any)['text-input']?.variable).toBe('valid_key') + expect(getTextInput(result[0]).variable).toBe('valid_key') }) /** @@ -613,8 +628,8 @@ describe('Model Config Utilities', () => { const result = promptVariablesToUserInputsForm(promptVariables) - expect((result[0] as any)['text-input']?.required).toBe(true) - expect((result[1] as any)['text-input']?.required).toBe(false) + expect(getTextInput(result[0]).required).toBe(true) + expect(getTextInput(result[1]).required).toBe(false) }) }) @@ -743,7 +758,7 @@ describe('Model Config Utilities', () => { bool1: 1, bool2: 0, bool3: 'yes', - bool4: null as any, + bool4: null, } const result = formatBooleanInputs(useInputs, inputs) @@ -811,9 +826,9 @@ describe('Model Config Utilities', () => { const backToUserInputs = promptVariablesToUserInputsForm(promptVars) expect(backToUserInputs).toHaveLength(2) - expect((backToUserInputs[0] as any)['text-input']?.variable).toBe('name') - expect((backToUserInputs[1] as any).select?.variable).toBe('type') - expect((backToUserInputs[1] as any).select?.options).toEqual(['A', 'B', 'C']) + expect(getTextInput(backToUserInputs[0]).variable).toBe('name') + expect(getSelectInput(backToUserInputs[1]).variable).toBe('type') + expect(getSelectInput(backToUserInputs[1]).options).toEqual(['A', 'B', 'C']) }) }) }) diff --git a/web/utils/model-config.ts b/web/utils/model-config.ts index 6ed408b37f0..ee5322a7a06 100644 --- a/web/utils/model-config.ts +++ b/web/utils/model-config.ts @@ -1,94 +1,139 @@ import type { PromptVariable } from '@/models/debug' import type { UserInputFormItem } from '@/types/app' -export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | null, dataset_query_variable?: string) => { +const isRecord = (value: unknown): value is Record => { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +const getString = (value: unknown) => { + return typeof value === 'string' ? value : '' +} + +const getOptionalString = (value: unknown) => { + return typeof value === 'string' ? value : undefined +} + +const getBoolean = (value: unknown, fallback = false) => { + return typeof value === 'boolean' ? value : fallback +} + +const getNumber = (value: unknown) => { + return typeof value === 'number' ? value : undefined +} + +const getDefaultValue = (value: unknown) => { + return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ? value : undefined +} + +const getStringArray = (value: unknown) => { + return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [] +} + +const getStringRecord = (value: unknown) => { + if (!isRecord(value)) + return undefined + + const record: Record = {} + Object.entries(value).forEach(([key, item]) => { + if (typeof item === 'string') + record[key] = item + }) + return record +} + +const getRecord = (value: unknown) => { + return isRecord(value) ? value : {} +} + +const getInputFormContent = (item: Record) => { + if (isRecord(item.paragraph)) + return { type: 'paragraph', content: item.paragraph } + + if (isRecord(item['text-input'])) + return { type: 'string', content: item['text-input'] } + + if (isRecord(item.number)) + return { type: 'number', content: item.number } + + if (isRecord(item.checkbox)) + return { type: 'boolean', content: item.checkbox } + + if (isRecord(item.file)) + return { type: 'file', content: item.file } + + if (isRecord(item['file-list'])) + return { type: 'file-list', content: item['file-list'] } + + if (isRecord(item.external_data_tool)) + return { type: getString(item.external_data_tool.type), content: item.external_data_tool } + + if (isRecord(item.json_object)) + return { type: 'json_object', content: item.json_object } + + return { type: 'select', content: getRecord(item.select) } +} + +export const userInputsFormToPromptVariables = (useInputs: Record[] | null, dataset_query_variable?: string) => { if (!useInputs) return [] const promptVariables: PromptVariable[] = [] - useInputs.forEach((item: any) => { - const isParagraph = !!item.paragraph - - const [type, content] = (() => { - if (isParagraph) - return ['paragraph', item.paragraph] - - if (item['text-input']) - return ['string', item['text-input']] - - if (item.number) - return ['number', item.number] - - if (item.checkbox) - return ['boolean', item.checkbox] - - if (item.file) - return ['file', item.file] - - if (item['file-list']) - return ['file-list', item['file-list']] - - if (item.external_data_tool) - return [item.external_data_tool.type, item.external_data_tool] - - if (item.json_object) - return ['json_object', item.json_object] - - return ['select', item.select || {}] - })() - const is_context_var = dataset_query_variable === content?.variable + useInputs.forEach((item) => { + const { type, content } = getInputFormContent(item) + const variable = getString(content.variable) + const is_context_var = dataset_query_variable === variable if (type === 'string' || type === 'paragraph') { promptVariables.push({ - key: content.variable, - name: content.label, - required: content.required, + key: variable, + name: getString(content.label), + required: getBoolean(content.required, true), type, - max_length: content.max_length, + max_length: getNumber(content.max_length), options: [], is_context_var, - hide: content.hide, - default: content.default, + hide: getBoolean(content.hide), + default: getDefaultValue(content.default), }) } else if (type === 'number') { promptVariables.push({ - key: content.variable, - name: content.label, - required: content.required, + key: variable, + name: getString(content.label), + required: getBoolean(content.required, true), type, options: [], - hide: content.hide, - default: content.default, + hide: getBoolean(content.hide), + default: getDefaultValue(content.default), }) } else if (type === 'boolean') { promptVariables.push({ - key: content.variable, - name: content.label, - required: content.required, + key: variable, + name: getString(content.label), + required: getBoolean(content.required, true), type: 'checkbox', options: [], - hide: content.hide, - default: content.default, + hide: getBoolean(content.hide), + default: getDefaultValue(content.default), }) } else if (type === 'select') { promptVariables.push({ - key: content.variable, - name: content.label, - required: content.required, + key: variable, + name: getString(content.label), + required: getBoolean(content.required, true), type: 'select', - options: content.options, + options: getStringArray(content.options), is_context_var, - hide: content.hide, - default: content.default, + hide: getBoolean(content.hide), + default: getDefaultValue(content.default), }) } else if (type === 'file') { promptVariables.push({ - key: content.variable, - name: content.label, - required: content.required, + key: variable, + name: getString(content.label), + required: getBoolean(content.required, true), type, config: { allowed_file_types: content.allowed_file_types, @@ -96,38 +141,38 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | allowed_file_upload_methods: content.allowed_file_upload_methods, number_limits: 1, }, - hide: content.hide, - default: content.default, + hide: getBoolean(content.hide), + default: getDefaultValue(content.default), }) } else if (type === 'file-list') { promptVariables.push({ - key: content.variable, - name: content.label, - required: content.required, + key: variable, + name: getString(content.label), + required: getBoolean(content.required, true), type, config: { allowed_file_types: content.allowed_file_types, allowed_file_extensions: content.allowed_file_extensions, allowed_file_upload_methods: content.allowed_file_upload_methods, - number_limits: content.max_length, + number_limits: getNumber(content.max_length), }, - hide: content.hide, - default: content.default, + hide: getBoolean(content.hide), + default: getDefaultValue(content.default), }) } else { promptVariables.push({ - key: content.variable, - name: content.label, - required: content.required, - type: content.type, - enabled: content.enabled, - config: content.config, - icon: content.icon, - icon_background: content.icon_background, + key: variable, + name: getString(content.label), + required: getBoolean(content.required, true), + type: getString(content.type || type), + enabled: getBoolean(content.enabled), + config: getRecord(content.config), + icon: getOptionalString(content.icon), + icon_background: getOptionalString(content.icon_background), is_context_var, - hide: content.hide, + hide: getBoolean(content.hide), }) } }) @@ -138,10 +183,10 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[ const userInputs: UserInputFormItem[] = [] promptVariables.filter(({ key, name }) => { return key && key.trim() && name && name.trim() - }).forEach((item: any) => { - if (item.type === 'string' || item.type === 'paragraph') { + }).forEach((item) => { + if (item.type === 'string') { userInputs.push({ - [item.type === 'string' ? 'text-input' : 'paragraph']: { + 'text-input': { label: item.name, variable: item.key, required: item.required !== false, // default true @@ -149,19 +194,43 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[ default: '', hide: item.hide, }, - } as any) + }) return } - if (item.type === 'number' || item.type === 'checkbox') { + if (item.type === 'paragraph') { userInputs.push({ - [item.type]: { + paragraph: { + label: item.name, + variable: item.key, + required: item.required !== false, // default true + max_length: item.max_length, + default: '', + hide: item.hide, + }, + }) + return + } + if (item.type === 'number') { + userInputs.push({ + number: { label: item.name, variable: item.key, required: item.required !== false, // default true default: '', hide: item.hide, }, - } as any) + }) + } + else if (item.type === 'checkbox') { + userInputs.push({ + checkbox: { + label: item.name, + variable: item.key, + required: item.required !== false, // default true + default: '', + hide: item.hide, + }, + }) } else if (item.type === 'select') { userInputs.push({ @@ -170,10 +239,10 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[ variable: item.key, required: item.required !== false, // default true options: item.options, - default: item.default ?? '', + default: getString(item.default), hide: item.hide, }, - } as any) + }) } else { userInputs.push({ @@ -182,20 +251,20 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[ variable: item.key, enabled: item.enabled, type: item.type, - config: item.config, + config: getStringRecord(item.config), required: item.required, icon: item.icon, icon_background: item.icon_background, hide: item.hide, }, - } as any) + }) } }) return userInputs } -export const formatBooleanInputs = (useInputs?: PromptVariable[] | null, inputs?: Record | null) => { +export const formatBooleanInputs = (useInputs?: PromptVariable[] | null, inputs?: Record | null) => { if (!useInputs) return inputs const res = { ...inputs }