From db97c47c5f32826e37a599ad72dbeecac6cbc3f4 Mon Sep 17 00:00:00 2001 From: chariri Date: Fri, 26 Jun 2026 02:53:46 +0900 Subject: [PATCH 1/2] refactor(api): migrate console app chat endpoints to BaseModel --- api/controllers/console/app/annotation.py | 102 ++++----- api/controllers/console/app/audio.py | 43 ++-- api/controllers/console/app/completion.py | 16 +- api/controllers/console/app/conversation.py | 34 ++- .../console/app/conversation_variables.py | 8 +- api/controllers/console/app/message.py | 79 ++----- api/controllers/console/app/statistic.py | 98 ++++---- api/fields/annotation_fields.py | 10 + api/fields/conversation_variable_fields.py | 13 +- api/openapi/markdown/console-openapi.md | 125 +++++----- .../controllers/console/app/test_audio.py | 12 +- .../controllers/console/app/test_audio_api.py | 149 ------------ .../console/app/test_message_api.py | 46 +++- .../console/app/test_statistic_api.py | 23 +- .../generated/api/console/agent/types.gen.ts | 21 +- .../generated/api/console/agent/zod.gen.ts | 21 +- .../generated/api/console/apps/orpc.gen.ts | 213 +++++++----------- .../generated/api/console/apps/types.gen.ts | 152 +++++-------- .../generated/api/console/apps/zod.gen.ts | 160 ++++++------- .../api/console/installed-apps/types.gen.ts | 1 - .../api/console/installed-apps/zod.gen.ts | 1 - packages/contracts/openapi-ts.api.config.ts | 78 ++++++- 22 files changed, 624 insertions(+), 781 deletions(-) delete mode 100644 api/tests/unit_tests/controllers/console/app/test_audio_api.py diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index 48fb4aedc63..adcf9a6a802 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -1,7 +1,7 @@ from typing import Any, Literal from uuid import UUID -from flask import abort, make_response, request +from flask import abort, request from flask_restx import Resource from pydantic import BaseModel, Field, TypeAdapter, field_validator @@ -26,10 +26,12 @@ from fields.annotation_fields import ( AnnotationExportList, AnnotationHitHistory, AnnotationHitHistoryList, + AnnotationJobStatusDetailResponse, + AnnotationJobStatusResponse, AnnotationList, ) from fields.base import ResponseModel -from libs.helper import uuid_value +from libs.helper import dump_response, uuid_value from libs.login import login_required from services.annotation_service import ( AppAnnotationService, @@ -99,23 +101,23 @@ class AnnotationFilePayload(BaseModel): return uuid_value(value) -class AnnotationJobStatusResponse(ResponseModel): - job_id: str | None = None - job_status: str | None = None - error_msg: str | None = None - record_count: int | None = None - - -class AnnotationEmbeddingModelResponse(ResponseModel): +class AnnotationSettingEmbeddingModelResponse(ResponseModel): embedding_provider_name: str | None = None embedding_model_name: str | None = None class AnnotationSettingResponse(ResponseModel): - id: str | None = None enabled: bool + id: str | None = None score_threshold: float | None = None - embedding_model: AnnotationEmbeddingModelResponse | None = None + embedding_model: AnnotationSettingEmbeddingModelResponse | None = None + + +class AnnotationBatchImportResponse(ResponseModel): + job_id: str | None = None + job_status: str | None = None + record_count: int | None = None + error_msg: str | None = None register_schema_models( @@ -142,7 +144,10 @@ register_response_schema_models( AnnotationHitHistory, AnnotationHitHistoryList, AnnotationJobStatusResponse, + AnnotationJobStatusDetailResponse, + AnnotationSettingEmbeddingModelResponse, AnnotationSettingResponse, + AnnotationBatchImportResponse, ) @@ -172,7 +177,7 @@ class AnnotationReplyActionApi(Resource): result = AppAnnotationService.enable_app_annotation(enable_args, str(app_id)) case "disable": result = AppAnnotationService.disable_app_annotation(str(app_id)) - return result, 200 + return dump_response(AnnotationJobStatusResponse, result), 200 @console_ns.route("/apps//annotation-setting") @@ -193,7 +198,7 @@ class AppAnnotationSettingDetailApi(Resource): @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT) def get(self, app_id: UUID): result = AppAnnotationService.get_app_annotation_setting_by_app_id(str(app_id)) - return result, 200 + return dump_response(AnnotationSettingResponse, result), 200 @console_ns.route("/apps//annotation-settings/") @@ -218,7 +223,7 @@ class AppAnnotationSettingUpdateApi(Resource): result = AppAnnotationService.update_app_annotation_setting( str(app_id), annotation_setting_id_str, setting_args ) - return result, 200 + return dump_response(AnnotationSettingResponse, result), 200 @console_ns.route("/apps//annotation-reply//status/") @@ -227,9 +232,7 @@ class AnnotationReplyActionStatusApi(Resource): @console_ns.doc(description="Get status of annotation reply action job") @console_ns.doc(params={"app_id": "Application ID", "job_id": "Job ID", "action": "Action type"}) @console_ns.response( - 200, - "Job status retrieved successfully", - console_ns.models[AnnotationJobStatusResponse.__name__], + 200, "Job status retrieved successfully", console_ns.models[AnnotationJobStatusDetailResponse.__name__] ) @console_ns.response(403, "Insufficient permissions") @setup_required @@ -251,7 +254,9 @@ class AnnotationReplyActionStatusApi(Resource): app_annotation_error_key = f"{action}_app_annotation_error_{job_id_str}" error_msg = redis_client.get(app_annotation_error_key).decode() - return {"job_id": job_id_str, "job_status": job_status, "error_msg": error_msg}, 200 + return AnnotationJobStatusDetailResponse( + job_id=job_id_str, job_status=job_status, error_msg=error_msg + ).model_dump(mode="json"), 200 @console_ns.route("/apps//annotations") @@ -275,14 +280,9 @@ class AnnotationApi(Resource): annotation_list, total = AppAnnotationService.get_annotation_list_by_app_id(str(app_id), page, limit, keyword) annotation_models = TypeAdapter(list[Annotation]).validate_python(annotation_list, from_attributes=True) - response = AnnotationList( - data=annotation_models, - has_more=len(annotation_list) == limit, - limit=limit, - total=total, - page=page, - ) - return response.model_dump(mode="json"), 200 + return AnnotationList( + data=annotation_models, has_more=len(annotation_list) == limit, limit=limit, total=total, page=page + ).model_dump(mode="json"), 200 @console_ns.doc("create_annotation") @console_ns.doc(description="Create a new annotation for an app") @@ -308,7 +308,7 @@ class AnnotationApi(Resource): if args.question is not None: upsert_args["question"] = args.question annotation = AppAnnotationService.up_insert_app_annotation_from_message(upsert_args, str(app_id)) - return Annotation.model_validate(annotation, from_attributes=True).model_dump(mode="json") + return dump_response(Annotation, annotation), 201 @setup_required @login_required @@ -357,14 +357,14 @@ class AnnotationExportApi(Resource): def get(self, app_id: UUID): annotation_list = AppAnnotationService.export_annotation_list_by_app_id(str(app_id)) annotation_models = TypeAdapter(list[Annotation]).validate_python(annotation_list, from_attributes=True) - response_data = AnnotationExportList(data=annotation_models).model_dump(mode="json") - - # Create response with secure headers for CSV export - response = make_response(response_data, 200) - response.headers["Content-Type"] = "application/json; charset=utf-8" - response.headers["X-Content-Type-Options"] = "nosniff" - - return response + return ( + AnnotationExportList(data=annotation_models).model_dump(mode="json"), + 200, + { + "Content-Type": "application/json; charset=utf-8", + "X-Content-Type-Options": "nosniff", + }, + ) @console_ns.route("/apps//annotations/") @@ -392,7 +392,7 @@ class AnnotationUpdateDeleteApi(Resource): annotation = AppAnnotationService.update_app_annotation_directly( update_args, str(app_id), str(annotation_id), db.session ) - return Annotation.model_validate(annotation, from_attributes=True).model_dump(mode="json") + return dump_response(Annotation, annotation) @setup_required @login_required @@ -411,9 +411,7 @@ class AnnotationBatchImportApi(Resource): @console_ns.doc(description="Batch import annotations from CSV file with rate limiting and security checks") @console_ns.doc(params={"app_id": "Application ID"}) @console_ns.response( - 200, - "Batch import started successfully", - console_ns.models[AnnotationJobStatusResponse.__name__], + 200, "Batch import started successfully", console_ns.models[AnnotationBatchImportResponse.__name__] ) @console_ns.response(403, "Insufficient permissions") @console_ns.response(400, "No file uploaded or too many files") @@ -460,7 +458,10 @@ class AnnotationBatchImportApi(Resource): if file_size == 0: raise ValueError("The uploaded file is empty") - return AppAnnotationService.batch_import_app_annotations(str(app_id), file) + return dump_response( + AnnotationBatchImportResponse, + AppAnnotationService.batch_import_app_annotations(str(app_id), file), + ) @console_ns.route("/apps//annotations/batch-import-status/") @@ -469,9 +470,7 @@ class AnnotationBatchImportStatusApi(Resource): @console_ns.doc(description="Get status of batch import job") @console_ns.doc(params={"app_id": "Application ID", "job_id": "Job ID"}) @console_ns.response( - 200, - "Job status retrieved successfully", - console_ns.models[AnnotationJobStatusResponse.__name__], + 200, "Job status retrieved successfully", console_ns.models[AnnotationJobStatusDetailResponse.__name__] ) @console_ns.response(403, "Insufficient permissions") @setup_required @@ -491,7 +490,9 @@ class AnnotationBatchImportStatusApi(Resource): indexing_error_msg_key = f"app_annotation_batch_import_error_msg_{str(job_id)}" error_msg = redis_client.get(indexing_error_msg_key).decode() - return {"job_id": job_id, "job_status": job_status, "error_msg": error_msg}, 200 + return AnnotationJobStatusDetailResponse( + job_id=str(job_id), job_status=job_status, error_msg=error_msg + ).model_dump(mode="json"), 200 @console_ns.route("/apps//annotations//hit-histories") @@ -520,11 +521,6 @@ class AnnotationHitHistoryListApi(Resource): history_models = TypeAdapter(list[AnnotationHitHistory]).validate_python( annotation_hit_history_list, from_attributes=True ) - response = AnnotationHitHistoryList( - data=history_models, - has_more=len(annotation_hit_history_list) == limit, - limit=limit, - total=total, - page=page, - ) - return response.model_dump(mode="json") + return AnnotationHitHistoryList( + data=history_models, has_more=len(annotation_hit_history_list) == limit, limit=limit, total=total, page=page + ).model_dump(mode="json") diff --git a/api/controllers/console/app/audio.py b/api/controllers/console/app/audio.py index b66c97c274c..77c30614081 100644 --- a/api/controllers/console/app/audio.py +++ b/api/controllers/console/app/audio.py @@ -1,5 +1,4 @@ import logging -from typing import Any from flask import request from flask_restx import Resource @@ -7,7 +6,6 @@ from pydantic import BaseModel, Field, RootModel from werkzeug.exceptions import InternalServerError import services -from controllers.common.fields import AudioBinaryResponse from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models from controllers.console import console_ns from controllers.console.app.error import ( @@ -31,7 +29,9 @@ from controllers.console.wraps import ( ) from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from extensions.ext_database import db +from fields.base import ResponseModel from graphon.model_runtime.errors.invoke import InvokeError +from libs.helper import dump_response from libs.login import login_required from models import App, AppMode from services.audio_service import AudioService @@ -56,16 +56,27 @@ class TextToSpeechVoiceQuery(BaseModel): language: str = Field(..., description="Language code") -class AudioTranscriptResponse(BaseModel): +class AudioTranscriptResponse(ResponseModel): text: str = Field(description="Transcribed text from audio") -class TextToSpeechVoiceListResponse(RootModel[list[dict[str, Any]]]): - root: list[dict[str, Any]] +class TextToSpeechVoiceResponse(ResponseModel): + # see api/core/plugin/impl/model.py + name: str = Field(description="Voice display name") + value: str = Field(description="Voice identifier") -register_schema_models(console_ns, AudioTranscriptResponse, TextToSpeechPayload, TextToSpeechVoiceQuery) -register_response_schema_models(console_ns, AudioBinaryResponse, TextToSpeechVoiceListResponse) +class TextToSpeechVoiceListResponse(RootModel[list[TextToSpeechVoiceResponse]]): + root: list[TextToSpeechVoiceResponse] = Field(description="Available voices") + + +register_schema_models(console_ns, TextToSpeechPayload, TextToSpeechVoiceQuery) +register_response_schema_models( + console_ns, + AudioTranscriptResponse, + TextToSpeechVoiceResponse, + TextToSpeechVoiceListResponse, +) @console_ns.route("/apps//audio-to-text") @@ -94,7 +105,7 @@ class ChatMessageAudioApi(Resource): end_user=None, ) - return response + return dump_response(AudioTranscriptResponse, response) except services.errors.app_model_config.AppModelConfigBrokenError: logger.exception("App model config broken.") raise AppUnavailableError() @@ -127,11 +138,8 @@ class ChatMessageTextApi(Resource): @console_ns.doc(description="Convert text to speech for chat messages") @console_ns.doc(params={"app_id": "App ID"}) @console_ns.expect(console_ns.models[TextToSpeechPayload.__name__]) - @console_ns.response( - 200, - "Text to speech conversion successful", - console_ns.models[AudioBinaryResponse.__name__], - ) + # TTS returns provider audio bytes, so the success response is intentionally schema-less. + @console_ns.response(200, "Text to speech conversion successful") @console_ns.response(400, "Bad request - Invalid parameters") @setup_required @login_required @@ -141,7 +149,8 @@ class ChatMessageTextApi(Resource): try: payload = TextToSpeechPayload.model_validate(console_ns.payload) - response = AudioService.transcript_tts( + # response-contract:ignore + return AudioService.transcript_tts( app_model=app_model, session=db.session, text=payload.text, @@ -149,7 +158,6 @@ class ChatMessageTextApi(Resource): message_id=payload.message_id, is_draft=True, ) - return response except services.errors.app_model_config.AppModelConfigBrokenError: logger.exception("App model config broken.") raise AppUnavailableError() @@ -180,8 +188,7 @@ class ChatMessageTextApi(Resource): class TextModesApi(Resource): @console_ns.doc("get_text_to_speech_voices") @console_ns.doc(description="Get available TTS voices for a specific language") - @console_ns.doc(params={"app_id": "App ID"}) - @console_ns.doc(params=query_params_from_model(TextToSpeechVoiceQuery)) + @console_ns.doc(params={"app_id": "App ID", **query_params_from_model(TextToSpeechVoiceQuery)}) @console_ns.response( 200, "TTS voices retrieved successfully", @@ -202,7 +209,7 @@ class TextModesApi(Resource): language=args.language, ) - return response + return dump_response(TextToSpeechVoiceListResponse, response) except services.errors.audio.ProviderNotSupportTextToSpeechLanageServiceError: raise AppUnavailableError("Text to audio voices language parameter loss.") except NoAudioUploadedServiceError: diff --git a/api/controllers/console/app/completion.py b/api/controllers/console/app/completion.py index 545fad34cde..73201af44c6 100644 --- a/api/controllers/console/app/completion.py +++ b/api/controllers/console/app/completion.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field, field_validator from werkzeug.exceptions import BadRequest, InternalServerError, NotFound import services -from controllers.common.fields import GeneratedAppResponse, SimpleResultResponse +from controllers.common.fields import SimpleResultResponse from controllers.common.schema import register_response_schema_models, register_schema_models from controllers.console import console_ns from controllers.console.agent.app_helpers import resolve_agent_app_model @@ -103,7 +103,7 @@ class ChatMessagePayload(BaseMessagePayload): register_schema_models(console_ns, CompletionMessagePayload, ChatMessagePayload) -register_response_schema_models(console_ns, GeneratedAppResponse, SimpleResultResponse) +register_response_schema_models(console_ns, SimpleResultResponse) # define completion message api for user @@ -113,7 +113,7 @@ class CompletionMessageApi(Resource): @console_ns.doc(description="Generate completion message for debugging") @console_ns.doc(params={"app_id": "Application ID"}) @console_ns.expect(console_ns.models[CompletionMessagePayload.__name__]) - @console_ns.response(200, "Completion generated successfully", console_ns.models[GeneratedAppResponse.__name__]) + @console_ns.response(200, "Completion generated successfully") @console_ns.response(400, "Invalid request parameters") @console_ns.response(404, "App not found") @setup_required @@ -134,6 +134,7 @@ class CompletionMessageApi(Resource): app_model=app_model, user=current_user, args=args, invoke_from=InvokeFrom.DEBUGGER, streaming=streaming ) + # response-contract:ignore compact_generate_response return helper.compact_generate_response(response) except services.errors.conversation.ConversationNotExistsError: raise NotFound("Conversation Not Exists.") @@ -177,7 +178,7 @@ class CompletionMessageStopApi(Resource): app_mode=AppMode.value_of(app_model.mode), ) - return {"result": "success"}, 200 + return SimpleResultResponse(result="success").model_dump(mode="json"), 200 @console_ns.route("/apps//chat-messages") @@ -186,7 +187,7 @@ class ChatMessageApi(Resource): @console_ns.doc(description="Generate chat message for debugging") @console_ns.doc(params={"app_id": "Application ID"}) @console_ns.expect(console_ns.models[ChatMessagePayload.__name__]) - @console_ns.response(200, "Chat message generated successfully", console_ns.models[GeneratedAppResponse.__name__]) + @console_ns.response(200, "Chat message generated successfully") @console_ns.response(400, "Invalid request parameters") @console_ns.response(404, "App or conversation not found") @setup_required @@ -207,7 +208,7 @@ class AgentChatMessageApi(Resource): @console_ns.doc(description="Generate an Agent App chat message for debugging") @console_ns.doc(params={"agent_id": "Agent ID"}) @console_ns.expect(console_ns.models[ChatMessagePayload.__name__]) - @console_ns.response(200, "Chat message generated successfully", console_ns.models[GeneratedAppResponse.__name__]) + @console_ns.response(200, "Chat message generated successfully") @console_ns.response(400, "Invalid request parameters") @console_ns.response(404, "Agent or conversation not found") @setup_required @@ -315,6 +316,7 @@ def _create_chat_message( app_model=app_model, user=current_user, args=args, invoke_from=InvokeFrom.DEBUGGER, streaming=streaming ) + # response-contract:ignore compact_generate_response return helper.compact_generate_response(response) except services.errors.conversation.ConversationNotExistsError: raise NotFound("Conversation Not Exists.") @@ -348,4 +350,4 @@ def _stop_chat_message(*, current_user_id: str, app_model: App, task_id: str): app_mode=AppMode.value_of(app_model.mode), ) - return {"result": "success"}, 200 + return SimpleResultResponse(result="success").model_dump(mode="json"), 200 diff --git a/api/controllers/console/app/conversation.py b/api/controllers/console/app/conversation.py index ec34c26fedd..6387ea441e5 100644 --- a/api/controllers/console/app/conversation.py +++ b/api/controllers/console/app/conversation.py @@ -9,7 +9,7 @@ from sqlalchemy import func, or_ from sqlalchemy.orm import selectinload from werkzeug.exceptions import NotFound -from controllers.common.schema import query_params_from_model, register_schema_models +from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models from controllers.console import console_ns from controllers.console.app.wraps import get_app_model from controllers.console.wraps import ( @@ -39,6 +39,7 @@ from fields.conversation_fields import ( ConversationWithSummaryPagination as ConversationWithSummaryPaginationResponse, ) from libs.datetime_utils import naive_utc_now, parse_time_range +from libs.helper import dump_response from libs.login import login_required from models import Conversation, EndUser, Message, MessageAnnotation from models.account import Account @@ -79,13 +80,14 @@ register_schema_models( console_ns, CompletionConversationQuery, ChatConversationQuery, +) +register_response_schema_models( + console_ns, ConversationResponse, ConversationPaginationResponse, ConversationMessageDetailResponse, ConversationWithSummaryPaginationResponse, ConversationDetailResponse, - CompletionConversationQuery, - ChatConversationQuery, ) @@ -93,8 +95,7 @@ register_schema_models( class CompletionConversationApi(Resource): @console_ns.doc("list_completion_conversations") @console_ns.doc(description="Get completion conversations with pagination and filtering") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(CompletionConversationQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(CompletionConversationQuery)}) @console_ns.response(200, "Success", console_ns.models[ConversationPaginationResponse.__name__]) @console_ns.response(403, "Insufficient permissions") @setup_required @@ -157,9 +158,7 @@ class CompletionConversationApi(Resource): conversations = db.paginate(query, page=args.page, per_page=args.limit, error_out=False) - return ConversationPaginationResponse.model_validate(conversations, from_attributes=True).model_dump( - mode="json" - ) + return dump_response(ConversationPaginationResponse, conversations) @console_ns.route("/apps//completion-conversations/") @@ -179,9 +178,9 @@ class CompletionConversationDetailApi(Resource): @get_app_model(mode=AppMode.COMPLETION) def get(self, current_user: Account, app_model: App, conversation_id: UUID): conversation_id_str = str(conversation_id) - return ConversationMessageDetailResponse.model_validate( - _get_conversation(current_user, app_model, conversation_id_str), from_attributes=True - ).model_dump(mode="json") + return dump_response( + ConversationMessageDetailResponse, _get_conversation(current_user, app_model, conversation_id_str) + ) @console_ns.doc("delete_completion_conversation") @console_ns.doc(description="Delete a completion conversation") @@ -211,8 +210,7 @@ class CompletionConversationDetailApi(Resource): class ChatConversationApi(Resource): @console_ns.doc("list_chat_conversations") @console_ns.doc(description="Get chat conversations with pagination, filtering and summary") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(ChatConversationQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(ChatConversationQuery)}) @console_ns.response(200, "Success", console_ns.models[ConversationWithSummaryPaginationResponse.__name__]) @console_ns.response(403, "Insufficient permissions") @setup_required @@ -314,9 +312,7 @@ class ChatConversationApi(Resource): conversations = db.paginate(query, page=args.page, per_page=args.limit, error_out=False) - return ConversationWithSummaryPaginationResponse.model_validate(conversations, from_attributes=True).model_dump( - mode="json" - ) + return dump_response(ConversationWithSummaryPaginationResponse, conversations) @console_ns.route("/apps//chat-conversations/") @@ -336,9 +332,9 @@ class ChatConversationDetailApi(Resource): @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT]) def get(self, current_user: Account, app_model: App, conversation_id: UUID): conversation_id_str = str(conversation_id) - return ConversationDetailResponse.model_validate( - _get_conversation(current_user, app_model, conversation_id_str), from_attributes=True - ).model_dump(mode="json") + return dump_response( + ConversationDetailResponse, _get_conversation(current_user, app_model, conversation_id_str) + ) @console_ns.doc("delete_chat_conversation") @console_ns.doc(description="Delete a chat conversation") diff --git a/api/controllers/console/app/conversation_variables.py b/api/controllers/console/app/conversation_variables.py index 3069dd3011c..aa8090f0440 100644 --- a/api/controllers/console/app/conversation_variables.py +++ b/api/controllers/console/app/conversation_variables.py @@ -22,7 +22,7 @@ from controllers.console.wraps import ( from extensions.ext_database import db from fields._value_type_serializer import serialize_value_type from fields.base import ResponseModel -from libs.helper import to_timestamp +from libs.helper import dump_response, to_timestamp from libs.login import login_required from models import ConversationVariable from models.model import App, AppMode @@ -119,7 +119,8 @@ class ConversationVariablesApi(Resource): with sessionmaker(db.engine, expire_on_commit=False).begin() as session: rows = session.scalars(stmt).all() - response = PaginatedConversationVariableResponse.model_validate( + return dump_response( + PaginatedConversationVariableResponse, { "page": page, "limit": page_size, @@ -135,6 +136,5 @@ class ConversationVariablesApi(Resource): ) for row in rows ], - } + }, ) - return response.model_dump(mode="json") diff --git a/api/controllers/console/app/message.py b/api/controllers/console/app/message.py index 195a41f2888..b51506c6e7d 100644 --- a/api/controllers/console/app/message.py +++ b/api/controllers/console/app/message.py @@ -1,5 +1,4 @@ import logging -from datetime import datetime from typing import Literal from uuid import UUID @@ -38,16 +37,10 @@ from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotIni from extensions.ext_database import db from fields.base import ResponseModel from fields.conversation_fields import ( - AgentThought, - ConversationAnnotation, - ConversationAnnotationHitHistory, - Feedback, - JSONValue, - MessageFile, - format_files_contained, + MessageDetail as BaseMessageDetailResponse, ) from graphon.model_runtime.errors.invoke import InvokeError -from libs.helper import to_timestamp, uuid_value +from libs.helper import dump_response, uuid_value from libs.infinite_scroll_pagination import InfiniteScrollPagination from libs.login import login_required from models.account import Account @@ -111,49 +104,16 @@ class FeedbackExportQuery(BaseModel): raise ValueError("has_comment must be a boolean value") -class AnnotationCountResponse(BaseModel): +class AnnotationCountResponse(ResponseModel): count: int = Field(description="Number of annotations") -class SuggestedQuestionsResponse(BaseModel): +class SuggestedQuestionsResponse(ResponseModel): data: list[str] = Field(description="Suggested question") -class MessageDetailResponse(ResponseModel): - id: str - conversation_id: str - inputs: dict[str, JSONValue] - query: str - message: JSONValue | None = None - message_tokens: int | None = None - answer: str = Field(validation_alias="re_sign_file_url_answer") - answer_tokens: int | None = None - provider_response_latency: float | None = None - from_source: str - from_end_user_id: str | None = None - from_account_id: str | None = None - feedbacks: list[Feedback] = Field(default_factory=list) - workflow_run_id: str | None = None - annotation: ConversationAnnotation | None = None - annotation_hit_history: ConversationAnnotationHitHistory | None = None - created_at: int | None = None - agent_thoughts: list[AgentThought] = Field(default_factory=list) - message_files: list[MessageFile] = Field(default_factory=list) +class MessageDetailResponse(BaseMessageDetailResponse): extra_contents: list[ExecutionExtraContentDomainModel] = Field(default_factory=list) - metadata: JSONValue | None = Field(default=None, validation_alias="message_metadata_dict") - status: str - error: str | None = None - parent_message_id: str | None = None - - @field_validator("inputs", mode="before") - @classmethod - def _normalize_inputs(cls, value: JSONValue) -> JSONValue: - return format_files_contained(value) - - @field_validator("created_at", mode="before") - @classmethod - def _normalize_created_at(cls, value: datetime | int | None) -> int | None: - return to_timestamp(value) class MessageInfiniteScrollPaginationResponse(ResponseModel): @@ -167,20 +127,23 @@ register_schema_models( ChatMessagesQuery, MessageFeedbackPayload, FeedbackExportQuery, +) +register_response_schema_models( + console_ns, AnnotationCountResponse, SuggestedQuestionsResponse, MessageDetailResponse, MessageInfiniteScrollPaginationResponse, + SimpleResultResponse, + TextFileResponse, ) -register_response_schema_models(console_ns, SimpleResultResponse, TextFileResponse) @console_ns.route("/apps//chat-messages") class ChatMessageListApi(Resource): @console_ns.doc("list_chat_messages") @console_ns.doc(description="Get chat messages for a conversation with pagination") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(ChatMessagesQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(ChatMessagesQuery)}) @console_ns.response(200, "Success", console_ns.models[MessageInfiniteScrollPaginationResponse.__name__]) @console_ns.response(404, "Conversation not found") @login_required @@ -270,7 +233,7 @@ class MessageAnnotationCountApi(Resource): select(func.count(MessageAnnotation.id)).where(MessageAnnotation.app_id == app_model.id) ) - return {"count": count} + return AnnotationCountResponse(count=count or 0).model_dump(mode="json") @console_ns.route("/apps//chat-messages//suggested-questions") @@ -319,13 +282,12 @@ class AgentMessageSuggestedQuestionApi(Resource): class MessageFeedbackExportApi(Resource): @console_ns.doc("export_feedbacks") @console_ns.doc(description="Export user feedback data for Google Sheets") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(FeedbackExportQuery)) @console_ns.response( 200, "Feedback data exported successfully", console_ns.models[TextFileResponse.__name__], ) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(FeedbackExportQuery)}) @console_ns.response(400, "Invalid parameters") @console_ns.response(500, "Internal server error") @setup_required @@ -350,7 +312,6 @@ class MessageFeedbackExportApi(Resource): end_date=args.end_date, format_type=args.format, ) - return export_data except ValueError as e: @@ -461,16 +422,16 @@ def _list_chat_messages(*, app_model: App, current_user: Account | None = None): history_messages = list(reversed(history_messages)) attach_message_extra_contents(history_messages) - return MessageInfiniteScrollPaginationResponse.model_validate( + return dump_response( + MessageInfiniteScrollPaginationResponse, InfiniteScrollPagination(data=history_messages, limit=args.limit, has_more=has_more), - from_attributes=True, - ).model_dump(mode="json") + ) def _update_message_feedback(*, current_user: Account, app_model: App): args = MessageFeedbackPayload.model_validate(console_ns.payload) - message_id = str(args.message_id) + message_id = args.message_id message = db.session.scalar( select(Message).where(Message.id == message_id, Message.app_id == app_model.id).limit(1) @@ -505,7 +466,7 @@ def _update_message_feedback(*, current_user: Account, app_model: App): db.session.commit() - return {"result": "success"} + return SimpleResultResponse(result="success").model_dump(mode="json") def _get_message_suggested_questions(*, current_user: Account, app_model: App, message_id: UUID): @@ -533,7 +494,7 @@ def _get_message_suggested_questions(*, current_user: Account, app_model: App, m logger.exception("internal server error.") raise InternalServerError() - return {"data": questions} + return dump_response(SuggestedQuestionsResponse, {"data": questions}) def _get_message_detail(*, app_model: App, message_id: UUID): @@ -547,4 +508,4 @@ def _get_message_detail(*, app_model: App, message_id: UUID): raise NotFound("Message Not Exists.") attach_message_extra_contents([message]) - return MessageDetailResponse.model_validate(message, from_attributes=True).model_dump(mode="json") + return dump_response(MessageDetailResponse, message) diff --git a/api/controllers/console/app/statistic.py b/api/controllers/console/app/statistic.py index fbb6d3e987f..c7c3c9e64b6 100644 --- a/api/controllers/console/app/statistic.py +++ b/api/controllers/console/app/statistic.py @@ -1,7 +1,7 @@ from decimal import Decimal import sqlalchemy as sa -from flask import abort, jsonify, request +from flask import abort, request from flask_restx import Resource from pydantic import BaseModel, Field, field_validator @@ -20,7 +20,7 @@ from core.app.entities.app_invoke_entities import InvokeFrom from extensions.ext_database import db from fields.base import ResponseModel from libs.datetime_utils import parse_time_range -from libs.helper import convert_datetime_to_date +from libs.helper import convert_datetime_to_date, dump_response from libs.login import login_required from models import AppMode from models.account import Account @@ -44,8 +44,15 @@ class DailyMessageStatisticItem(ResponseModel): message_count: int -class DailyMessageStatisticResponse(ResponseModel): - data: list[DailyMessageStatisticItem] +register_schema_models(console_ns, StatisticTimeRangeQuery) + + +class StatisticDataResponse[T](ResponseModel): + data: list[T] + + +class DailyMessageStatisticResponse(StatisticDataResponse[DailyMessageStatisticItem]): + pass class DailyConversationStatisticItem(ResponseModel): @@ -53,8 +60,8 @@ class DailyConversationStatisticItem(ResponseModel): conversation_count: int -class DailyConversationStatisticResponse(ResponseModel): - data: list[DailyConversationStatisticItem] +class DailyConversationStatisticResponse(StatisticDataResponse[DailyConversationStatisticItem]): + pass class DailyTerminalStatisticItem(ResponseModel): @@ -62,19 +69,19 @@ class DailyTerminalStatisticItem(ResponseModel): terminal_count: int -class DailyTerminalStatisticResponse(ResponseModel): - data: list[DailyTerminalStatisticItem] +class DailyTerminalStatisticResponse(StatisticDataResponse[DailyTerminalStatisticItem]): + pass class DailyTokenCostStatisticItem(ResponseModel): date: str - token_count: int - total_price: str | float - currency: str + token_count: int | None = None + total_price: Decimal | None = None + currency: str | None = None -class DailyTokenCostStatisticResponse(ResponseModel): - data: list[DailyTokenCostStatisticItem] +class DailyTokenCostStatisticResponse(StatisticDataResponse[DailyTokenCostStatisticItem]): + pass class AverageSessionInteractionStatisticItem(ResponseModel): @@ -82,8 +89,8 @@ class AverageSessionInteractionStatisticItem(ResponseModel): interactions: float -class AverageSessionInteractionStatisticResponse(ResponseModel): - data: list[AverageSessionInteractionStatisticItem] +class AverageSessionInteractionStatisticResponse(StatisticDataResponse[AverageSessionInteractionStatisticItem]): + pass class UserSatisfactionRateStatisticItem(ResponseModel): @@ -91,8 +98,8 @@ class UserSatisfactionRateStatisticItem(ResponseModel): rate: float -class UserSatisfactionRateStatisticResponse(ResponseModel): - data: list[UserSatisfactionRateStatisticItem] +class UserSatisfactionRateStatisticResponse(StatisticDataResponse[UserSatisfactionRateStatisticItem]): + pass class AverageResponseTimeStatisticItem(ResponseModel): @@ -100,8 +107,8 @@ class AverageResponseTimeStatisticItem(ResponseModel): latency: float -class AverageResponseTimeStatisticResponse(ResponseModel): - data: list[AverageResponseTimeStatisticItem] +class AverageResponseTimeStatisticResponse(StatisticDataResponse[AverageResponseTimeStatisticItem]): + pass class TokensPerSecondStatisticItem(ResponseModel): @@ -109,20 +116,27 @@ class TokensPerSecondStatisticItem(ResponseModel): tps: float -class TokensPerSecondStatisticResponse(ResponseModel): - data: list[TokensPerSecondStatisticItem] +class TokensPerSecondStatisticResponse(StatisticDataResponse[TokensPerSecondStatisticItem]): + pass -register_schema_models(console_ns, StatisticTimeRangeQuery) register_response_schema_models( console_ns, + DailyMessageStatisticItem, DailyMessageStatisticResponse, + DailyConversationStatisticItem, DailyConversationStatisticResponse, + DailyTerminalStatisticItem, DailyTerminalStatisticResponse, + DailyTokenCostStatisticItem, DailyTokenCostStatisticResponse, + AverageSessionInteractionStatisticItem, AverageSessionInteractionStatisticResponse, + UserSatisfactionRateStatisticItem, UserSatisfactionRateStatisticResponse, + AverageResponseTimeStatisticItem, AverageResponseTimeStatisticResponse, + TokensPerSecondStatisticItem, TokensPerSecondStatisticResponse, ) @@ -131,8 +145,7 @@ register_response_schema_models( class DailyMessageStatistic(Resource): @console_ns.doc("get_daily_message_statistics") @console_ns.doc(description="Get daily message statistics for an application") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)}) @console_ns.response( 200, "Daily message statistics retrieved successfully", @@ -185,15 +198,14 @@ WHERE for i in rs: response_data.append({"date": str(i.date), "message_count": i.message_count}) - return jsonify({"data": response_data}) + return dump_response(DailyMessageStatisticResponse, {"data": response_data}) @console_ns.route("/apps//statistics/daily-conversations") class DailyConversationStatistic(Resource): @console_ns.doc("get_daily_conversation_statistics") @console_ns.doc(description="Get daily conversation statistics for an application") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)}) @console_ns.response( 200, "Daily conversation statistics retrieved successfully", @@ -245,15 +257,14 @@ WHERE for i in rs: response_data.append({"date": str(i.date), "conversation_count": i.conversation_count}) - return jsonify({"data": response_data}) + return dump_response(DailyConversationStatisticResponse, {"data": response_data}) @console_ns.route("/apps//statistics/daily-end-users") class DailyTerminalsStatistic(Resource): @console_ns.doc("get_daily_terminals_statistics") @console_ns.doc(description="Get daily terminal/end-user statistics for an application") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)}) @console_ns.response( 200, "Daily terminal statistics retrieved successfully", @@ -306,15 +317,14 @@ WHERE for i in rs: response_data.append({"date": str(i.date), "terminal_count": i.terminal_count}) - return jsonify({"data": response_data}) + return dump_response(DailyTerminalStatisticResponse, {"data": response_data}) @console_ns.route("/apps//statistics/token-costs") class DailyTokenCostStatistic(Resource): @console_ns.doc("get_daily_token_cost_statistics") @console_ns.doc(description="Get daily token cost statistics for an application") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)}) @console_ns.response( 200, "Daily token cost statistics retrieved successfully", @@ -370,15 +380,14 @@ WHERE {"date": str(i.date), "token_count": i.token_count, "total_price": i.total_price, "currency": "USD"} ) - return jsonify({"data": response_data}) + return dump_response(DailyTokenCostStatisticResponse, {"data": response_data}) @console_ns.route("/apps//statistics/average-session-interactions") class AverageSessionInteractionStatistic(Resource): @console_ns.doc("get_average_session_interaction_statistics") @console_ns.doc(description="Get average session interaction statistics for an application") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)}) @console_ns.response( 200, "Average session interaction statistics retrieved successfully", @@ -450,15 +459,14 @@ ORDER BY {"date": str(i.date), "interactions": float(i.interactions.quantize(Decimal("0.01")))} ) - return jsonify({"data": response_data}) + return dump_response(AverageSessionInteractionStatisticResponse, {"data": response_data}) @console_ns.route("/apps//statistics/user-satisfaction-rate") class UserSatisfactionRateStatistic(Resource): @console_ns.doc("get_user_satisfaction_rate_statistics") @console_ns.doc(description="Get user satisfaction rate statistics for an application") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)}) @console_ns.response( 200, "User satisfaction rate statistics retrieved successfully", @@ -520,15 +528,14 @@ WHERE } ) - return jsonify({"data": response_data}) + return dump_response(UserSatisfactionRateStatisticResponse, {"data": response_data}) @console_ns.route("/apps//statistics/average-response-time") class AverageResponseTimeStatistic(Resource): @console_ns.doc("get_average_response_time_statistics") @console_ns.doc(description="Get average response time statistics for an application") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)}) @console_ns.response( 200, "Average response time statistics retrieved successfully", @@ -581,15 +588,14 @@ WHERE for i in rs: response_data.append({"date": str(i.date), "latency": round(i.latency * 1000, 4)}) - return jsonify({"data": response_data}) + return dump_response(AverageResponseTimeStatisticResponse, {"data": response_data}) @console_ns.route("/apps//statistics/tokens-per-second") class TokensPerSecondStatistic(Resource): @console_ns.doc("get_tokens_per_second_statistics") @console_ns.doc(description="Get tokens per second statistics for an application") - @console_ns.doc(params={"app_id": "Application ID"}) - @console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery)) + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)}) @console_ns.response( 200, "Tokens per second statistics retrieved successfully", @@ -645,4 +651,4 @@ WHERE for i in rs: response_data.append({"date": str(i.date), "tps": round(i.tokens_per_second, 4)}) - return jsonify({"data": response_data}) + return dump_response(TokensPerSecondStatisticResponse, {"data": response_data}) diff --git a/api/fields/annotation_fields.py b/api/fields/annotation_fields.py index 4546a051cce..86a13a32bd2 100644 --- a/api/fields/annotation_fields.py +++ b/api/fields/annotation_fields.py @@ -1,6 +1,7 @@ from __future__ import annotations from datetime import datetime +from typing import Literal from pydantic import Field, field_validator @@ -29,6 +30,15 @@ class AnnotationList(ResponseModel): page: int +class AnnotationJobStatusResponse(ResponseModel): + job_id: str + job_status: Literal["waiting", "processing", "completed", "error"] | str + + +class AnnotationJobStatusDetailResponse(AnnotationJobStatusResponse): + error_msg: str = "" + + class AnnotationExportList(ResponseModel): data: list[Annotation] diff --git a/api/fields/conversation_variable_fields.py b/api/fields/conversation_variable_fields.py index 4d5de84fd98..6618475f2f3 100644 --- a/api/fields/conversation_variable_fields.py +++ b/api/fields/conversation_variable_fields.py @@ -3,25 +3,14 @@ from __future__ import annotations from datetime import datetime from typing import Any -from flask_restx import fields from pydantic import field_validator from fields.base import ResponseModel from graphon.variables.types import SegmentType -from libs.helper import TimestampField, to_timestamp +from libs.helper import to_timestamp from ._value_type_serializer import serialize_value_type -conversation_variable_fields = { - "id": fields.String, - "name": fields.String, - "value_type": fields.String(attribute=serialize_value_type), - "value": fields.String, - "description": fields.String, - "created_at": TimestampField, - "updated_at": TimestampField, -} - class ConversationVariableResponse(ResponseModel): id: str diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md index b3a0b8a6a71..2c939d68c9e 100644 --- a/api/openapi/markdown/console-openapi.md +++ b/api/openapi/markdown/console-openapi.md @@ -1782,7 +1782,7 @@ Get status of annotation reply action job | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusResponse](#annotationjobstatusresponse)
| +| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusDetailResponse](#annotationjobstatusdetailresponse)
| | 403 | Insufficient permissions | | ### [GET] /apps/{app_id}/annotation-setting @@ -1891,7 +1891,7 @@ Batch import annotations from CSV file with rate limiting and security checks | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Batch import started successfully | **application/json**: [AnnotationJobStatusResponse](#annotationjobstatusresponse)
| +| 200 | Batch import started successfully | **application/json**: [AnnotationBatchImportResponse](#annotationbatchimportresponse)
| | 400 | No file uploaded or too many files | | | 403 | Insufficient permissions | | | 413 | File too large | | @@ -1911,7 +1911,7 @@ Get status of batch import job | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusResponse](#annotationjobstatusresponse)
| +| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusDetailResponse](#annotationjobstatusdetailresponse)
| | 403 | Insufficient permissions | | ### [GET] /apps/{app_id}/annotations/count @@ -2227,11 +2227,11 @@ Generate completion message for debugging #### Responses -| Code | Description | Schema | -| ---- | ----------- | ------ | -| 200 | Completion generated successfully | **application/json**: [GeneratedAppResponse](#generatedappresponse)
| -| 400 | Invalid request parameters | | -| 404 | App not found | | +| Code | Description | +| ---- | ----------- | +| 200 | Completion generated successfully | +| 400 | Invalid request parameters | +| 404 | App not found | ### [POST] /apps/{app_id}/completion-messages/{task_id}/stop Stop a running completion message generation @@ -2789,10 +2789,10 @@ Convert text to speech for chat messages #### Responses -| Code | Description | Schema | -| ---- | ----------- | ------ | -| 200 | Text to speech conversion successful | **application/json**: [AudioBinaryResponse](#audiobinaryresponse)
| -| 400 | Bad request - Invalid parameters | | +| Code | Description | +| ---- | ----------- | +| 200 | Text to speech conversion successful | +| 400 | Bad request - Invalid parameters | ### [GET] /apps/{app_id}/text-to-audio/voices Get available TTS voices for a specific language @@ -13429,7 +13429,6 @@ Soft lifecycle state for Agent records. | created_at | integer | | No | | files | [ string ] | | Yes | | id | string | | Yes | -| message_chain_id | string | | No | | message_id | string | | Yes | | observation | string | | No | | position | integer | | Yes | @@ -13501,19 +13500,21 @@ Soft lifecycle state for Agent records. | id | string | | Yes | | question | string | | No | +#### AnnotationBatchImportResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| error_msg | string | | No | +| job_id | string | | No | +| job_status | string | | No | +| record_count | integer | | No | + #### AnnotationCountResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | count | integer | Number of annotations | Yes | -#### AnnotationEmbeddingModelResponse - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| embedding_model_name | string | | No | -| embedding_provider_name | string | | No | - #### AnnotationExportList | Name | Type | Description | Required | @@ -13555,14 +13556,20 @@ Soft lifecycle state for Agent records. | limit | integer,
**Default:** 20 | Page size | No | | page | integer,
**Default:** 1 | Page number | No | -#### AnnotationJobStatusResponse +#### AnnotationJobStatusDetailResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | error_msg | string | | No | -| job_id | string | | No | -| job_status | string | | No | -| record_count | integer | | No | +| job_id | string | | Yes | +| job_status | string
string | | Yes | + +#### AnnotationJobStatusResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| job_id | string | | Yes | +| job_status | string
string | | Yes | #### AnnotationList @@ -13596,11 +13603,18 @@ Soft lifecycle state for Agent records. | ---- | ---- | ----------- | -------- | | action | string,
**Available values:** "disable", "enable" | *Enum:* `"disable"`, `"enable"` | Yes | +#### AnnotationSettingEmbeddingModelResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| embedding_model_name | string | | No | +| embedding_provider_name | string | | No | + #### AnnotationSettingResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| embedding_model | [AnnotationEmbeddingModelResponse](#annotationembeddingmodelresponse) | | No | +| embedding_model | [AnnotationSettingEmbeddingModelResponse](#annotationsettingembeddingmodelresponse) | | No | | enabled | boolean | | Yes | | id | string | | No | | score_threshold | number | | No | @@ -14512,13 +14526,13 @@ Enum class for configurate method of provider model. | admin_feedback_stats | [FeedbackStat](#feedbackstat) | | No | | annotation | [ConversationAnnotation](#conversationannotation) | | No | | created_at | integer | | No | -| first_message | [SimpleMessageDetail](#simplemessagedetail) | | No | | from_account_id | string | | No | | from_account_name | string | | No | | from_end_user_id | string | | No | | from_end_user_session_id | string | | No | | from_source | string | | Yes | | id | string | | Yes | +| message | [SimpleMessageDetail](#simplemessagedetail) | | No | | model_config | [SimpleModelConfig](#simplemodelconfig) | | No | | read_at | integer | | No | | status | string | | Yes | @@ -14540,8 +14554,8 @@ Enum class for configurate method of provider model. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | annotation_create_account | [SimpleAccount](#simpleaccount) | | No | +| annotation_id | string | | Yes | | created_at | integer | | No | -| id | string | | Yes | #### ConversationDetail @@ -14582,11 +14596,11 @@ Enum class for configurate method of provider model. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | created_at | integer | | No | -| first_message | [MessageDetail](#messagedetail) | | No | | from_account_id | string | | No | | from_end_user_id | string | | No | | from_source | string | | Yes | | id | string | | Yes | +| message | [MessageDetail](#messagedetail) | | No | | model_config | [ModelConfig](#modelconfig) | | No | | status | string | | Yes | @@ -14594,10 +14608,10 @@ Enum class for configurate method of provider model. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| has_next | boolean | | Yes | -| items | [ [Conversation](#conversation) ] | | Yes | +| data | [ [Conversation](#conversation) ] | | Yes | +| has_more | boolean | | Yes | +| limit | integer | | Yes | | page | integer | | Yes | -| per_page | integer | | Yes | | total | integer | | Yes | #### ConversationRenamePayload @@ -14650,7 +14664,7 @@ Enum class for configurate method of provider model. | read_at | integer | | No | | status | string | | Yes | | status_count | [StatusCount](#statuscount) | | No | -| summary_or_query | string | | Yes | +| summary | string | | Yes | | updated_at | integer | | No | | user_feedback_stats | [FeedbackStat](#feedbackstat) | | No | @@ -14658,10 +14672,10 @@ Enum class for configurate method of provider model. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| has_next | boolean | | Yes | -| items | [ [ConversationWithSummary](#conversationwithsummary) ] | | Yes | +| data | [ [ConversationWithSummary](#conversationwithsummary) ] | | Yes | +| has_more | boolean | | Yes | +| limit | integer | | Yes | | page | integer | | Yes | -| per_page | integer | | Yes | | total | integer | | Yes | #### ConvertToWorkflowPayload @@ -14834,10 +14848,10 @@ Model class for provider custom model configuration. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| currency | string | | Yes | +| currency | string | | No | | date | string | | Yes | -| token_count | integer | | Yes | -| total_price | string
number | | Yes | +| token_count | integer | | No | +| total_price | string | | No | #### DailyTokenCostStatisticResponse @@ -17051,6 +17065,7 @@ Enum class for large language model mode. | agent_thoughts | [ [AgentThought](#agentthought) ] | | Yes | | annotation | [ConversationAnnotation](#conversationannotation) | | No | | annotation_hit_history | [ConversationAnnotationHitHistory](#conversationannotationhithistory) | | No | +| answer | string | | Yes | | answer_tokens | integer | | Yes | | conversation_id | string | | Yes | | created_at | integer | | No | @@ -17063,12 +17078,11 @@ Enum class for large language model mode. | inputs | object | | Yes | | message | [JSONValue](#jsonvalue) | | Yes | | message_files | [ [MessageFile](#messagefile) ] | | Yes | -| message_metadata_dict | [JSONValue](#jsonvalue) | | Yes | | message_tokens | integer | | Yes | +| metadata | [JSONValue](#jsonvalue) | | Yes | | parent_message_id | string | | No | | provider_response_latency | number | | Yes | | query | string | | Yes | -| re_sign_file_url_answer | string | | Yes | | status | string | | Yes | | workflow_run_id | string | | No | @@ -17076,28 +17090,28 @@ Enum class for large language model mode. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| agent_thoughts | [ [AgentThought](#agentthought) ] | | No | +| agent_thoughts | [ [AgentThought](#agentthought) ] | | Yes | | annotation | [ConversationAnnotation](#conversationannotation) | | No | | annotation_hit_history | [ConversationAnnotationHitHistory](#conversationannotationhithistory) | | No | -| answer_tokens | integer | | No | +| answer | string | | Yes | +| answer_tokens | integer | | Yes | | conversation_id | string | | Yes | | created_at | integer | | No | | error | string | | No | | extra_contents | [ [HumanInputContent](#humaninputcontent) ] | | No | -| feedbacks | [ [Feedback](#feedback) ] | | No | +| feedbacks | [ [Feedback](#feedback) ] | | Yes | | from_account_id | string | | No | | from_end_user_id | string | | No | | from_source | string | | Yes | | id | string | | Yes | | inputs | object | | Yes | -| message | [JSONValue](#jsonvalue) | | No | -| message_files | [ [MessageFile](#messagefile) ] | | No | -| message_metadata_dict | [JSONValue](#jsonvalue) | | No | -| message_tokens | integer | | No | +| message | [JSONValue](#jsonvalue) | | Yes | +| message_files | [ [MessageFile](#messagefile) ] | | Yes | +| message_tokens | integer | | Yes | +| metadata | [JSONValue](#jsonvalue) | | Yes | | parent_message_id | string | | No | -| provider_response_latency | number | | No | +| provider_response_latency | number | | Yes | | query | string | | Yes | -| re_sign_file_url_answer | string | | Yes | | status | string | | Yes | | workflow_run_id | string | | No | @@ -19203,7 +19217,7 @@ Model class for provider quota configuration. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| model_dict | [JSONValue](#jsonvalue) | | No | +| model | [JSONValue](#jsonvalue) | | No | | pre_prompt | string | | No | #### SimpleProviderEntityResponse @@ -19773,9 +19787,11 @@ Tag type #### TextToSpeechVoiceListResponse +Available voices + | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| TextToSpeechVoiceListResponse | array | | | +| TextToSpeechVoiceListResponse | array | Available voices | | #### TextToSpeechVoiceQuery @@ -19783,6 +19799,13 @@ Tag type | ---- | ---- | ----------- | -------- | | language | string | Language code | Yes | +#### TextToSpeechVoiceResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| name | string | Voice display name | Yes | +| value | string | Voice identifier | Yes | + #### TokensPerSecondStatisticItem | Name | Type | Description | Required | diff --git a/api/tests/unit_tests/controllers/console/app/test_audio.py b/api/tests/unit_tests/controllers/console/app/test_audio.py index 82b9b68247f..1411a336482 100644 --- a/api/tests/unit_tests/controllers/console/app/test_audio.py +++ b/api/tests/unit_tests/controllers/console/app/test_audio.py @@ -120,7 +120,8 @@ def test_console_text_api_error_mapping(app: Flask, monkeypatch: pytest.MonkeyPa def test_console_text_modes_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(AudioService, "transcript_tts_voices", lambda **_kwargs: ["voice-1"]) + expected_voices = [{"name": "Voice 1", "value": "voice-1"}] + monkeypatch.setattr(AudioService, "transcript_tts_voices", lambda **_kwargs: expected_voices) api = TextModesApi() handler = unwrap(api.get) @@ -129,7 +130,7 @@ def test_console_text_modes_success(app: Flask, monkeypatch: pytest.MonkeyPatch) with app.test_request_context("/console/api/apps/app/text-to-audio/voices?language=en", method="GET"): response = handler(api, app_model=app_model) - assert response == ["voice-1"] + assert response == expected_voices def test_console_text_modes_language_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: @@ -214,7 +215,8 @@ def test_text_to_audio_voices_success(app: Flask, monkeypatch: pytest.MonkeyPatc api = TextModesApi() method = unwrap(api.get) - monkeypatch.setattr(AudioService, "transcript_tts_voices", lambda **_kwargs: ["voice-1"]) + expected_voices = [{"name": "Voice 1", "value": "voice-1"}] + monkeypatch.setattr(AudioService, "transcript_tts_voices", lambda **_kwargs: expected_voices) app_model = SimpleNamespace(tenant_id="tenant-1") @@ -225,7 +227,7 @@ def test_text_to_audio_voices_success(app: Flask, monkeypatch: pytest.MonkeyPatc ): response = method(api, app_model=app_model) - assert response == ["voice-1"] + assert response == expected_voices def test_audio_to_text_with_invalid_file(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: @@ -272,7 +274,7 @@ def test_text_to_audio_voices_with_language_filter(app: Flask, monkeypatch: pyte monkeypatch.setattr( AudioService, "transcript_tts_voices", - lambda **_kwargs: [{"id": "voice-1", "name": "Voice 1"}], + lambda **_kwargs: [{"name": "Voice 1", "value": "voice-1"}], ) app_model = SimpleNamespace(tenant_id="tenant-1") diff --git a/api/tests/unit_tests/controllers/console/app/test_audio_api.py b/api/tests/unit_tests/controllers/console/app/test_audio_api.py deleted file mode 100644 index 40e6be11417..00000000000 --- a/api/tests/unit_tests/controllers/console/app/test_audio_api.py +++ /dev/null @@ -1,149 +0,0 @@ -from __future__ import annotations - -import io -from inspect import unwrap -from types import SimpleNamespace - -import pytest -from flask import Flask - -from controllers.console.app import audio as audio_module -from controllers.console.app.error import AudioTooLargeError -from services.errors.audio import AudioTooLargeServiceError - - -def test_audio_to_text_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: - api = audio_module.ChatMessageAudioApi() - method = unwrap(api.post) - - response_payload = {"text": "hello"} - monkeypatch.setattr(audio_module.AudioService, "transcript_asr", lambda **_kwargs: response_payload) - - app_model = SimpleNamespace(id="app-1") - - data = {"file": (io.BytesIO(b"x"), "sample.wav")} - with app.test_request_context( - "/console/api/apps/app-1/audio-to-text", - method="POST", - data=data, - content_type="multipart/form-data", - ): - response = method(api, app_model=app_model) - - assert response == response_payload - - -def test_audio_to_text_maps_audio_too_large(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: - api = audio_module.ChatMessageAudioApi() - method = unwrap(api.post) - - monkeypatch.setattr( - audio_module.AudioService, - "transcript_asr", - lambda **_kwargs: (_ for _ in ()).throw(AudioTooLargeServiceError("too large")), - ) - - app_model = SimpleNamespace(id="app-1") - - data = {"file": (io.BytesIO(b"x"), "sample.wav")} - with app.test_request_context( - "/console/api/apps/app-1/audio-to-text", - method="POST", - data=data, - content_type="multipart/form-data", - ): - with pytest.raises(AudioTooLargeError): - method(api, app_model=app_model) - - -def test_text_to_audio_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: - api = audio_module.ChatMessageTextApi() - method = unwrap(api.post) - - monkeypatch.setattr(audio_module.AudioService, "transcript_tts", lambda **_kwargs: {"audio": "ok"}) - - app_model = SimpleNamespace(id="app-1") - - with app.test_request_context( - "/console/api/apps/app-1/text-to-audio", - method="POST", - json={"text": "hello"}, - ): - response = method(api, app_model=app_model) - - assert response == {"audio": "ok"} - - -def test_text_to_audio_voices_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: - api = audio_module.TextModesApi() - method = unwrap(api.get) - - monkeypatch.setattr(audio_module.AudioService, "transcript_tts_voices", lambda **_kwargs: ["voice-1"]) - - app_model = SimpleNamespace(tenant_id="tenant-1") - - with app.test_request_context( - "/console/api/apps/app-1/text-to-audio/voices", - method="GET", - query_string={"language": "en-US"}, - ): - response = method(api, app_model=app_model) - - assert response == ["voice-1"] - - -def test_audio_to_text_with_invalid_file(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: - api = audio_module.ChatMessageAudioApi() - method = unwrap(api.post) - - monkeypatch.setattr(audio_module.AudioService, "transcript_asr", lambda **_kwargs: {"text": "test"}) - - app_model = SimpleNamespace(id="app-1") - - data = {"file": (io.BytesIO(b"invalid"), "sample.xyz")} - with app.test_request_context( - "/console/api/apps/app-1/audio-to-text", - method="POST", - data=data, - content_type="multipart/form-data", - ): - # Should not raise, AudioService is mocked - response = method(api, app_model=app_model) - assert response == {"text": "test"} - - -def test_text_to_audio_with_language_param(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: - api = audio_module.ChatMessageTextApi() - method = unwrap(api.post) - - monkeypatch.setattr(audio_module.AudioService, "transcript_tts", lambda **_kwargs: {"audio": "test"}) - - app_model = SimpleNamespace(id="app-1") - - with app.test_request_context( - "/console/api/apps/app-1/text-to-audio", - method="POST", - json={"text": "hello", "language": "en-US"}, - ): - response = method(api, app_model=app_model) - assert response == {"audio": "test"} - - -def test_text_to_audio_voices_with_language_filter(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: - api = audio_module.TextModesApi() - method = unwrap(api.get) - - monkeypatch.setattr( - audio_module.AudioService, - "transcript_tts_voices", - lambda **_kwargs: [{"id": "voice-1", "name": "Voice 1"}], - ) - - app_model = SimpleNamespace(tenant_id="tenant-1") - - with app.test_request_context( - "/console/api/apps/app-1/text-to-audio/voices?language=en-US", - method="GET", - ): - response = method(api, app_model=app_model) - assert isinstance(response, list) diff --git a/api/tests/unit_tests/controllers/console/app/test_message_api.py b/api/tests/unit_tests/controllers/console/app/test_message_api.py index 067edc6fd68..20187da6159 100644 --- a/api/tests/unit_tests/controllers/console/app/test_message_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_message_api.py @@ -125,13 +125,57 @@ def test_message_detail_response_normalizes_aliases_and_timestamp(app: Flask, mo "conversation_id": "550e8400-e29b-41d4-a716-446655440001", "inputs": {"foo": "bar"}, "query": "hello", - "re_sign_file_url_answer": "world", + "message": [{"text": "hello"}], + "message_tokens": 7, + "answer": "world", + "answer_tokens": 11, + "provider_response_latency": 1.25, "from_source": "user", + "from_end_user_id": None, + "from_account_id": "550e8400-e29b-41d4-a716-446655440002", + "feedbacks": [], + "workflow_run_id": None, + "annotation": None, + "annotation_hit_history": None, "status": "normal", "created_at": created_at, + "agent_thoughts": [], + "message_files": [], "message_metadata_dict": {"token_usage": 3}, + "error": None, + "parent_message_id": None, + "extra_contents": [], } ) assert response.answer == "world" + assert response.message_tokens == 7 + assert response.answer_tokens == 11 + assert response.provider_response_latency == 1.25 assert response.metadata == {"token_usage": 3} assert response.created_at == int(created_at.timestamp()) + assert response.model_dump(mode="json") == { + "id": "550e8400-e29b-41d4-a716-446655440000", + "conversation_id": "550e8400-e29b-41d4-a716-446655440001", + "inputs": {"foo": "bar"}, + "query": "hello", + "message": [{"text": "hello"}], + "message_tokens": 7, + "answer": "world", + "answer_tokens": 11, + "provider_response_latency": 1.25, + "from_source": "user", + "from_end_user_id": None, + "from_account_id": "550e8400-e29b-41d4-a716-446655440002", + "feedbacks": [], + "workflow_run_id": None, + "annotation": None, + "annotation_hit_history": None, + "created_at": int(created_at.timestamp()), + "agent_thoughts": [], + "message_files": [], + "metadata": {"token_usage": 3}, + "status": "normal", + "error": None, + "parent_message_id": None, + "extra_contents": [], + } diff --git a/api/tests/unit_tests/controllers/console/app/test_statistic_api.py b/api/tests/unit_tests/controllers/console/app/test_statistic_api.py index b0506e348fc..c51a38ad798 100644 --- a/api/tests/unit_tests/controllers/console/app/test_statistic_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_statistic_api.py @@ -3,6 +3,7 @@ from __future__ import annotations from decimal import Decimal from inspect import unwrap from types import SimpleNamespace +from typing import Any import pytest from flask import Flask @@ -39,6 +40,10 @@ def _install_common(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(statistic_module, "convert_datetime_to_date", lambda field: field) +def _json_payload(response: Any) -> dict[str, Any]: + return response if isinstance(response, dict) else response.get_json() + + def test_daily_message_statistic_returns_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: api = statistic_module.DailyMessageStatistic() method = unwrap(api.get) @@ -50,7 +55,7 @@ def test_daily_message_statistic_returns_rows(app: Flask, monkeypatch: pytest.Mo with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"): response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1")) - assert response.get_json() == {"data": [{"date": "2024-01-01", "message_count": 3}]} + assert _json_payload(response) == {"data": [{"date": "2024-01-01", "message_count": 3}]} def test_daily_conversation_statistic_returns_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: @@ -64,7 +69,7 @@ def test_daily_conversation_statistic_returns_rows(app: Flask, monkeypatch: pyte with app.test_request_context("/console/api/apps/app-1/statistics/daily-conversations", method="GET"): response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1")) - assert response.get_json() == {"data": [{"date": "2024-01-02", "conversation_count": 5}]} + assert _json_payload(response) == {"data": [{"date": "2024-01-02", "conversation_count": 5}]} def test_daily_token_cost_statistic_returns_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: @@ -78,11 +83,11 @@ def test_daily_token_cost_statistic_returns_rows(app: Flask, monkeypatch: pytest with app.test_request_context("/console/api/apps/app-1/statistics/token-costs", method="GET"): response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1")) - data = response.get_json() + data = _json_payload(response) assert len(data["data"]) == 1 assert data["data"][0]["date"] == "2024-01-03" assert data["data"][0]["token_count"] == 10 - assert data["data"][0]["total_price"] == 0.25 + assert Decimal(data["data"][0]["total_price"]) == Decimal("0.25") def test_daily_terminals_statistic_returns_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: @@ -96,7 +101,7 @@ def test_daily_terminals_statistic_returns_rows(app: Flask, monkeypatch: pytest. with app.test_request_context("/console/api/apps/app-1/statistics/daily-end-users", method="GET"): response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1")) - assert response.get_json() == {"data": [{"date": "2024-01-04", "terminal_count": 7}]} + assert _json_payload(response) == {"data": [{"date": "2024-01-04", "terminal_count": 7}]} def test_average_session_interaction_statistic_requires_chat_mode(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: @@ -139,7 +144,7 @@ def test_daily_message_statistic_multiple_rows(app: Flask, monkeypatch: pytest.M with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"): response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1")) - data = response.get_json() + data = _json_payload(response) assert len(data["data"]) == 3 @@ -153,7 +158,7 @@ def test_daily_message_statistic_empty_result(app: Flask, monkeypatch: pytest.Mo with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"): response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1")) - assert response.get_json() == {"data": []} + assert _json_payload(response) == {"data": []} def test_daily_conversation_statistic_with_time_range(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: @@ -172,7 +177,7 @@ def test_daily_conversation_statistic_with_time_range(app: Flask, monkeypatch: p with app.test_request_context("/console/api/apps/app-1/statistics/daily-conversations", method="GET"): response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1")) - assert response.get_json() == {"data": [{"date": "2024-01-02", "conversation_count": 5}]} + assert _json_payload(response) == {"data": [{"date": "2024-01-02", "conversation_count": 5}]} def test_daily_token_cost_with_multiple_currencies(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: @@ -189,5 +194,5 @@ def test_daily_token_cost_with_multiple_currencies(app: Flask, monkeypatch: pyte with app.test_request_context("/console/api/apps/app-1/statistics/token-costs", method="GET"): response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1")) - data = response.get_json() + data = _json_payload(response) assert len(data["data"]) == 2 diff --git a/packages/contracts/generated/api/console/agent/types.gen.ts b/packages/contracts/generated/api/console/agent/types.gen.ts index 43119c4f1f4..f631022795e 100644 --- a/packages/contracts/generated/api/console/agent/types.gen.ts +++ b/packages/contracts/generated/api/console/agent/types.gen.ts @@ -266,15 +266,16 @@ export type AgentLogMessageListResponse = { } export type MessageDetailResponse = { - agent_thoughts?: Array + agent_thoughts: Array annotation?: ConversationAnnotation | null annotation_hit_history?: ConversationAnnotationHitHistory | null - answer_tokens?: number | null + answer: string + answer_tokens: number conversation_id: string created_at?: number | null error?: string | null extra_contents?: Array - feedbacks?: Array + feedbacks: Array from_account_id?: string | null from_end_user_id?: string | null from_source: string @@ -282,14 +283,13 @@ export type MessageDetailResponse = { inputs: { [key: string]: JsonValue } - message?: JsonValue | null - message_files?: Array - message_metadata_dict?: JsonValue | null - message_tokens?: number | null + message: JsonValue + message_files: Array + message_tokens: number + metadata: JsonValue parent_message_id?: string | null - provider_response_latency?: number | null + provider_response_latency: number query: string - re_sign_file_url_answer: string status: string workflow_run_id?: string | null } @@ -723,7 +723,6 @@ export type AgentThought = { created_at?: number | null files: Array id: string - message_chain_id?: string | null message_id: string observation?: string | null position: number @@ -743,8 +742,8 @@ export type ConversationAnnotation = { export type ConversationAnnotationHitHistory = { annotation_create_account?: SimpleAccount | null + annotation_id: string created_at?: number | null - id: string } export type HumanInputContent = { diff --git a/packages/contracts/generated/api/console/agent/zod.gen.ts b/packages/contracts/generated/api/console/agent/zod.gen.ts index d7f5681ffc4..b40b8e4b1d3 100644 --- a/packages/contracts/generated/api/console/agent/zod.gen.ts +++ b/packages/contracts/generated/api/console/agent/zod.gen.ts @@ -570,7 +570,6 @@ export const zAgentThought = z.object({ created_at: z.int().nullish(), files: z.array(z.string()), id: z.string(), - message_chain_id: z.string().nullish(), message_id: z.string(), observation: z.string().nullish(), position: z.int(), @@ -1056,8 +1055,8 @@ export const zConversationAnnotation = z.object({ */ export const zConversationAnnotationHitHistory = z.object({ annotation_create_account: zSimpleAccount.nullish(), + annotation_id: z.string(), created_at: z.int().nullish(), - id: z.string(), }) /** @@ -2032,28 +2031,28 @@ export const zHumanInputContent = z.object({ * MessageDetailResponse */ export const zMessageDetailResponse = z.object({ - agent_thoughts: z.array(zAgentThought).optional(), + agent_thoughts: z.array(zAgentThought), annotation: zConversationAnnotation.nullish(), annotation_hit_history: zConversationAnnotationHitHistory.nullish(), - answer_tokens: z.int().nullish(), + answer: z.string(), + answer_tokens: z.int(), conversation_id: z.string(), created_at: z.int().nullish(), error: z.string().nullish(), extra_contents: z.array(zHumanInputContent).optional(), - feedbacks: z.array(zFeedback).optional(), + feedbacks: z.array(zFeedback), from_account_id: z.string().nullish(), from_end_user_id: z.string().nullish(), from_source: z.string(), id: z.string(), inputs: z.record(z.string(), zJsonValue), - message: zJsonValue.nullish(), - message_files: z.array(zMessageFile).optional(), - message_metadata_dict: zJsonValue.nullish(), - message_tokens: z.int().nullish(), + message: zJsonValue, + message_files: z.array(zMessageFile), + message_tokens: z.int(), + metadata: zJsonValue, parent_message_id: z.string().nullish(), - provider_response_latency: z.number().nullish(), + provider_response_latency: z.number(), query: z.string(), - re_sign_file_url_answer: z.string(), status: z.string(), workflow_run_id: z.string().nullish(), }) diff --git a/packages/contracts/generated/api/console/apps/orpc.gen.ts b/packages/contracts/generated/api/console/apps/orpc.gen.ts index ea72df28458..beb3e4cfcb8 100644 --- a/packages/contracts/generated/api/console/apps/orpc.gen.ts +++ b/packages/contracts/generated/api/console/apps/orpc.gen.ts @@ -302,11 +302,8 @@ import { zPostAppsByAppIdAudioToTextResponse, zPostAppsByAppIdChatMessagesByTaskIdStopPath, zPostAppsByAppIdChatMessagesByTaskIdStopResponse, - zPostAppsByAppIdCompletionMessagesBody, zPostAppsByAppIdCompletionMessagesByTaskIdStopPath, zPostAppsByAppIdCompletionMessagesByTaskIdStopResponse, - zPostAppsByAppIdCompletionMessagesPath, - zPostAppsByAppIdCompletionMessagesResponse, zPostAppsByAppIdConvertToWorkflowBody, zPostAppsByAppIdConvertToWorkflowPath, zPostAppsByAppIdConvertToWorkflowResponse, @@ -340,9 +337,6 @@ import { zPostAppsByAppIdSiteResponse, zPostAppsByAppIdStarPath, zPostAppsByAppIdStarResponse, - zPostAppsByAppIdTextToAudioBody, - zPostAppsByAppIdTextToAudioPath, - zPostAppsByAppIdTextToAudioResponse, zPostAppsByAppIdTraceBody, zPostAppsByAppIdTraceConfigBody, zPostAppsByAppIdTraceConfigPath, @@ -1649,28 +1643,7 @@ export const byTaskId2 = { stop: stop2, } -/** - * Generate completion message for debugging - */ -export const post21 = oc - .route({ - description: 'Generate completion message for debugging', - inputStructure: 'detailed', - method: 'POST', - operationId: 'postAppsByAppIdCompletionMessages', - path: '/apps/{app_id}/completion-messages', - tags: ['console'], - }) - .input( - z.object({ - body: zPostAppsByAppIdCompletionMessagesBody, - params: zPostAppsByAppIdCompletionMessagesPath, - }), - ) - .output(zPostAppsByAppIdCompletionMessagesResponse) - export const completionMessages = { - post: post21, byTaskId: byTaskId2, } @@ -1705,7 +1678,7 @@ export const conversationVariables = { * Convert expert mode of chatbot app to workflow mode * Convert Completion App to Workflow App */ -export const post22 = oc +export const post21 = oc .route({ description: 'Convert application to workflow mode\nConvert expert mode of chatbot app to workflow mode\nConvert Completion App to Workflow App', @@ -1725,7 +1698,7 @@ export const post22 = oc .output(zPostAppsByAppIdConvertToWorkflowResponse) export const convertToWorkflow = { - post: post22, + post: post21, } /** @@ -1733,7 +1706,7 @@ export const convertToWorkflow = { * * Create a copy of an existing application */ -export const post23 = oc +export const post22 = oc .route({ description: 'Create a copy of an existing application', inputStructure: 'detailed', @@ -1748,7 +1721,7 @@ export const post23 = oc .output(zPostAppsByAppIdCopyResponse) export const copy = { - post: post23, + post: post22, } /** @@ -1802,7 +1775,7 @@ export const export3 = { /** * Create or update message feedback (like/dislike) */ -export const post24 = oc +export const post23 = oc .route({ description: 'Create or update message feedback (like/dislike)', inputStructure: 'detailed', @@ -1815,14 +1788,14 @@ export const post24 = oc .output(zPostAppsByAppIdFeedbacksResponse) export const feedbacks = { - post: post24, + post: post23, export: export3, } /** * Update application icon */ -export const post25 = oc +export const post24 = oc .route({ description: 'Update application icon', inputStructure: 'detailed', @@ -1835,7 +1808,7 @@ export const post25 = oc .output(zPostAppsByAppIdIconResponse) export const icon = { - post: post25, + post: post24, } /** @@ -1866,7 +1839,7 @@ export const messages = { * * Update application model configuration */ -export const post26 = oc +export const post25 = oc .route({ description: 'Update application model configuration', inputStructure: 'detailed', @@ -1882,13 +1855,13 @@ export const post26 = oc .output(zPostAppsByAppIdModelConfigResponse) export const modelConfig = { - post: post26, + post: post25, } /** * Check if app name is available */ -export const post27 = oc +export const post26 = oc .route({ description: 'Check if app name is available', inputStructure: 'detailed', @@ -1901,13 +1874,13 @@ export const post27 = oc .output(zPostAppsByAppIdNameResponse) export const name = { - post: post27, + post: post26, } /** * Publish app to Creators Platform */ -export const post28 = oc +export const post27 = oc .route({ inputStructure: 'detailed', method: 'POST', @@ -1920,7 +1893,7 @@ export const post28 = oc .output(zPostAppsByAppIdPublishToCreatorsPlatformResponse) export const publishToCreatorsPlatform = { - post: post28, + post: post27, } /** @@ -1941,7 +1914,7 @@ export const get28 = oc /** * Create MCP server configuration for an application */ -export const post29 = oc +export const post28 = oc .route({ description: 'Create MCP server configuration for an application', inputStructure: 'detailed', @@ -1971,14 +1944,14 @@ export const put = oc export const server = { get: get28, - post: post29, + post: post28, put, } /** * Reset access token for application site */ -export const post30 = oc +export const post29 = oc .route({ description: 'Reset access token for application site', inputStructure: 'detailed', @@ -1991,13 +1964,13 @@ export const post30 = oc .output(zPostAppsByAppIdSiteAccessTokenResetResponse) export const accessTokenReset = { - post: post30, + post: post29, } /** * Update application site configuration */ -export const post31 = oc +export const post30 = oc .route({ description: 'Update application site configuration', inputStructure: 'detailed', @@ -2010,14 +1983,14 @@ export const post31 = oc .output(zPostAppsByAppIdSiteResponse) export const site = { - post: post31, + post: post30, accessTokenReset, } /** * Enable or disable app site */ -export const post32 = oc +export const post31 = oc .route({ description: 'Enable or disable app site', inputStructure: 'detailed', @@ -2030,7 +2003,7 @@ export const post32 = oc .output(zPostAppsByAppIdSiteEnableResponse) export const siteEnable = { - post: post32, + post: post31, } /** @@ -2051,7 +2024,7 @@ export const delete7 = oc /** * Star an application for the current account */ -export const post33 = oc +export const post32 = oc .route({ description: 'Star an application for the current account', inputStructure: 'detailed', @@ -2065,7 +2038,7 @@ export const post33 = oc export const star = { delete: delete7, - post: post33, + post: post32, } /** @@ -2295,25 +2268,7 @@ export const voices = { get: get37, } -/** - * Convert text to speech for chat messages - */ -export const post34 = oc - .route({ - description: 'Convert text to speech for chat messages', - inputStructure: 'detailed', - method: 'POST', - operationId: 'postAppsByAppIdTextToAudio', - path: '/apps/{app_id}/text-to-audio', - tags: ['console'], - }) - .input( - z.object({ body: zPostAppsByAppIdTextToAudioBody, params: zPostAppsByAppIdTextToAudioPath }), - ) - .output(zPostAppsByAppIdTextToAudioResponse) - export const textToAudio = { - post: post34, voices, } @@ -2338,7 +2293,7 @@ export const get38 = oc /** * Update app tracing configuration */ -export const post35 = oc +export const post33 = oc .route({ description: 'Update app tracing configuration', inputStructure: 'detailed', @@ -2352,7 +2307,7 @@ export const post35 = oc export const trace = { get: get38, - post: post35, + post: post33, } /** @@ -2421,7 +2376,7 @@ export const patch = oc * * Create a new tracing configuration for an application */ -export const post36 = oc +export const post34 = oc .route({ description: 'Create a new tracing configuration for an application', inputStructure: 'detailed', @@ -2441,13 +2396,13 @@ export const traceConfig = { delete: delete8, get: get39, patch, - post: post36, + post: post34, } /** * Update app trigger (enable/disable) */ -export const post37 = oc +export const post35 = oc .route({ inputStructure: 'detailed', method: 'POST', @@ -2465,7 +2420,7 @@ export const post37 = oc .output(zPostAppsByAppIdTriggerEnableResponse) export const triggerEnable = { - post: post37, + post: post35, } /** @@ -2573,7 +2528,7 @@ export const count3 = { * * Stop running workflow task */ -export const post38 = oc +export const post36 = oc .route({ description: 'Stop running workflow task', inputStructure: 'detailed', @@ -2587,7 +2542,7 @@ export const post38 = oc .output(zPostAppsByAppIdWorkflowRunsTasksByTaskIdStopResponse) export const stop3 = { - post: post38, + post: post36, } export const byTaskId3 = { @@ -2690,7 +2645,7 @@ export const read = { /** * Upload one workflow Agent sandbox file as a Dify ToolFile mapping */ -export const post39 = oc +export const post37 = oc .route({ description: 'Upload one workflow Agent sandbox file as a Dify ToolFile mapping', inputStructure: 'detailed', @@ -2708,7 +2663,7 @@ export const post39 = oc .output(zPostAppsByAppIdWorkflowRunsByWorkflowRunIdAgentNodesByNodeIdSandboxFilesUploadResponse) export const upload2 = { - post: post39, + post: post37, } /** @@ -2859,7 +2814,7 @@ export const byReplyId = { * * Add a reply to a workflow comment */ -export const post40 = oc +export const post38 = oc .route({ description: 'Add a reply to a workflow comment', inputStructure: 'detailed', @@ -2879,7 +2834,7 @@ export const post40 = oc .output(zPostAppsByAppIdWorkflowCommentsByCommentIdRepliesResponse) export const replies = { - post: post40, + post: post38, byReplyId, } @@ -2888,7 +2843,7 @@ export const replies = { * * Resolve a workflow comment */ -export const post41 = oc +export const post39 = oc .route({ description: 'Resolve a workflow comment', inputStructure: 'detailed', @@ -2902,7 +2857,7 @@ export const post41 = oc .output(zPostAppsByAppIdWorkflowCommentsByCommentIdResolveResponse) export const resolve = { - post: post41, + post: post39, } /** @@ -2996,7 +2951,7 @@ export const get52 = oc * * Create a new workflow comment */ -export const post42 = oc +export const post40 = oc .route({ description: 'Create a new workflow comment', inputStructure: 'detailed', @@ -3017,7 +2972,7 @@ export const post42 = oc export const comments = { get: get52, - post: post42, + post: post40, mentionUsers, byCommentId, } @@ -3198,7 +3153,7 @@ export const get59 = oc /** * Update conversation variables for workflow draft */ -export const post43 = oc +export const post41 = oc .route({ description: 'Update conversation variables for workflow draft', inputStructure: 'detailed', @@ -3217,7 +3172,7 @@ export const post43 = oc export const conversationVariables2 = { get: get59, - post: post43, + post: post41, } /** @@ -3241,7 +3196,7 @@ export const get60 = oc /** * Update environment variables for workflow draft */ -export const post44 = oc +export const post42 = oc .route({ description: 'Update environment variables for workflow draft', inputStructure: 'detailed', @@ -3260,13 +3215,13 @@ export const post44 = oc export const environmentVariables = { get: get60, - post: post44, + post: post42, } /** * Update draft workflow features */ -export const post45 = oc +export const post43 = oc .route({ description: 'Update draft workflow features', inputStructure: 'detailed', @@ -3284,7 +3239,7 @@ export const post45 = oc .output(zPostAppsByAppIdWorkflowsDraftFeaturesResponse) export const features = { - post: post45, + post: post43, } /** @@ -3292,7 +3247,7 @@ export const features = { * * Test human input delivery for workflow */ -export const post46 = oc +export const post44 = oc .route({ description: 'Test human input delivery for workflow', inputStructure: 'detailed', @@ -3311,7 +3266,7 @@ export const post46 = oc .output(zPostAppsByAppIdWorkflowsDraftHumanInputNodesByNodeIdDeliveryTestResponse) export const deliveryTest = { - post: post46, + post: post44, } /** @@ -3319,7 +3274,7 @@ export const deliveryTest = { * * Get human input form preview for workflow */ -export const post47 = oc +export const post45 = oc .route({ description: 'Get human input form preview for workflow', inputStructure: 'detailed', @@ -3338,7 +3293,7 @@ export const post47 = oc .output(zPostAppsByAppIdWorkflowsDraftHumanInputNodesByNodeIdFormPreviewResponse) export const preview3 = { - post: post47, + post: post45, } /** @@ -3346,7 +3301,7 @@ export const preview3 = { * * Submit human input form preview for workflow */ -export const post48 = oc +export const post46 = oc .route({ description: 'Submit human input form preview for workflow', inputStructure: 'detailed', @@ -3365,7 +3320,7 @@ export const post48 = oc .output(zPostAppsByAppIdWorkflowsDraftHumanInputNodesByNodeIdFormRunResponse) export const run5 = { - post: post48, + post: post46, } export const form2 = { @@ -3391,7 +3346,7 @@ export const humanInput2 = { * * Run draft workflow iteration node */ -export const post49 = oc +export const post47 = oc .route({ description: 'Run draft workflow iteration node', inputStructure: 'detailed', @@ -3410,7 +3365,7 @@ export const post49 = oc .output(zPostAppsByAppIdWorkflowsDraftIterationNodesByNodeIdRunResponse) export const run6 = { - post: post49, + post: post47, } export const byNodeId6 = { @@ -3430,7 +3385,7 @@ export const iteration2 = { * * Run draft workflow loop node */ -export const post50 = oc +export const post48 = oc .route({ description: 'Run draft workflow loop node', inputStructure: 'detailed', @@ -3449,7 +3404,7 @@ export const post50 = oc .output(zPostAppsByAppIdWorkflowsDraftLoopNodesByNodeIdRunResponse) export const run7 = { - post: post50, + post: post48, } export const byNodeId7 = { @@ -3481,7 +3436,7 @@ export const candidates = { get: get61, } -export const post51 = oc +export const post49 = oc .route({ inputStructure: 'detailed', method: 'POST', @@ -3498,10 +3453,10 @@ export const post51 = oc .output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCopyFromRosterResponse) export const copyFromRoster = { - post: post51, + post: post49, } -export const post52 = oc +export const post50 = oc .route({ inputStructure: 'detailed', method: 'POST', @@ -3518,10 +3473,10 @@ export const post52 = oc .output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactResponse) export const impact = { - post: post52, + post: post50, } -export const post53 = oc +export const post51 = oc .route({ inputStructure: 'detailed', method: 'POST', @@ -3538,10 +3493,10 @@ export const post53 = oc .output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerSaveToRosterResponse) export const saveToRoster = { - post: post53, + post: post51, } -export const post54 = oc +export const post52 = oc .route({ inputStructure: 'detailed', method: 'POST', @@ -3558,7 +3513,7 @@ export const post54 = oc .output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerValidateResponse) export const validate = { - post: post54, + post: post52, } export const get62 = oc @@ -3622,7 +3577,7 @@ export const lastRun = { * * Run draft workflow node */ -export const post55 = oc +export const post53 = oc .route({ description: 'Run draft workflow node', inputStructure: 'detailed', @@ -3641,7 +3596,7 @@ export const post55 = oc .output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdRunResponse) export const run8 = { - post: post55, + post: post53, } /** @@ -3649,7 +3604,7 @@ export const run8 = { * * Poll for trigger events and execute single node when event arrives */ -export const post56 = oc +export const post54 = oc .route({ description: 'Poll for trigger events and execute single node when event arrives', inputStructure: 'detailed', @@ -3663,7 +3618,7 @@ export const post56 = oc .output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdTriggerRunResponse) export const run9 = { - post: post56, + post: post54, } export const trigger = { @@ -3723,7 +3678,7 @@ export const nodes7 = { * * Run draft workflow */ -export const post57 = oc +export const post55 = oc .route({ description: 'Run draft workflow', inputStructure: 'detailed', @@ -3742,7 +3697,7 @@ export const post57 = oc .output(zPostAppsByAppIdWorkflowsDraftRunResponse) export const run10 = { - post: post57, + post: post55, } /** @@ -3864,7 +3819,7 @@ export const systemVariables = { * * Poll for trigger events and execute full workflow when event arrives */ -export const post58 = oc +export const post56 = oc .route({ description: 'Poll for trigger events and execute full workflow when event arrives', inputStructure: 'detailed', @@ -3883,7 +3838,7 @@ export const post58 = oc .output(zPostAppsByAppIdWorkflowsDraftTriggerRunResponse) export const run11 = { - post: post58, + post: post56, } /** @@ -3891,7 +3846,7 @@ export const run11 = { * * Full workflow debug when the start node is a trigger */ -export const post59 = oc +export const post57 = oc .route({ description: 'Full workflow debug when the start node is a trigger', inputStructure: 'detailed', @@ -3910,7 +3865,7 @@ export const post59 = oc .output(zPostAppsByAppIdWorkflowsDraftTriggerRunAllResponse) export const runAll = { - post: post59, + post: post57, } export const trigger2 = { @@ -4063,7 +4018,7 @@ export const get72 = oc * * Sync draft workflow configuration */ -export const post60 = oc +export const post58 = oc .route({ description: 'Sync draft workflow configuration', inputStructure: 'detailed', @@ -4083,7 +4038,7 @@ export const post60 = oc export const draft2 = { get: get72, - post: post60, + post: post58, conversationVariables: conversationVariables2, environmentVariables, features, @@ -4119,7 +4074,7 @@ export const get73 = oc /** * Publish workflow */ -export const post61 = oc +export const post59 = oc .route({ inputStructure: 'detailed', method: 'POST', @@ -4138,7 +4093,7 @@ export const post61 = oc export const publish = { get: get73, - post: post61, + post: post59, } /** @@ -4275,7 +4230,7 @@ export const triggers2 = { /** * Restore a published workflow version into the draft workflow */ -export const post62 = oc +export const post60 = oc .route({ description: 'Restore a published workflow version into the draft workflow', inputStructure: 'detailed', @@ -4288,7 +4243,7 @@ export const post62 = oc .output(zPostAppsByAppIdWorkflowsByWorkflowIdRestoreResponse) export const restore = { - post: post62, + post: post60, } /** @@ -4513,7 +4468,7 @@ export const get81 = oc * * Create a new API key for an app */ -export const post63 = oc +export const post61 = oc .route({ description: 'Create a new API key for an app', inputStructure: 'detailed', @@ -4529,7 +4484,7 @@ export const post63 = oc export const apiKeys = { get: get81, - post: post63, + post: post61, byApiKeyId, } @@ -4587,7 +4542,7 @@ export const get83 = oc * * Create a new application */ -export const post64 = oc +export const post62 = oc .route({ description: 'Create a new application', inputStructure: 'detailed', @@ -4603,7 +4558,7 @@ export const post64 = oc export const apps = { get: get83, - post: post64, + post: post62, imports, starred, workflows, diff --git a/packages/contracts/generated/api/console/apps/types.gen.ts b/packages/contracts/generated/api/console/apps/types.gen.ts index 9e79518f3cd..58ae37753f2 100644 --- a/packages/contracts/generated/api/console/apps/types.gen.ts +++ b/packages/contracts/generated/api/console/apps/types.gen.ts @@ -253,14 +253,18 @@ export type AnnotationReplyPayload = { } export type AnnotationJobStatusResponse = { - error_msg?: string | null - job_id?: string | null - job_status?: string | null - record_count?: number | null + job_id: string + job_status: 'completed' | 'error' | 'processing' | 'waiting' | string +} + +export type AnnotationJobStatusDetailResponse = { + error_msg?: string + job_id: string + job_status: 'completed' | 'error' | 'processing' | 'waiting' | string } export type AnnotationSettingResponse = { - embedding_model?: AnnotationEmbeddingModelResponse | null + embedding_model?: AnnotationSettingEmbeddingModelResponse | null enabled: boolean id?: string | null score_threshold?: number | null @@ -296,6 +300,13 @@ export type Annotation = { question?: string | null } +export type AnnotationBatchImportResponse = { + error_msg?: string | null + job_id?: string | null + job_status?: string | null + record_count?: number | null +} + export type AnnotationCountResponse = { count: number } @@ -353,10 +364,10 @@ export type AudioTranscriptResponse = { } export type ConversationWithSummaryPagination = { - has_next: boolean - items: Array + data: Array + has_more: boolean + limit: number page: number - per_page: number total: number } @@ -391,37 +402,24 @@ export type SimpleResultResponse = { } export type ConversationPagination = { - has_next: boolean - items: Array + data: Array + has_more: boolean + limit: number page: number - per_page: number total: number } export type ConversationMessageDetail = { created_at?: number | null - first_message?: MessageDetail | null from_account_id?: string | null from_end_user_id?: string | null from_source: string id: string + message?: MessageDetail | null model_config?: ModelConfig | null status: string } -export type CompletionMessagePayload = { - files?: Array | null - inputs: { - [key: string]: unknown - } - model_config?: { - [key: string]: unknown - } - query?: string - response_mode?: 'blocking' | 'streaming' - retriever_from?: string -} - export type PaginatedConversationVariableResponse = { data: Array has_more: boolean @@ -469,15 +467,16 @@ export type AppIconPayload = { } export type MessageDetailResponse = { - agent_thoughts?: Array + agent_thoughts: Array annotation?: ConversationAnnotation | null annotation_hit_history?: ConversationAnnotationHitHistory | null - answer_tokens?: number | null + answer: string + answer_tokens: number conversation_id: string created_at?: number | null error?: string | null extra_contents?: Array - feedbacks?: Array + feedbacks: Array from_account_id?: string | null from_end_user_id?: string | null from_source: string @@ -485,14 +484,13 @@ export type MessageDetailResponse = { inputs: { [key: string]: JsonValue } - message?: JsonValue | null - message_files?: Array - message_metadata_dict?: JsonValue | null - message_tokens?: number | null + message: JsonValue + message_files: Array + message_tokens: number + metadata: JsonValue parent_message_id?: string | null - provider_response_latency?: number | null + provider_response_latency: number query: string - re_sign_file_url_answer: string status: string workflow_run_id?: string | null } @@ -641,18 +639,7 @@ export type UserSatisfactionRateStatisticResponse = { data: Array } -export type TextToSpeechPayload = { - message_id?: string | null - streaming?: boolean | null - text: string - voice?: string | null -} - -export type AudioBinaryResponse = Blob | File - -export type TextToSpeechVoiceListResponse = Array<{ - [key: string]: unknown -}> +export type TextToSpeechVoiceListResponse = Array export type AppTraceResponse = { enabled: boolean @@ -1396,7 +1383,7 @@ export type CliToolSuggestion = { name: string } -export type AnnotationEmbeddingModelResponse = { +export type AnnotationSettingEmbeddingModelResponse = { embedding_model_name?: string | null embedding_provider_name?: string | null } @@ -1427,7 +1414,7 @@ export type ConversationWithSummary = { read_at?: number | null status: string status_count?: StatusCount | null - summary_or_query: string + summary: string updated_at?: number | null user_feedback_stats?: FeedbackStat | null } @@ -1441,13 +1428,13 @@ export type Conversation = { admin_feedback_stats?: FeedbackStat | null annotation?: ConversationAnnotation | null created_at?: number | null - first_message?: SimpleMessageDetail | null from_account_id?: string | null from_account_name?: string | null from_end_user_id?: string | null from_end_user_session_id?: string | null from_source: string id: string + message?: SimpleMessageDetail | null model_config?: SimpleModelConfig | null read_at?: number | null status: string @@ -1459,6 +1446,7 @@ export type MessageDetail = { agent_thoughts: Array annotation?: ConversationAnnotation | null annotation_hit_history?: ConversationAnnotationHitHistory | null + answer: string answer_tokens: number conversation_id: string created_at?: number | null @@ -1473,12 +1461,11 @@ export type MessageDetail = { } message: JsonValue message_files: Array - message_metadata_dict: JsonValue message_tokens: number + metadata: JsonValue parent_message_id?: string | null provider_response_latency: number query: string - re_sign_file_url_answer: string status: string workflow_run_id?: string | null } @@ -1498,7 +1485,6 @@ export type AgentThought = { created_at?: number | null files: Array id: string - message_chain_id?: string | null message_id: string observation?: string | null position: number @@ -1518,8 +1504,8 @@ export type ConversationAnnotation = { export type ConversationAnnotationHitHistory = { annotation_create_account?: SimpleAccount | null + annotation_id: string created_at?: number | null - id: string } export type HumanInputContent = { @@ -1578,10 +1564,10 @@ export type DailyMessageStatisticItem = { } export type DailyTokenCostStatisticItem = { - currency: string + currency?: string | null date: string - token_count: number - total_price: string | number + token_count?: number | null + total_price?: string | null } export type TokensPerSecondStatisticItem = { @@ -1594,6 +1580,11 @@ export type UserSatisfactionRateStatisticItem = { rate: number } +export type TextToSpeechVoiceResponse = { + name: string + value: string +} + export type WorkflowAppLogPartialResponse = { created_at?: number | null created_by_account?: SimpleAccount | null @@ -2034,7 +2025,7 @@ export type EnvSuggestion = { } export type SimpleModelConfig = { - model_dict?: JsonValue | null + model?: JsonValue | null pre_prompt?: string | null } @@ -3335,7 +3326,7 @@ export type GetAppsByAppIdAnnotationReplyByActionStatusByJobIdErrors = { } export type GetAppsByAppIdAnnotationReplyByActionStatusByJobIdResponses = { - 200: AnnotationJobStatusResponse + 200: AnnotationJobStatusDetailResponse } export type GetAppsByAppIdAnnotationReplyByActionStatusByJobIdResponse @@ -3459,7 +3450,7 @@ export type PostAppsByAppIdAnnotationsBatchImportErrors = { } export type PostAppsByAppIdAnnotationsBatchImportResponses = { - 200: AnnotationJobStatusResponse + 200: AnnotationBatchImportResponse } export type PostAppsByAppIdAnnotationsBatchImportResponse @@ -3480,7 +3471,7 @@ export type GetAppsByAppIdAnnotationsBatchImportStatusByJobIdErrors = { } export type GetAppsByAppIdAnnotationsBatchImportStatusByJobIdResponses = { - 200: AnnotationJobStatusResponse + 200: AnnotationJobStatusDetailResponse } export type GetAppsByAppIdAnnotationsBatchImportStatusByJobIdResponse @@ -3831,27 +3822,6 @@ export type GetAppsByAppIdCompletionConversationsByConversationIdResponses = { export type GetAppsByAppIdCompletionConversationsByConversationIdResponse = GetAppsByAppIdCompletionConversationsByConversationIdResponses[keyof GetAppsByAppIdCompletionConversationsByConversationIdResponses] -export type PostAppsByAppIdCompletionMessagesData = { - body: CompletionMessagePayload - path: { - app_id: string - } - query?: never - url: '/apps/{app_id}/completion-messages' -} - -export type PostAppsByAppIdCompletionMessagesErrors = { - 400: unknown - 404: unknown -} - -export type PostAppsByAppIdCompletionMessagesResponses = { - 200: GeneratedAppResponse -} - -export type PostAppsByAppIdCompletionMessagesResponse - = PostAppsByAppIdCompletionMessagesResponses[keyof PostAppsByAppIdCompletionMessagesResponses] - export type PostAppsByAppIdCompletionMessagesByTaskIdStopData = { body?: never path: { @@ -4405,26 +4375,6 @@ export type GetAppsByAppIdStatisticsUserSatisfactionRateResponses = { export type GetAppsByAppIdStatisticsUserSatisfactionRateResponse = GetAppsByAppIdStatisticsUserSatisfactionRateResponses[keyof GetAppsByAppIdStatisticsUserSatisfactionRateResponses] -export type PostAppsByAppIdTextToAudioData = { - body: TextToSpeechPayload - path: { - app_id: string - } - query?: never - url: '/apps/{app_id}/text-to-audio' -} - -export type PostAppsByAppIdTextToAudioErrors = { - 400: unknown -} - -export type PostAppsByAppIdTextToAudioResponses = { - 200: AudioBinaryResponse -} - -export type PostAppsByAppIdTextToAudioResponse - = PostAppsByAppIdTextToAudioResponses[keyof PostAppsByAppIdTextToAudioResponses] - export type GetAppsByAppIdTextToAudioVoicesData = { body?: never path: { diff --git a/packages/contracts/generated/api/console/apps/zod.gen.ts b/packages/contracts/generated/api/console/apps/zod.gen.ts index 9b86fda0a62..2eab48639e7 100644 --- a/packages/contracts/generated/api/console/apps/zod.gen.ts +++ b/packages/contracts/generated/api/console/apps/zod.gen.ts @@ -144,10 +144,17 @@ export const zAnnotationReplyPayload = z.object({ * AnnotationJobStatusResponse */ export const zAnnotationJobStatusResponse = z.object({ - error_msg: z.string().nullish(), - job_id: z.string().nullish(), - job_status: z.string().nullish(), - record_count: z.int().nullish(), + job_id: z.string(), + job_status: z.union([z.enum(['completed', 'error', 'processing', 'waiting']), z.string()]), +}) + +/** + * AnnotationJobStatusDetailResponse + */ +export const zAnnotationJobStatusDetailResponse = z.object({ + error_msg: z.string().optional().default(''), + job_id: z.string(), + job_status: z.union([z.enum(['completed', 'error', 'processing', 'waiting']), z.string()]), }) /** @@ -190,6 +197,16 @@ export const zAnnotationList = z.object({ total: z.int(), }) +/** + * AnnotationBatchImportResponse + */ +export const zAnnotationBatchImportResponse = z.object({ + error_msg: z.string().nullish(), + job_id: z.string().nullish(), + job_status: z.string().nullish(), + record_count: z.int().nullish(), +}) + /** * AnnotationCountResponse */ @@ -242,18 +259,6 @@ export const zSimpleResultResponse = z.object({ result: z.string(), }) -/** - * CompletionMessagePayload - */ -export const zCompletionMessagePayload = z.object({ - files: z.array(z.unknown()).nullish(), - inputs: z.record(z.string(), z.unknown()), - model_config: z.record(z.string(), z.unknown()).optional(), - query: z.string().optional().default(''), - response_mode: z.enum(['blocking', 'streaming']).optional().default('blocking'), - retriever_from: z.string().optional().default('dev'), -}) - /** * ConvertToWorkflowPayload */ @@ -393,26 +398,6 @@ export const zAppSiteStatusPayload = z.object({ enable_site: z.boolean(), }) -/** - * TextToSpeechPayload - */ -export const zTextToSpeechPayload = z.object({ - message_id: z.string().nullish(), - streaming: z.boolean().nullish(), - text: z.string(), - voice: z.string().nullish(), -}) - -/** - * AudioBinaryResponse - */ -export const zAudioBinaryResponse = z.custom() - -/** - * TextToSpeechVoiceListResponse - */ -export const zTextToSpeechVoiceListResponse = z.array(z.record(z.string(), z.unknown())) - /** * AppTraceResponse */ @@ -1069,9 +1054,9 @@ export const zAgentSkillUploadResponse = z.object({ }) /** - * AnnotationEmbeddingModelResponse + * AnnotationSettingEmbeddingModelResponse */ -export const zAnnotationEmbeddingModelResponse = z.object({ +export const zAnnotationSettingEmbeddingModelResponse = z.object({ embedding_model_name: z.string().nullish(), embedding_provider_name: z.string().nullish(), }) @@ -1080,7 +1065,7 @@ export const zAnnotationEmbeddingModelResponse = z.object({ * AnnotationSettingResponse */ export const zAnnotationSettingResponse = z.object({ - embedding_model: zAnnotationEmbeddingModelResponse.nullish(), + embedding_model: zAnnotationSettingEmbeddingModelResponse.nullish(), enabled: z.boolean(), id: z.string().nullish(), score_threshold: z.number().nullish(), @@ -1150,7 +1135,6 @@ export const zAgentThought = z.object({ created_at: z.int().nullish(), files: z.array(z.string()), id: z.string(), - message_chain_id: z.string().nullish(), message_id: z.string(), observation: z.string().nullish(), position: z.int(), @@ -1275,10 +1259,13 @@ export const zDailyMessageStatisticResponse = z.object({ * DailyTokenCostStatisticItem */ export const zDailyTokenCostStatisticItem = z.object({ - currency: z.string(), + currency: z.string().nullish(), date: z.string(), - token_count: z.int(), - total_price: z.union([z.string(), z.number()]), + token_count: z.int().nullish(), + total_price: z + .string() + .regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/) + .nullish(), }) /** @@ -1318,6 +1305,21 @@ export const zUserSatisfactionRateStatisticResponse = z.object({ data: z.array(zUserSatisfactionRateStatisticItem), }) +/** + * TextToSpeechVoiceResponse + */ +export const zTextToSpeechVoiceResponse = z.object({ + name: z.string(), + value: z.string(), +}) + +/** + * TextToSpeechVoiceListResponse + * + * Available voices + */ +export const zTextToSpeechVoiceListResponse = z.array(zTextToSpeechVoiceResponse) + /** * SimpleAccount */ @@ -1371,8 +1373,8 @@ export const zConversationAnnotation = z.object({ */ export const zConversationAnnotationHitHistory = z.object({ annotation_create_account: zSimpleAccount.nullish(), + annotation_id: z.string(), created_at: z.int().nullish(), - id: z.string(), }) /** @@ -1393,6 +1395,7 @@ export const zMessageDetail = z.object({ agent_thoughts: z.array(zAgentThought), annotation: zConversationAnnotation.nullish(), annotation_hit_history: zConversationAnnotationHitHistory.nullish(), + answer: z.string(), answer_tokens: z.int(), conversation_id: z.string(), created_at: z.int().nullish(), @@ -1405,12 +1408,11 @@ export const zMessageDetail = z.object({ inputs: z.record(z.string(), zJsonValue), message: zJsonValue, message_files: z.array(zMessageFile), - message_metadata_dict: zJsonValue, message_tokens: z.int(), + metadata: zJsonValue, parent_message_id: z.string().nullish(), provider_response_latency: z.number(), query: z.string(), - re_sign_file_url_answer: z.string(), status: z.string(), workflow_run_id: z.string().nullish(), }) @@ -2157,11 +2159,11 @@ export const zConversationDetail = z.object({ */ export const zConversationMessageDetail = z.object({ created_at: z.int().nullish(), - first_message: zMessageDetail.nullish(), from_account_id: z.string().nullish(), from_end_user_id: z.string().nullish(), from_source: z.string(), id: z.string(), + message: zMessageDetail.nullish(), model_config: zModelConfig.nullish(), status: z.string(), }) @@ -2307,7 +2309,7 @@ export const zSkillToolInferenceResult = z.object({ * SimpleModelConfig */ export const zSimpleModelConfig = z.object({ - model_dict: zJsonValue.nullish(), + model: zJsonValue.nullish(), pre_prompt: z.string().nullish(), }) @@ -2340,7 +2342,7 @@ export const zConversationWithSummary = z.object({ read_at: z.int().nullish(), status: z.string(), status_count: zStatusCount.nullish(), - summary_or_query: z.string(), + summary: z.string(), updated_at: z.int().nullish(), user_feedback_stats: zFeedbackStat.nullish(), }) @@ -2349,10 +2351,10 @@ export const zConversationWithSummary = z.object({ * ConversationWithSummaryPagination */ export const zConversationWithSummaryPagination = z.object({ - has_next: z.boolean(), - items: z.array(zConversationWithSummary), + data: z.array(zConversationWithSummary), + has_more: z.boolean(), + limit: z.int(), page: z.int(), - per_page: z.int(), total: z.int(), }) @@ -2373,13 +2375,13 @@ export const zConversation = z.object({ admin_feedback_stats: zFeedbackStat.nullish(), annotation: zConversationAnnotation.nullish(), created_at: z.int().nullish(), - first_message: zSimpleMessageDetail.nullish(), from_account_id: z.string().nullish(), from_account_name: z.string().nullish(), from_end_user_id: z.string().nullish(), from_end_user_session_id: z.string().nullish(), from_source: z.string(), id: z.string(), + message: zSimpleMessageDetail.nullish(), model_config: zSimpleModelConfig.nullish(), read_at: z.int().nullish(), status: z.string(), @@ -2391,10 +2393,10 @@ export const zConversation = z.object({ * ConversationPagination */ export const zConversationPagination = z.object({ - has_next: z.boolean(), - items: z.array(zConversation), + data: z.array(zConversation), + has_more: z.boolean(), + limit: z.int(), page: z.int(), - per_page: z.int(), total: z.int(), }) @@ -3452,28 +3454,28 @@ export const zHumanInputContent = z.object({ * MessageDetailResponse */ export const zMessageDetailResponse = z.object({ - agent_thoughts: z.array(zAgentThought).optional(), + agent_thoughts: z.array(zAgentThought), annotation: zConversationAnnotation.nullish(), annotation_hit_history: zConversationAnnotationHitHistory.nullish(), - answer_tokens: z.int().nullish(), + answer: z.string(), + answer_tokens: z.int(), conversation_id: z.string(), created_at: z.int().nullish(), error: z.string().nullish(), extra_contents: z.array(zHumanInputContent).optional(), - feedbacks: z.array(zFeedback).optional(), + feedbacks: z.array(zFeedback), from_account_id: z.string().nullish(), from_end_user_id: z.string().nullish(), from_source: z.string(), id: z.string(), inputs: z.record(z.string(), zJsonValue), - message: zJsonValue.nullish(), - message_files: z.array(zMessageFile).optional(), - message_metadata_dict: zJsonValue.nullish(), - message_tokens: z.int().nullish(), + message: zJsonValue, + message_files: z.array(zMessageFile), + message_tokens: z.int(), + metadata: zJsonValue, parent_message_id: z.string().nullish(), - provider_response_latency: z.number().nullish(), + provider_response_latency: z.number(), query: z.string(), - re_sign_file_url_answer: z.string(), status: z.string(), workflow_run_id: z.string().nullish(), }) @@ -4075,7 +4077,7 @@ export const zGetAppsByAppIdAnnotationReplyByActionStatusByJobIdPath = z.object( * Job status retrieved successfully */ export const zGetAppsByAppIdAnnotationReplyByActionStatusByJobIdResponse - = zAnnotationJobStatusResponse + = zAnnotationJobStatusDetailResponse export const zGetAppsByAppIdAnnotationSettingPath = z.object({ app_id: z.uuid(), @@ -4142,7 +4144,7 @@ export const zPostAppsByAppIdAnnotationsBatchImportPath = z.object({ /** * Batch import started successfully */ -export const zPostAppsByAppIdAnnotationsBatchImportResponse = zAnnotationJobStatusResponse +export const zPostAppsByAppIdAnnotationsBatchImportResponse = zAnnotationBatchImportResponse export const zGetAppsByAppIdAnnotationsBatchImportStatusByJobIdPath = z.object({ app_id: z.uuid(), @@ -4153,7 +4155,7 @@ export const zGetAppsByAppIdAnnotationsBatchImportStatusByJobIdPath = z.object({ * Job status retrieved successfully */ export const zGetAppsByAppIdAnnotationsBatchImportStatusByJobIdResponse - = zAnnotationJobStatusResponse + = zAnnotationJobStatusDetailResponse export const zGetAppsByAppIdAnnotationsCountPath = z.object({ app_id: z.uuid(), @@ -4345,17 +4347,6 @@ export const zGetAppsByAppIdCompletionConversationsByConversationIdPath = z.obje export const zGetAppsByAppIdCompletionConversationsByConversationIdResponse = zConversationMessageDetail -export const zPostAppsByAppIdCompletionMessagesBody = zCompletionMessagePayload - -export const zPostAppsByAppIdCompletionMessagesPath = z.object({ - app_id: z.uuid(), -}) - -/** - * Completion generated successfully - */ -export const zPostAppsByAppIdCompletionMessagesResponse = zGeneratedAppResponse - export const zPostAppsByAppIdCompletionMessagesByTaskIdStopPath = z.object({ app_id: z.uuid(), task_id: z.string(), @@ -4692,17 +4683,6 @@ export const zGetAppsByAppIdStatisticsUserSatisfactionRateQuery = z.object({ export const zGetAppsByAppIdStatisticsUserSatisfactionRateResponse = zUserSatisfactionRateStatisticResponse -export const zPostAppsByAppIdTextToAudioBody = zTextToSpeechPayload - -export const zPostAppsByAppIdTextToAudioPath = z.object({ - app_id: z.uuid(), -}) - -/** - * Text to speech conversion successful - */ -export const zPostAppsByAppIdTextToAudioResponse = zAudioBinaryResponse - export const zGetAppsByAppIdTextToAudioVoicesPath = z.object({ app_id: z.uuid(), }) diff --git a/packages/contracts/generated/api/console/installed-apps/types.gen.ts b/packages/contracts/generated/api/console/installed-apps/types.gen.ts index f9a5eb01edc..61b1303d5b5 100644 --- a/packages/contracts/generated/api/console/installed-apps/types.gen.ts +++ b/packages/contracts/generated/api/console/installed-apps/types.gen.ts @@ -246,7 +246,6 @@ export type AgentThought = { created_at?: number | null files: Array id: string - message_chain_id?: string | null message_id: string observation?: string | null position: number diff --git a/packages/contracts/generated/api/console/installed-apps/zod.gen.ts b/packages/contracts/generated/api/console/installed-apps/zod.gen.ts index a4556058506..53439d132c8 100644 --- a/packages/contracts/generated/api/console/installed-apps/zod.gen.ts +++ b/packages/contracts/generated/api/console/installed-apps/zod.gen.ts @@ -266,7 +266,6 @@ export const zAgentThought = z.object({ created_at: z.int().nullish(), files: z.array(z.string()), id: z.string(), - message_chain_id: z.string().nullish(), message_id: z.string(), observation: z.string().nullish(), position: z.int(), diff --git a/packages/contracts/openapi-ts.api.config.ts b/packages/contracts/openapi-ts.api.config.ts index 8fce8a25bd3..1adbf4fda8e 100644 --- a/packages/contracts/openapi-ts.api.config.ts +++ b/packages/contracts/openapi-ts.api.config.ts @@ -10,13 +10,21 @@ type SwaggerSchema = JsonObject & { $ref?: string } +type OpenApiMediaType = JsonObject & { + schema?: SwaggerSchema +} + +type OpenApiResponse = JsonObject & { + content?: Record +} + type OpenApiComponents = JsonObject & { schemas?: Record } type SwaggerOperation = JsonObject & { operationId?: string - responses?: Record + responses?: Record } type SwaggerDocument = JsonObject & { @@ -52,6 +60,17 @@ const currentDir = path.dirname(fileURLToPath(import.meta.url)) const apiOpenApiDir = path.resolve(currentDir, 'openapi') const operationMethods = new Set(['delete', 'get', 'patch', 'post', 'put']) +const pydanticDecimalStringPattern = '^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$' +const codegenSafeDecimalStringPattern = '^(?![-+.]*$)[+-]?0*\\d*\\.?\\d*$' + +const opaqueJsonContent = (): Record => ({ + 'application/json': { + schema: { + additionalProperties: true, + type: 'object', + }, + }, +}) const apiSpecs: ApiSpec[] = [ { filename: 'console-openapi.json', name: 'console' }, @@ -182,6 +201,46 @@ const addOperationIds = (document: SwaggerDocument) => { } } +const isOpaqueContractResponse = (response: OpenApiResponse) => { + const content = response.content + if (!isObject(content)) + return false + + return Object.entries(content).some(([mediaType, media]) => { + if (!isObject(media)) + return false + + return (mediaType === 'application/json' || mediaType === 'text/event-stream') && !('schema' in media) + }) +} + +const hasOpaqueContractSuccessResponse = (operation: SwaggerOperation) => { + return Object.entries(operation.responses ?? {}).some(([status, response]) => { + return /^2\d\d$/.test(status) && isObject(response) && isOpaqueContractResponse(response) + }) +} + +const normalizeOpaqueContractResponses = (document: SwaggerDocument) => { + // Some backend endpoints has no schema (e.g. external) and will trap heyapi here + // So we forge an opaque schema here + for (const pathItem of Object.values(document.paths ?? {})) { + for (const [method, operation] of Object.entries(pathItem)) { + if (!operationMethods.has(method) || !isObject(operation)) + continue + + const swaggerOperation = operation as SwaggerOperation + if (!hasOpaqueContractSuccessResponse(swaggerOperation)) + continue + + Object.values(swaggerOperation.responses ?? {}) + .filter(response => isObject(response) && isOpaqueContractResponse(response)) + .forEach((response) => { + response.content = opaqueJsonContent() + }) + } + } +} + const hasSuccessResponse = (operation: SwaggerOperation) => { return Object.entries(operation.responses ?? {}).some(([status, response]) => { if (!/^2\d\d$/.test(status)) @@ -215,6 +274,7 @@ const filterContractOperations = (document: SwaggerDocument) => { } const normalizeApiSwagger = (document: SwaggerDocument) => { + normalizeOpaqueContractResponses(document) filterContractOperations(document) addOperationIds(document) @@ -380,10 +440,20 @@ const createApiConfig = (job: ApiJob): UserConfig => ({ 'name': 'zod', '~resolvers': { string: (ctx) => { - if (ctx.schema.format !== 'binary') - return undefined + if (ctx.schema.format === 'binary') + return $(ctx.symbols.z).attr('custom').call().generic($.type.or($.type('Blob'), $.type('File'))) - return $(ctx.symbols.z).attr('custom').call().generic($.type.or($.type('Blob'), $.type('File'))) + if (ctx.schema.pattern === pydanticDecimalStringPattern) { + // the pydantic generated regex will emit error like + // regexp/no-useless-assertions, so patch the regex here + return $(ctx.symbols.z) + .attr('string') + .call() + .attr('regex') + .call($.regexp(codegenSafeDecimalStringPattern)) + } + + return undefined }, }, }, From e149cc99728d3c698df3a7cceabd9feb00a1714d Mon Sep 17 00:00:00 2001 From: chariri Date: Fri, 26 Jun 2026 02:54:19 +0900 Subject: [PATCH 2/2] fix(web): read agent debug answer from generated contract --- .../agent-detail/configure/components/preview/chat.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/chat.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/chat.tsx index a3127b4bd49..9ad0555d196 100644 --- a/web/features/agent-v2/agent-detail/configure/components/preview/chat.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/preview/chat.tsx @@ -217,14 +217,8 @@ const toFeedback = (feedback: NonNullable[nu } } -type AgentDebugMessageWithLegacyAnswer = MessageDetailResponse & { - answer?: string | null -} - const getAgentDebugMessageAnswer = (message: MessageDetailResponse) => { - const legacyAnswer = (message as AgentDebugMessageWithLegacyAnswer).answer - - return message.re_sign_file_url_answer ?? legacyAnswer ?? '' + return message.answer ?? '' } function getFormattedAgentDebugChatTree(messages: MessageDetailResponse[]): ChatItemInTree[] {