feat: 增加对话总结部分
This commit is contained in:
parent
75eaef0514
commit
ca779ca227
|
|
@ -18,13 +18,14 @@ from backend.services.glm_client import GLMClient
|
||||||
|
|
||||||
class ChatService:
|
class ChatService:
|
||||||
"""Chat completion service with tool support"""
|
"""Chat completion service with tool support"""
|
||||||
|
|
||||||
MAX_ITERATIONS = 5
|
MAX_ITERATIONS = 5
|
||||||
|
|
||||||
def __init__(self, glm_client: GLMClient):
|
def __init__(self, glm_client: GLMClient):
|
||||||
self.glm_client = glm_client
|
self.glm_client = glm_client
|
||||||
self.executor = ToolExecutor(registry=registry)
|
self.executor = ToolExecutor(registry=registry)
|
||||||
|
|
||||||
|
|
||||||
def sync_response(self, conv: Conversation, tools_enabled: bool = True):
|
def sync_response(self, conv: Conversation, tools_enabled: bool = True):
|
||||||
"""Sync response with tool call support"""
|
"""Sync response with tool call support"""
|
||||||
tools = registry.list_all() if tools_enabled else None
|
tools = registry.list_all() if tools_enabled else None
|
||||||
|
|
@ -59,7 +60,7 @@ class ChatService:
|
||||||
usage = result.get("usage", {})
|
usage = result.get("usage", {})
|
||||||
prompt_tokens = usage.get("prompt_tokens", 0)
|
prompt_tokens = usage.get("prompt_tokens", 0)
|
||||||
completion_tokens = usage.get("completion_tokens", 0)
|
completion_tokens = usage.get("completion_tokens", 0)
|
||||||
|
|
||||||
# Create message
|
# Create message
|
||||||
msg = Message(
|
msg = Message(
|
||||||
id=str(uuid.uuid4()),
|
id=str(uuid.uuid4()),
|
||||||
|
|
@ -70,14 +71,27 @@ class ChatService:
|
||||||
thinking_content=message.get("reasoning_content", ""),
|
thinking_content=message.get("reasoning_content", ""),
|
||||||
)
|
)
|
||||||
db.session.add(msg)
|
db.session.add(msg)
|
||||||
|
|
||||||
# Create tool call records
|
# Create tool call records
|
||||||
self._save_tool_calls(msg.id, all_tool_calls, all_tool_results)
|
self._save_tool_calls(msg.id, all_tool_calls, all_tool_results)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
user = get_or_create_default_user()
|
user = get_or_create_default_user()
|
||||||
record_token_usage(user.id, conv.model, prompt_tokens, completion_tokens)
|
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({
|
return ok({
|
||||||
"message": self._message_to_dict(msg),
|
"message": self._message_to_dict(msg),
|
||||||
"usage": {
|
"usage": {
|
||||||
|
|
@ -85,6 +99,7 @@ class ChatService:
|
||||||
"completion_tokens": completion_tokens,
|
"completion_tokens": completion_tokens,
|
||||||
"total_tokens": usage.get("total_tokens", 0)
|
"total_tokens": usage.get("total_tokens", 0)
|
||||||
},
|
},
|
||||||
|
"suggested_title": suggested_title,
|
||||||
})
|
})
|
||||||
|
|
||||||
# Process tool calls
|
# Process tool calls
|
||||||
|
|
@ -236,7 +251,8 @@ class ChatService:
|
||||||
if full_thinking:
|
if full_thinking:
|
||||||
yield f"event: process_step\ndata: {json.dumps({'index': step_index, 'type': 'thinking', 'content': full_thinking}, ensure_ascii=False)}\n\n"
|
yield f"event: process_step\ndata: {json.dumps({'index': step_index, 'type': 'thinking', 'content': full_thinking}, ensure_ascii=False)}\n\n"
|
||||||
step_index += 1
|
step_index += 1
|
||||||
|
|
||||||
|
suggested_title = None
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
msg = Message(
|
msg = Message(
|
||||||
id=msg_id,
|
id=msg_id,
|
||||||
|
|
@ -247,15 +263,34 @@ class ChatService:
|
||||||
thinking_content=full_thinking,
|
thinking_content=full_thinking,
|
||||||
)
|
)
|
||||||
db.session.add(msg)
|
db.session.add(msg)
|
||||||
|
|
||||||
# Create tool call records
|
# Create tool call records
|
||||||
self._save_tool_calls(msg_id, all_tool_calls, all_tool_results)
|
self._save_tool_calls(msg_id, all_tool_calls, all_tool_results)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
user = get_or_create_default_user()
|
user = get_or_create_default_user()
|
||||||
record_token_usage(user.id, conv_model, prompt_tokens, token_count)
|
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
|
return
|
||||||
|
|
||||||
yield f"event: error\ndata: {json.dumps({'content': 'exceeded maximum tool call iterations'}, ensure_ascii=False)}\n\n"
|
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 = ''
|
streamThinking.value = ''
|
||||||
streamToolCalls.value = []
|
streamToolCalls.value = []
|
||||||
streamProcessSteps.value = []
|
streamProcessSteps.value = []
|
||||||
|
|
||||||
// Update conversation in list (move to top)
|
// Update conversation in list (move to top)
|
||||||
const idx = conversations.value.findIndex(c => c.id === convId)
|
const idx = conversations.value.findIndex(c => c.id === convId)
|
||||||
if (idx > 0) {
|
if (idx > 0) {
|
||||||
const [conv] = conversations.value.splice(idx, 1)
|
const [conv] = conversations.value.splice(idx, 1)
|
||||||
conv.message_count = (conv.message_count || 0) + 2
|
conv.message_count = (conv.message_count || 0) + 2
|
||||||
|
if (data.suggested_title) {
|
||||||
|
conv.title = data.suggested_title
|
||||||
|
}
|
||||||
conversations.value.unshift(conv)
|
conversations.value.unshift(conv)
|
||||||
} else if (idx === 0) {
|
} else if (idx === 0) {
|
||||||
conversations.value[0].message_count = (conversations.value[0].message_count || 0) + 2
|
conversations.value[0].message_count = (conversations.value[0].message_count || 0) + 2
|
||||||
}
|
if (data.suggested_title) {
|
||||||
// Auto title: use first message if title is empty
|
conversations.value[0].title = data.suggested_title
|
||||||
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 (_) {}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 后台完成,重新加载该对话的消息
|
// 后台完成,重新加载该对话的消息
|
||||||
try {
|
try {
|
||||||
const res = await messageApi.list(convId, null, 50)
|
const res = await messageApi.list(convId, null, 50)
|
||||||
// 更新对话列表中的消息计数
|
// 更新对话列表中的消息计数和标题
|
||||||
const idx = conversations.value.findIndex(c => c.id === convId)
|
const idx = conversations.value.findIndex(c => c.id === convId)
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
conversations.value[idx].message_count = res.data.items.length
|
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 (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,6 @@ watch(() => props.streaming, (streaming) => {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
animation: spin 2s linear infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.streaming-text {
|
.streaming-text {
|
||||||
|
|
@ -450,15 +449,6 @@ watch(() => props.streaming, (streaming) => {
|
||||||
background: var(--bg-hover);
|
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 {
|
.process-content {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue