feat: 增加对话总结部分
This commit is contained in:
parent
75eaef0514
commit
ca779ca227
|
|
@ -18,13 +18,14 @@ from backend.services.glm_client import GLMClient
|
|||
|
||||
class ChatService:
|
||||
"""Chat completion service with tool support"""
|
||||
|
||||
|
||||
MAX_ITERATIONS = 5
|
||||
|
||||
|
||||
def __init__(self, glm_client: GLMClient):
|
||||
self.glm_client = glm_client
|
||||
self.executor = ToolExecutor(registry=registry)
|
||||
|
||||
|
||||
|
||||
def sync_response(self, conv: Conversation, tools_enabled: bool = True):
|
||||
"""Sync response with tool call support"""
|
||||
tools = registry.list_all() if tools_enabled else None
|
||||
|
|
@ -59,7 +60,7 @@ class ChatService:
|
|||
usage = result.get("usage", {})
|
||||
prompt_tokens = usage.get("prompt_tokens", 0)
|
||||
completion_tokens = usage.get("completion_tokens", 0)
|
||||
|
||||
|
||||
# Create message
|
||||
msg = Message(
|
||||
id=str(uuid.uuid4()),
|
||||
|
|
@ -70,14 +71,27 @@ class ChatService:
|
|||
thinking_content=message.get("reasoning_content", ""),
|
||||
)
|
||||
db.session.add(msg)
|
||||
|
||||
|
||||
# Create tool call records
|
||||
self._save_tool_calls(msg.id, all_tool_calls, all_tool_results)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
user = get_or_create_default_user()
|
||||
record_token_usage(user.id, conv.model, prompt_tokens, completion_tokens)
|
||||
|
||||
|
||||
# Set title if needed (first message)
|
||||
suggested_title = None
|
||||
if not conv.title or conv.title == "新对话":
|
||||
user_msg = Message.query.filter_by(
|
||||
conversation_id=conv.id, role="user"
|
||||
).order_by(Message.created_at.asc()).first()
|
||||
if user_msg and user_msg.content:
|
||||
suggested_title = user_msg.content.strip()[:30]
|
||||
if not suggested_title:
|
||||
suggested_title = "新对话"
|
||||
conv.title = suggested_title
|
||||
db.session.commit()
|
||||
|
||||
return ok({
|
||||
"message": self._message_to_dict(msg),
|
||||
"usage": {
|
||||
|
|
@ -85,6 +99,7 @@ class ChatService:
|
|||
"completion_tokens": completion_tokens,
|
||||
"total_tokens": usage.get("total_tokens", 0)
|
||||
},
|
||||
"suggested_title": suggested_title,
|
||||
})
|
||||
|
||||
# Process tool calls
|
||||
|
|
@ -236,7 +251,8 @@ class ChatService:
|
|||
if full_thinking:
|
||||
yield f"event: process_step\ndata: {json.dumps({'index': step_index, 'type': 'thinking', 'content': full_thinking}, ensure_ascii=False)}\n\n"
|
||||
step_index += 1
|
||||
|
||||
|
||||
suggested_title = None
|
||||
with app.app_context():
|
||||
msg = Message(
|
||||
id=msg_id,
|
||||
|
|
@ -247,15 +263,34 @@ class ChatService:
|
|||
thinking_content=full_thinking,
|
||||
)
|
||||
db.session.add(msg)
|
||||
|
||||
|
||||
# Create tool call records
|
||||
self._save_tool_calls(msg_id, all_tool_calls, all_tool_results)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
user = get_or_create_default_user()
|
||||
record_token_usage(user.id, conv_model, prompt_tokens, token_count)
|
||||
|
||||
yield f"event: done\ndata: {json.dumps({'message_id': msg_id, 'token_count': token_count})}\n\n"
|
||||
|
||||
# Check if we need to set title (first message in conversation)
|
||||
conv = db.session.get(Conversation, conv_id)
|
||||
if conv and (not conv.title or conv.title == "新对话"):
|
||||
# Get user message content
|
||||
user_msg = Message.query.filter_by(
|
||||
conversation_id=conv_id, role="user"
|
||||
).order_by(Message.created_at.asc()).first()
|
||||
if user_msg and user_msg.content:
|
||||
# Use first 30 chars of user message as title
|
||||
suggested_title = user_msg.content.strip()[:30]
|
||||
if not suggested_title:
|
||||
suggested_title = "新对话"
|
||||
# Refresh conv to avoid stale state
|
||||
db.session.refresh(conv)
|
||||
conv.title = suggested_title
|
||||
db.session.commit()
|
||||
else:
|
||||
suggested_title = None
|
||||
|
||||
yield f"event: done\ndata: {json.dumps({'message_id': msg_id, 'token_count': token_count, 'suggested_title': suggested_title}, ensure_ascii=False)}\n\n"
|
||||
return
|
||||
|
||||
yield f"event: error\ndata: {json.dumps({'content': 'exceeded maximum tool call iterations'}, ensure_ascii=False)}\n\n"
|
||||
|
|
|
|||
|
|
@ -298,30 +298,37 @@ async function sendMessage(content) {
|
|||
streamThinking.value = ''
|
||||
streamToolCalls.value = []
|
||||
streamProcessSteps.value = []
|
||||
|
||||
// Update conversation in list (move to top)
|
||||
const idx = conversations.value.findIndex(c => c.id === convId)
|
||||
if (idx > 0) {
|
||||
const [conv] = conversations.value.splice(idx, 1)
|
||||
conv.message_count = (conv.message_count || 0) + 2
|
||||
if (data.suggested_title) {
|
||||
conv.title = data.suggested_title
|
||||
}
|
||||
conversations.value.unshift(conv)
|
||||
} else if (idx === 0) {
|
||||
conversations.value[0].message_count = (conversations.value[0].message_count || 0) + 2
|
||||
}
|
||||
// Auto title: use first message if title is empty
|
||||
if (conversations.value[0] && !conversations.value[0].title) {
|
||||
try {
|
||||
await conversationApi.update(convId, { title: content.slice(0, 30) })
|
||||
conversations.value[0].title = content.slice(0, 30)
|
||||
} catch (_) {}
|
||||
if (data.suggested_title) {
|
||||
conversations.value[0].title = data.suggested_title
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 后台完成,重新加载该对话的消息
|
||||
try {
|
||||
const res = await messageApi.list(convId, null, 50)
|
||||
// 更新对话列表中的消息计数
|
||||
// 更新对话列表中的消息计数和标题
|
||||
const idx = conversations.value.findIndex(c => c.id === convId)
|
||||
if (idx >= 0) {
|
||||
conversations.value[idx].message_count = res.data.items.length
|
||||
// 从服务器获取最新标题
|
||||
if (res.data.items.length > 0) {
|
||||
const convRes = await conversationApi.get(convId)
|
||||
if (convRes.data.title) {
|
||||
conversations.value[idx].title = convRes.data.title
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,7 +297,6 @@ watch(() => props.streaming, (streaming) => {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
.streaming-text {
|
||||
|
|
@ -450,15 +449,6 @@ watch(() => props.streaming, (streaming) => {
|
|||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.process-item.loading.tool_call .process-icon {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.process-content {
|
||||
padding: 12px;
|
||||
background: var(--bg-primary);
|
||||
|
|
|
|||
Loading…
Reference in New Issue