Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions apps/chat/api/portal_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,41 @@ def get_parameters():
@staticmethod
def get_response():
return DefaultResultSerializer

class ConversationQuery(APIMixin):
@staticmethod
def get_parameters():
return [
OpenApiParameter(
name="application_id",
description="会话所属应用ID",
type=OpenApiTypes.STR,
location="path",
required=True,
),
OpenApiParameter(
name="current_page",
description="当前页码",
type=OpenApiTypes.INT,
location="path",
required=True,
),
OpenApiParameter(
name="page_size",
description="每页数量",
type=OpenApiTypes.INT,
location="path",
required=True,
),
OpenApiParameter(
name="abstract",
description="会话摘要模糊搜索",
type=OpenApiTypes.STR,
location="query",
required=False,
),
]

@staticmethod
def get_response():
return DefaultResultSerializer
79 changes: 62 additions & 17 deletions apps/chat/serializers/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,24 +138,28 @@ def page(self, current_page, page_size, user_id, with_valid=True):


def get_recent_chats_map(user_id, application_ids, limit=5):
"""批量返回 application_id -> 该应用最近的 limit 条历史会话;show_history=false 的应用不在此表里"""
chats = Chat.objects.filter(chat_user_id=user_id, is_deleted=False, application_id__in=application_ids).order_by(
"application_id", "-update_time", "id"
"""批量返回 application_id -> 该应用最近的 limit 条历史会话及其总数;show_history=false 的应用不在此表里"""
chats = (
Chat.objects.filter(chat_user_id=user_id, is_deleted=False, application_id__in=application_ids)
.order_by("application_id", "-update_time", "id")
.values("application_id", "id", "abstract", "create_time", "update_time")
)
result = {}
chat_map = {}
count_map = {}
for chat in chats:
key = str(chat.application_id)
if len(result.get(key, [])) >= limit:
continue
result.setdefault(key, []).append(
{
"id": str(chat.id),
"abstract": chat.abstract,
"create_time": str(chat.create_time),
"update_time": str(chat.update_time),
}
)
return result
key = str(chat["application_id"])
count_map[key] = count_map.get(key, 0) + 1
recent = chat_map.setdefault(key, [])
if len(recent) < limit:
recent.append(
{
"id": str(chat["id"]),
"abstract": chat["abstract"],
"create_time": str(chat["create_time"]),
"update_time": str(chat["update_time"]),
}
)
return chat_map, count_map


class PortalHistoricalConversationSerializer(serializers.Serializer):
Expand Down Expand Up @@ -192,8 +196,49 @@ def page(self, current_page, page_size, user_id, with_valid=True):
app_ids = [record["id"] for record in result["records"]]
settings_map = get_application_settings_map(app_ids)
show_history_ids = [aid for aid in app_ids if settings_map.get(aid, {}).get("show_history")]
chat_map = get_recent_chats_map(user_id, show_history_ids) if show_history_ids else {}
chat_map, count_map = get_recent_chats_map(user_id, show_history_ids) if show_history_ids else ({}, {})
for record in result["records"]:
record.update(settings_map.get(record["id"], {}))
record["conversations"] = chat_map.get(record["id"], [])
record["count"] = count_map.get(record["id"], 0)
return result


class PortalConversationQuerySerializer(PortalApplicationAuthMixin, serializers.Serializer):
"""门户单个智能体的历史会话分页查询(懒加载/滚动加载),仅返回摘要,支持摘要模糊查询。"""

application_id = serializers.UUIDField(required=True, label=_("Application ID"), help_text=_("Application ID"))
abstract = serializers.CharField(
required=False, allow_blank=True, label=_("Abstract"), help_text=_("Conversation abstract keyword")
)

def _authorize(self, user_id):
application_id = str(self.data.get("application_id"))
authorized_ids = {str(item) for item in self.get_authorized_application_ids(user_id)}
if application_id not in authorized_ids:
raise serializers.ValidationError(_("Application is not authorized"))
if not get_application_settings_map([application_id]).get(application_id, {}).get("show_history"):
raise serializers.ValidationError(_("Application historical conversation is not enabled"))

def page(self, current_page, page_size, user_id, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
self._authorize(user_id)
application_id = self.data.get("application_id")
queryset = Chat.objects.filter(application_id=application_id, chat_user_id=user_id, is_deleted=False).order_by(
"-update_time", "id"
)
abstract = self.data.get("abstract")
if abstract:
queryset = queryset.filter(abstract__icontains=abstract)
return page_search(
current_page,
page_size,
queryset,
post_records_handler=lambda chat: {
"id": str(chat.id),
"abstract": chat.abstract,
"create_time": str(chat.create_time),
"update_time": str(chat.update_time),
},
)
1 change: 1 addition & 0 deletions apps/chat/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
path('api_key/<str:api_key_id>', v3_views.ChatUserApiKeyView.Operate.as_view()),
path('portal/application/<int:current_page>/<int:page_size>', v3_views.PortalApplicationView.as_view(), name='v3_portal_application'),
path('portal/chat/<int:current_page>/<int:page_size>', v3_views.PortalHistoricalConversationView.as_view(), name='v3_portal_historical_conversation'),
path('portal/chat/<str:application_id>/<int:current_page>/<int:page_size>', v3_views.PortalConversationQueryView.as_view(), name='v3_portal_conversation_query'),
path('knowledge/<str:knowledge_id>/retrieve', retrieve_view),
path('knowledge/<str:knowledge_id>/mcp', knowledge_mcp_view),
]
Expand Down
24 changes: 24 additions & 0 deletions apps/chat/views/v3/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from chat.api.portal_api import PortalAPI
from chat.serializers.portal import (
PortalApplicationSerializer,
PortalConversationQuerySerializer,
PortalHistoricalConversationSerializer,
)

Expand Down Expand Up @@ -76,3 +77,26 @@ def get(self, request: Request, current_page: int, page_size: int):
data={**query_params_to_single_dict(request.query_params)}
).page(current_page, page_size, str(request.user.id))
)


