This commit is contained in:
chariri 2026-06-25 18:39:16 +00:00 committed by GitHub
commit 448636638a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 625 additions and 788 deletions

View File

@ -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/<uuid:app_id>/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/<uuid:app_id>/annotation-settings/<uuid:annotation_setting_id>")
@ -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/<uuid:app_id>/annotation-reply/<string:action>/status/<uuid:job_id>")
@ -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/<uuid:app_id>/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/<uuid:app_id>/annotations/<uuid:annotation_id>")
@ -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/<uuid:app_id>/annotations/batch-import-status/<uuid:job_id>")
@ -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/<uuid:app_id>/annotations/<uuid:annotation_id>/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")

View File

@ -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/<uuid:app_id>/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:

View File

@ -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/<uuid:app_id>/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

View File

@ -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/<uuid:app_id>/completion-conversations/<uuid:conversation_id>")
@ -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/<uuid:app_id>/chat-conversations/<uuid:conversation_id>")
@ -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")

View File

@ -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")

View File

@ -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/<uuid:app_id>/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/<uuid:app_id>/chat-messages/<uuid:message_id>/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)

View File

@ -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/<uuid:app_id>/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/<uuid:app_id>/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/<uuid:app_id>/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/<uuid:app_id>/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/<uuid:app_id>/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/<uuid:app_id>/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/<uuid:app_id>/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})

View File

@ -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]

View File

@ -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

View File

@ -1782,7 +1782,7 @@ Get status of annotation reply action job
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusResponse](#annotationjobstatusresponse)<br> |
| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusDetailResponse](#annotationjobstatusdetailresponse)<br> |
| 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)<br> |
| 200 | Batch import started successfully | **application/json**: [AnnotationBatchImportResponse](#annotationbatchimportresponse)<br> |
| 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)<br> |
| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusDetailResponse](#annotationjobstatusdetailresponse)<br> |
| 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)<br> |
| 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)<br> |
| 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, <br>**Default:** 20 | Page size | No |
| page | integer, <br>**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<br>string | | Yes |
#### AnnotationJobStatusResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| job_id | string | | Yes |
| job_status | string<br>string | | Yes |
#### AnnotationList
@ -13596,11 +13603,18 @@ Soft lifecycle state for Agent records.
| ---- | ---- | ----------- | -------- |
| action | string, <br>**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<br>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 |

View File

@ -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")

View File

@ -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)

View File

@ -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": [],
}

View File

@ -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

View File

@ -266,15 +266,16 @@ export type AgentLogMessageListResponse = {
}
export type MessageDetailResponse = {
agent_thoughts?: Array<AgentThought>
agent_thoughts: Array<AgentThought>
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<HumanInputContent>
feedbacks?: Array<Feedback>
feedbacks: Array<Feedback>
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<MessageFile>
message_metadata_dict?: JsonValue | null
message_tokens?: number | null
message: JsonValue
message_files: Array<MessageFile>
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<string>
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 = {

View File

@ -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(),
})

View File

@ -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,

View File

@ -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<ConversationWithSummary>
data: Array<ConversationWithSummary>
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<Conversation>
data: Array<Conversation>
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<unknown> | null
inputs: {
[key: string]: unknown
}
model_config?: {
[key: string]: unknown
}
query?: string
response_mode?: 'blocking' | 'streaming'
retriever_from?: string
}
export type PaginatedConversationVariableResponse = {
data: Array<ConversationVariableResponse>
has_more: boolean
@ -469,15 +467,16 @@ export type AppIconPayload = {
}
export type MessageDetailResponse = {
agent_thoughts?: Array<AgentThought>
agent_thoughts: Array<AgentThought>
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<HumanInputContent>
feedbacks?: Array<Feedback>
feedbacks: Array<Feedback>
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<MessageFile>
message_metadata_dict?: JsonValue | null
message_tokens?: number | null
message: JsonValue
message_files: Array<MessageFile>
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<UserSatisfactionRateStatisticItem>
}
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<TextToSpeechVoiceResponse>
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<AgentThought>
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<MessageFile>
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<string>
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: {

View File

@ -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<Blob | File>()
/**
* 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(),
})

View File

@ -246,7 +246,6 @@ export type AgentThought = {
created_at?: number | null
files: Array<string>
id: string
message_chain_id?: string | null
message_id: string
observation?: string | null
position: number

View File

@ -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(),

View File

@ -10,13 +10,21 @@ type SwaggerSchema = JsonObject & {
$ref?: string
}
type OpenApiMediaType = JsonObject & {
schema?: SwaggerSchema
}
type OpenApiResponse = JsonObject & {
content?: Record<string, OpenApiMediaType>
}
type OpenApiComponents = JsonObject & {
schemas?: Record<string, SwaggerSchema>
}
type SwaggerOperation = JsonObject & {
operationId?: string
responses?: Record<string, unknown>
responses?: Record<string, OpenApiResponse>
}
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<string, OpenApiMediaType> => ({
'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
},
},
},

View File

@ -217,14 +217,8 @@ const toFeedback = (feedback: NonNullable<MessageDetailResponse['feedbacks']>[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[] {