refactor(web): migrate trial app console contracts (#38254)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Stephen Zhou
2026-07-02 00:31:49 +08:00
committed by GitHub
parent 8540ca9242
commit ca8b680322
23 changed files with 1934 additions and 824 deletions
+258 -5
View File
@@ -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)
+204 -3
View File
@@ -9385,7 +9385,7 @@ Bedrock retrieval test (internal use only)
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TrialAppDetailWithSite](#trialappdetailwithsite)<br> |
| 200 | Success | **application/json**: [TrialAppDetailResponse](#trialappdetailresponse)<br> |
### [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)<br> |
| 200 | Success | **application/json**: [TrialDatasetListResponse](#trialdatasetlistresponse)<br> |
### [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)<br> |
| 200 | Success | **application/json**: [TrialWorkflowResponse](#trialworkflowresponse)<br> |
### [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, <br>**Default:** 20 | Number of items per page | No |
| page | integer, <br>**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 |
-28
View File
@@ -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
@@ -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<TrialDeletedTool>
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<TrialDeletedToolResponse>
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<string>
site?: TrialSite
tags?: Array<TrialTag>
updated_at?: number
updated_by?: string
use_icon_as_answer_icon?: boolean
workflow?: TrialWorkflowPartial
site: TrialSiteResponse
tags?: Array<TrialTagResponse>
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<TrialDataset>
has_more?: boolean
limit?: number
page?: number
total?: number
export type TrialDatasetListResponse = {
data: Array<TrialDatasetResponse>
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<TrialConversationVariable>
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<TrialPipelineVariable>
tool_published?: boolean
updated_at?: number
updated_by?: TrialSimpleAccount
version?: string
export type TrialWorkflowResponse = {
conversation_variables?: Array<JsonObject2>
created_at?: number | null
created_by?: TrialWorkflowAccount | null
environment_variables?: Array<JsonObject2>
features?: JsonObject2
graph: JsonObject2
hash?: string | null
id: string
marked_comment?: string | null
marked_name?: string | null
rag_pipeline_variables?: Array<JsonObject2>
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<JsonObject2>
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<string>
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<JsonObject2>
}
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<unknown>
| 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<string>
}
@@ -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<unknown>
| 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<string>
allow_file_upload_methods?: Array<string>
allowed_file_types?: Array<string>
belong_to_node_id?: string
default_value?:
| string
| number
| number
| boolean
| {
[key: string]: unknown
}
| Array<unknown>
| null
label?: string
max_length?: number
options?: Array<string>
placeholder?: string
required?: boolean
tooltips?: string
type?: string
unit?: string
variable?: string
export type TrialAppAgentMode = {
enabled?: boolean | null
strategy?: string | null
tools?: Array<JsonObject2>
}
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
@@ -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
@@ -68,9 +68,9 @@ const AppInfo: FC<Props> = ({
<AppIcon
size="large"
iconType={appDetail.site.icon_type}
icon={appDetail.site.icon}
background={appDetail.site.icon_background}
imageUrl={appDetail.site.icon_url}
icon={appDetail.site.icon ?? undefined}
background={appDetail.site.icon_background ?? undefined}
imageUrl={appDetail.site.icon_url ?? undefined}
/>
<AppTypeIcon
wrapperClassName="absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm"
@@ -1,5 +1,3 @@
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
import type { ToolNodeType } from '@/app/components/workflow/nodes/tool/types'
import type { TryAppInfo } from '@/service/try-app'
import type { AgentTool } from '@/types/app'
import { uniqBy } from 'es-toolkit/compat'
@@ -68,6 +66,40 @@ const getIconUrl = (providerId: string, type: ProviderType) => {
return `${MARKETPLACE_API_PREFIX}/plugins/${organization}/${pluginName}/icon`
}
const isRecord = (value: unknown): value is Record<string, unknown> => {
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'),
}
}]
}))
}
@@ -70,9 +70,9 @@ const TryApp: FC<Props> = ({
<AppIcon
size="large"
iconType={appDetail.site.icon_type}
icon={appDetail.site.icon}
background={appDetail.site.icon_background}
imageUrl={appDetail.site.icon_url}
icon={appDetail.site.icon ?? undefined}
background={appDetail.site.icon_background ?? undefined}
imageUrl={appDetail.site.icon_url ?? undefined}
/>
<div className="grow truncate system-md-semibold text-text-primary" title={appDetail.name}>{appDetail.name}</div>
</div>
@@ -29,9 +29,20 @@ vi.mock('@/hooks/use-breakpoints', () => ({
},
}))
vi.mock('@/app/components/app/configuration/config', () => ({
default: () => <div data-testid="config-component">Config</div>,
}))
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 <div data-testid="config-component" data-has-deleted-tool={String(hasDeletedTool)}>Config</div>
}
return {
default: MockConfig,
}
})
vi.mock('@/app/components/app/configuration/debug', () => ({
default: () => <div data-testid="debug-component">Debug</div>,
@@ -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(<BasicAppPreview appId="test-app-id" />)
await waitFor(() => {
expect(screen.getByTestId('config-component')).toBeInTheDocument()
expect(screen.getByTestId('config-component')).toHaveAttribute('data-has-deleted-tool', 'true')
})
})
})
@@ -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 }) => (
<div
data-testid="workflow-preview"
className={className}
data-mini-map-to-right={miniMapToRight}
data-nodes-count={nodes?.length}
data-edges-count={edges?.length}
data-viewport={JSON.stringify(viewport)}
>
WorkflowPreview
</div>
@@ -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(<FlowAppPreview appId="test-app-id" />)
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: {
@@ -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<ModelConfig['agentConfig']['tools'][number], { tool_name: string }>
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<string, unknown> => {
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<string>(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<string, unknown>[]): NonNullable<ModelConfig['external_data_tools']> => {
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<ModelConfig['external_data_tools']>[number])) ?? []
}
const normalizeCollectionType = (value: unknown): AgentToolItem['provider_type'] => {
const type = getString(value)
const collectionTypes = new Set<string>(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<string, unknown>) => {
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<string, unknown>) => {
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<string, unknown>) => {
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<string, unknown>,
deletedTools: TryAppInfo['deleted_tools'] | undefined,
collectionList: ReturnType<typeof useAllToolProviders>['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<Props> = ({
appId,
}) => {
@@ -73,16 +325,13 @@ const BasicAppPreview: FC<Props> = ({
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<Props> = ({
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!)
@@ -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<string, BlockEnum> = {
'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<string, unknown> => {
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<Props> = ({
appId,
className,
@@ -26,10 +168,11 @@ const FlowAppPreview: FC<Props> = ({
}
if (!data)
return null
const previewGraph = normalizeWorkflowPreviewGraph(data.graph)
return (
<div className="size-full">
<WorkflowPreview
{...data.graph}
{...previewGraph}
className={cn(className)}
miniMapToRight
/>
+1 -69
View File
@@ -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<TryAppInfo>())
export const trialAppDatasetsContract = base
.route({
path: '/trial-apps/{appId}/datasets',
method: 'GET',
})
.input(type<{
params: {
appId: string
}
query: {
ids: string[]
}
}>())
.output(type<DataSetListResponse>())
export const trialAppWorkflowsContract = base
.route({
path: '/trial-apps/{appId}/workflows',
method: 'GET',
})
.input(type<{
params: {
appId: string
}
}>())
.output(type<TryAppFlowPreview>())
export const trialAppParametersContract = base
.route({
path: '/trial-apps/{appId}/parameters',
method: 'GET',
})
.input(type<{
params: {
appId: string
}
}>())
.output(type<ChatConfig>())
export const trialAppsRouterContract = {
...trialApps,
info: trialAppInfoContract,
datasets: trialAppDatasetsContract,
parameters: trialAppParametersContract,
workflows: trialAppWorkflowsContract,
}
export const trialAppsConsoleRouterContract = {
trialApps: trialAppsRouterContract,
}
export const trialAppsConsoleRouterContract = {}
+2
View File
@@ -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,
-21
View File
@@ -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
}
}
+37
View File
@@ -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.
+33 -1
View File
@@ -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<string>()
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,
+18 -11
View File
@@ -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'] },
})
})
})
+281 -17
View File
@@ -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<TryAppInfo> => {
return consoleClient.trialApps.info({ params: { appId } })
type TryAppParameters = import('@dify/contracts/api/console/trial-apps/types.gen').Parameters
const transferMethodValues = new Set<string>(Object.values(TransferMethod))
const supportUploadFileTypeValues = new Set<string>(Object.values(SupportUploadFileTypes))
const isRecord = (value: unknown): value is Record<string, unknown> => {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
export const fetchTryAppDatasets = (appId: string, ids: string[]): Promise<DataSetListResponse> => {
const queryString = qs.stringify({ ids }, { indices: false })
const url = `/trial-apps/${encodeURIComponent(appId)}/datasets${queryString ? `?${queryString}` : ''}`
return get<DataSetListResponse>(url)
const getString = (value: unknown, fallback = '') => {
return typeof value === 'string' ? value : fallback
}
export const fetchTryAppFlowPreview = (appId: string): Promise<TryAppFlowPreview> => {
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<ChatConfig> => {
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<string, string | undefined> = {}
Object.entries(value).forEach(([key, item]) => {
if (typeof item === 'string')
record[key] = item
})
return record
}
const normalizeEnabledConfig = (value: Record<string, unknown>, fallback = false) => {
return {
...value,
enabled: getBoolean(value.enabled, fallback),
}
}
const normalizeTextToSpeechConfig = (value: Record<string, unknown>): 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<string, unknown>): 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<ChatConfig['file_upload']>['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<string, unknown>): 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<string, unknown>) => {
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<string, unknown>) => {
return {
...normalizeBaseInputForm(value),
max_length: getNumber(value.max_length, 0),
}
}
const normalizeFileInputForm = (value: Record<string, unknown>) => {
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<string, unknown>): 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<ChatConfig['user_input_form']>((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<ReturnType<typeof fetchTryAppInfo>>
+5 -6
View File
@@ -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<DataSetListResponse>({
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)
+45 -8
View File
@@ -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<TextTypeFormItem, 'default' | 'max_length'> & {
default?: string | number
max_length?: number
}
type CheckboxTypeFormItem = Omit<TextTypeFormItem, 'default' | 'max_length'> & {
default?: string | boolean
}
type FileTypeFormItem = Omit<TextTypeFormItem, 'max_length'> & Partial<UploadFileSetting> & {
max_length?: number
}
type ExternalDataToolFormItem = ExternalDataTool & {
label: string
variable: string
required?: boolean
hide?: boolean
}
type JsonObjectFormItem = Omit<TextTypeFormItem, 'max_length'> & {
json_schema?: string | Record<string, unknown>
}
/**
* 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<string, any>
tool_parameters: Record<string, unknown>
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
+30 -15
View File
@@ -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'])
})
})
})
+155 -86
View File
@@ -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<string, unknown> => {
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<string, string | undefined> = {}
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<string, unknown>) => {
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<string, unknown>[] | 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<string, string | number | object | boolean> | null) => {
export const formatBooleanInputs = (useInputs?: PromptVariable[] | null, inputs?: Record<string, string | number | object | boolean | null> | null) => {
if (!useInputs)
return inputs
const res = { ...inputs }