class PortalConversationQueryView(APIView):
authentication_classes = [ChatTokenAuth]

@extend_schema(
methods=["GET"],
description=_("Get portal application historical conversation by page"),
summary=_("Get portal application historical conversation by page"),
operation_id=_("Get portal application historical conversation by page"), # type: ignore
parameters=PortalAPI.ConversationQuery.get_parameters(),
responses=PortalAPI.ConversationQuery.get_response(),
tags=[_("V3 Chat")], # type: ignore
)
def get(self, request: Request, application_id: str, current_page: int, page_size: int):
return result.success(
PortalConversationQuerySerializer(
data={
**query_params_to_single_dict(request.query_params),
"application_id": application_id,
}
).page(current_page, page_size, str(request.user.id))
)
9 changes: 9 additions & 0 deletions apps/locales/en_US/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -9747,3 +9747,12 @@ msgstr "Width and height must be in [512, 2048] and the area must not exceed 102

msgid "api_key is required"
msgstr "api_key is required"


#: apps/chat/serializers/portal.py:219
msgid "Application is not authorized"
msgstr "Application is not authorized"

#: apps/chat/serializers/portal.py:221
msgid "Application historical conversation is not enabled"
msgstr "Application historical conversation is not enabled"
9 changes: 9 additions & 0 deletions apps/locales/zh_CN/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -9919,3 +9919,12 @@ msgstr "宽和高必须在 [512, 2048] 之间,且面积不得超过 1024x1024
#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py
msgid "api_key is required"
msgstr "api_key 必填"


#: apps/chat/serializers/portal.py:219
msgid "Application is not authorized"
msgstr "应用未授权"

#: apps/chat/serializers/portal.py:221
msgid "Application historical conversation is not enabled"
msgstr "应用未开启历史记录"
9 changes: 9 additions & 0 deletions apps/locales/zh_Hant/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -9917,3 +9917,12 @@ msgstr "寬和高必須在 [512, 2048] 之間,且面積不得超過 1024x1024
#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py
msgid "api_key is required"
msgstr "api_key 必填"


#: apps/chat/serializers/portal.py:219
msgid "Application is not authorized"
msgstr "應用未授權"

#: apps/chat/serializers/portal.py:221
msgid "Application historical conversation is not enabled"
msgstr "應用未開啟歷史記錄"
8 changes: 8 additions & 0 deletions ui/src/api/chat/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ const postResumeConversationMessage = (applicationId: string, chatId: string, ch
const getConversationPage = (applicationId: string, page: number, size: number) =>
get<ResponsePage<PortalConversationSummary>>(`${getApplicationPath(applicationId)}/chat/${page}/${size}`)

/** 获取门户单个智能体的历史会话分页(懒加载),仅返回摘要,支持摘要模糊搜索。 */
const getPortalConversationPage = (applicationId: string, page: number, size: number, abstract?: string) =>
get<ResponsePage<PortalConversationSummary>>(
`/v3/portal/chat/${applicationId}/${page}/${size}`,
abstract ? { abstract } : undefined,
)

/** 获取单条会话记录详情(含执行详情 execution_details、知识来源、tokens、耗时)。 */
const getConversationRecordDetail = (applicationId: string, chatId: string, chatRecordId: string) =>
get(`${getApplicationPath(applicationId)}/chat/${chatId}/chat_record/${chatRecordId}`)
Expand All @@ -48,6 +55,7 @@ export default {
postCancelConversationMessage,
postResumeConversationMessage,
getConversationPage,
getPortalConversationPage,
getConversationRecordDetail,
getConversationRecordPage,
deleteConversation,
Expand Down
1 change: 1 addition & 0 deletions ui/src/api/types/chat-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export interface PortalApplicationConversations {
name: string
icon: string
conversations: PortalConversationSummary[]
count: number
}
2 changes: 1 addition & 1 deletion ui/src/conversation-panel/view/portal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function createPortalConversation(options: PortalConversationOptions): Po
const portalList = createPortalConversationListStore({
pageApplicationConversations: (page, size) => PortalApi.getPortalConversationPage(page, size),
pageApplications: (page, size, name) => PortalApi.getPortalApplicationPage(page, size, name),
pageGroupConversations: (applicationId, page, size) => ConversationApi.getConversationPage(applicationId, page, size),
pageGroupConversations: (applicationId, page, size) => ConversationApi.getPortalConversationPage(applicationId, page, size),
removeConversation: (applicationId, chatId) => ConversationApi.deleteConversation(applicationId, chatId),
currentApplicationId,
isApplicationListActive,
Expand Down
Loading