2025-12-08 15:31:19 +09:00
|
|
|
from typing import Any
|
|
|
|
|
from uuid import UUID
|
|
|
|
|
|
|
|
|
|
from flask import request
|
|
|
|
|
from flask_restx import marshal_with
|
2025-12-11 17:05:41 +08:00
|
|
|
from pydantic import BaseModel, Field, model_validator
|
2024-12-22 10:39:29 +08:00
|
|
|
from sqlalchemy.orm import Session
|
2024-02-06 13:21:13 +08:00
|
|
|
from werkzeug.exceptions import NotFound
|
|
|
|
|
|
2025-12-08 15:31:19 +09:00
|
|
|
from controllers.common.schema import register_schema_models
|
2023-05-25 15:54:45 +08:00
|
|
|
from controllers.console.explore.error import NotChatAppError
|
|
|
|
|
from controllers.console.explore.wraps import InstalledAppResource
|
2024-04-09 17:04:48 +08:00
|
|
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
2024-12-22 10:39:29 +08:00
|
|
|
from extensions.ext_database import db
|
2023-09-27 16:06:32 +08:00
|
|
|
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
|
2025-09-10 01:54:26 +08:00
|
|
|
from libs.login import current_user
|
|
|
|
|
from models import Account
|
2024-04-08 18:51:46 +08:00
|
|
|
from models.model import AppMode
|
2023-05-25 15:54:45 +08:00
|
|
|
from services.conversation_service import ConversationService
|
2024-01-12 12:34:01 +08:00
|
|
|
from services.errors.conversation import ConversationNotExistsError, LastConversationNotExistsError
|
2023-05-25 15:54:45 +08:00
|
|
|
from services.web_conversation_service import WebConversationService
|
|
|
|
|
|
2025-09-28 13:37:06 +08:00
|
|
|
from .. import console_ns
|
2023-05-25 15:54:45 +08:00
|
|
|
|
2025-09-28 13:37:06 +08:00
|
|
|
|
2025-12-08 15:31:19 +09:00
|
|
|
class ConversationListQuery(BaseModel):
|
|
|
|
|
last_id: UUID | None = None
|
|
|
|
|
limit: int = Field(default=20, ge=1, le=100)
|
|
|
|
|
pinned: bool | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConversationRenamePayload(BaseModel):
|
2025-12-11 17:05:41 +08:00
|
|
|
name: str | None = None
|
2025-12-08 15:31:19 +09:00
|
|
|
auto_generate: bool = False
|
|
|
|
|
|
2025-12-11 17:05:41 +08:00
|
|
|
@model_validator(mode="after")
|
|
|
|
|
def validate_name_requirement(self):
|
|
|
|
|
if not self.auto_generate:
|
|
|
|
|
if self.name is None or not self.name.strip():
|
|
|
|
|
raise ValueError("name is required when auto_generate is false")
|
|
|
|
|
return self
|
|
|
|
|
|
2025-12-08 15:31:19 +09:00
|
|
|
|
|
|
|
|
register_schema_models(console_ns, ConversationListQuery, ConversationRenamePayload)
|
|
|
|
|
|
|
|
|
|
|
2025-09-28 13:37:06 +08:00
|
|
|
@console_ns.route(
|
|
|
|
|
"/installed-apps/<uuid:installed_app_id>/conversations",
|
|
|
|
|
endpoint="installed_app_conversations",
|
|
|
|
|
)
|
2023-05-25 15:54:45 +08:00
|
|
|
class ConversationListApi(InstalledAppResource):
|
|
|
|
|
@marshal_with(conversation_infinite_scroll_pagination_fields)
|
2025-12-08 15:31:19 +09:00
|
|
|
@console_ns.expect(console_ns.models[ConversationListQuery.__name__])
|
2023-05-25 15:54:45 +08:00
|
|
|
def get(self, installed_app):
|
|
|
|
|
app_model = installed_app.app
|
2024-04-08 18:51:46 +08:00
|
|
|
app_mode = AppMode.value_of(app_model.mode)
|
2024-09-13 22:42:08 +08:00
|
|
|
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
|
2023-05-25 15:54:45 +08:00
|
|
|
raise NotChatAppError()
|
|
|
|
|
|
2025-12-08 15:31:19 +09:00
|
|
|
raw_args: dict[str, Any] = {
|
|
|
|
|
"last_id": request.args.get("last_id"),
|
|
|
|
|
"limit": request.args.get("limit", default=20, type=int),
|
|
|
|
|
"pinned": request.args.get("pinned"),
|
|
|
|
|
}
|
|
|
|
|
if raw_args["last_id"] is None:
|
|
|
|
|
raw_args["last_id"] = None
|
|
|
|
|
pinned_value = raw_args["pinned"]
|
|
|
|
|
if isinstance(pinned_value, str):
|
|
|
|
|
raw_args["pinned"] = pinned_value == "true"
|
|
|
|
|
args = ConversationListQuery.model_validate(raw_args)
|
2023-05-25 15:54:45 +08:00
|
|
|
|
|
|
|
|
try:
|
2025-09-10 01:54:26 +08:00
|
|
|
if not isinstance(current_user, Account):
|
|
|
|
|
raise ValueError("current_user must be an Account instance")
|
2024-12-22 10:39:29 +08:00
|
|
|
with Session(db.engine) as session:
|
|
|
|
|
return WebConversationService.pagination_by_last_id(
|
|
|
|
|
session=session,
|
|
|
|
|
app_model=app_model,
|
|
|
|
|
user=current_user,
|
2025-12-08 15:31:19 +09:00
|
|
|
last_id=str(args.last_id) if args.last_id else None,
|
|
|
|
|
limit=args.limit,
|
2024-12-22 10:39:29 +08:00
|
|
|
invoke_from=InvokeFrom.EXPLORE,
|
2025-12-08 15:31:19 +09:00
|
|
|
pinned=args.pinned,
|
2024-12-22 10:39:29 +08:00
|
|
|
)
|
2023-05-25 15:54:45 +08:00
|
|
|
except LastConversationNotExistsError:
|
|
|
|
|
raise NotFound("Last Conversation Not Exists.")
|
|
|
|
|
|
|
|
|
|
|
2025-09-28 13:37:06 +08:00
|
|
|
@console_ns.route(
|
|
|
|
|
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>",
|
|
|
|
|
endpoint="installed_app_conversation",
|
|
|
|
|
)
|
2023-05-25 15:54:45 +08:00
|
|
|
class ConversationApi(InstalledAppResource):
|
|
|
|
|
def delete(self, installed_app, c_id):
|
|
|
|
|
app_model = installed_app.app
|
2024-04-08 18:51:46 +08:00
|
|
|
app_mode = AppMode.value_of(app_model.mode)
|
2024-09-13 22:42:08 +08:00
|
|
|
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
|
2023-05-25 15:54:45 +08:00
|
|
|
raise NotChatAppError()
|
|
|
|
|
|
|
|
|
|
conversation_id = str(c_id)
|
2023-07-27 13:08:57 +08:00
|
|
|
try:
|
2025-09-10 01:54:26 +08:00
|
|
|
if not isinstance(current_user, Account):
|
|
|
|
|
raise ValueError("current_user must be an Account instance")
|
2023-07-27 13:08:57 +08:00
|
|
|
ConversationService.delete(app_model, conversation_id, current_user)
|
|
|
|
|
except ConversationNotExistsError:
|
|
|
|
|
raise NotFound("Conversation Not Exists.")
|
2023-05-25 15:54:45 +08:00
|
|
|
|
|
|
|
|
return {"result": "success"}, 204
|
|
|
|
|
|
|
|
|
|
|
2025-09-28 13:37:06 +08:00
|
|
|
@console_ns.route(
|
|
|
|
|
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/name",
|
|
|
|
|
endpoint="installed_app_conversation_rename",
|
|
|
|
|
)
|
2023-05-25 15:54:45 +08:00
|
|
|
class ConversationRenameApi(InstalledAppResource):
|
2023-09-27 16:06:32 +08:00
|
|
|
@marshal_with(simple_conversation_fields)
|
2025-12-08 15:31:19 +09:00
|
|
|
@console_ns.expect(console_ns.models[ConversationRenamePayload.__name__])
|
2023-05-25 15:54:45 +08:00
|
|
|
def post(self, installed_app, c_id):
|
|
|
|
|
app_model = installed_app.app
|
2024-04-08 18:51:46 +08:00
|
|
|
app_mode = AppMode.value_of(app_model.mode)
|
2024-09-13 22:42:08 +08:00
|
|
|
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
|
2023-05-25 15:54:45 +08:00
|
|
|
raise NotChatAppError()
|
|
|
|
|
|
|
|
|
|
conversation_id = str(c_id)
|
|
|
|
|
|
2025-12-08 15:31:19 +09:00
|
|
|
payload = ConversationRenamePayload.model_validate(console_ns.payload or {})
|
2023-05-25 15:54:45 +08:00
|
|
|
|
|
|
|
|
try:
|
2025-09-10 01:54:26 +08:00
|
|
|
if not isinstance(current_user, Account):
|
|
|
|
|
raise ValueError("current_user must be an Account instance")
|
2023-11-13 22:05:46 +08:00
|
|
|
return ConversationService.rename(
|
2025-12-08 15:31:19 +09:00
|
|
|
app_model, conversation_id, current_user, payload.name, payload.auto_generate
|
2023-11-13 22:05:46 +08:00
|
|
|
)
|
2023-05-25 15:54:45 +08:00
|
|
|
except ConversationNotExistsError:
|
|
|
|
|
raise NotFound("Conversation Not Exists.")
|
|
|
|
|
|
|
|
|
|
|
2025-09-28 13:37:06 +08:00
|
|
|
@console_ns.route(
|
|
|
|
|
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/pin",
|
|
|
|
|
endpoint="installed_app_conversation_pin",
|
|
|
|
|
)
|
2023-05-25 15:54:45 +08:00
|
|
|
class ConversationPinApi(InstalledAppResource):
|
|
|
|
|
def patch(self, installed_app, c_id):
|
|
|
|
|
app_model = installed_app.app
|
2024-04-08 18:51:46 +08:00
|
|
|
app_mode = AppMode.value_of(app_model.mode)
|
2024-09-13 22:42:08 +08:00
|
|
|
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
|
2023-05-25 15:54:45 +08:00
|
|
|
raise NotChatAppError()
|
|
|
|
|
|
|
|
|
|
conversation_id = str(c_id)
|
|
|
|
|
|
|
|
|
|
try:
|
2025-09-10 01:54:26 +08:00
|
|
|
if not isinstance(current_user, Account):
|
|
|
|
|
raise ValueError("current_user must be an Account instance")
|
2023-05-25 15:54:45 +08:00
|
|
|
WebConversationService.pin(app_model, conversation_id, current_user)
|
|
|
|
|
except ConversationNotExistsError:
|
|
|
|
|
raise NotFound("Conversation Not Exists.")
|
|
|
|
|
|
|
|
|
|
return {"result": "success"}
|
|
|
|
|
|
|
|
|
|
|
2025-09-28 13:37:06 +08:00
|
|
|
@console_ns.route(
|
|
|
|
|
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/unpin",
|
|
|
|
|
endpoint="installed_app_conversation_unpin",
|
|
|
|
|
)
|
2023-05-25 15:54:45 +08:00
|
|
|
class ConversationUnPinApi(InstalledAppResource):
|
|
|
|
|
def patch(self, installed_app, c_id):
|
|
|
|
|
app_model = installed_app.app
|
2024-04-08 18:51:46 +08:00
|
|
|
app_mode = AppMode.value_of(app_model.mode)
|
2024-09-13 22:42:08 +08:00
|
|
|
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
|
2023-05-25 15:54:45 +08:00
|
|
|
raise NotChatAppError()
|
|
|
|
|
|
|
|
|
|
conversation_id = str(c_id)
|
2025-09-10 01:54:26 +08:00
|
|
|
if not isinstance(current_user, Account):
|
|
|
|
|
raise ValueError("current_user must be an Account instance")
|
2023-05-25 15:54:45 +08:00
|
|
|
WebConversationService.unpin(app_model, conversation_id, current_user)
|
|
|
|
|
|
|
|
|
|
return {"result": "success"}
|