feat: 话题添加描述字段,工具消息按 tool_call_id 合并展示
- TopicSummary 新增 description 字段,侧边栏优先显示描述 - ToolPanel 使用 toolCallId 将 tool_call 和 tool_result 配对合并展示 - 保存消息时同步更新 topics 表的 message_count 和 last_active_at - ChatMessage 新增 toolCallId 字段 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
44e82e8473
commit
598d425c28
@ -13,6 +13,7 @@ pub struct TopicSummary {
|
||||
pub topic_id: String,
|
||||
pub session_id: String,
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
pub message_count: i64,
|
||||
pub created_at: i64,
|
||||
pub last_active_at: i64,
|
||||
@ -73,6 +74,7 @@ async fn handle_list_topics(
|
||||
topic_id: t.id,
|
||||
session_id: t.session_id,
|
||||
title: t.title,
|
||||
description: t.description.filter(|d| !d.is_empty()),
|
||||
message_count: t.message_count,
|
||||
created_at: t.created_at,
|
||||
last_active_at: t.last_active_at,
|
||||
|
||||
@ -107,6 +107,7 @@ async fn handle_create_session(
|
||||
topic_id: t.id,
|
||||
session_id: t.session_id,
|
||||
title: t.title,
|
||||
description: t.description.filter(|d| !d.is_empty()),
|
||||
message_count: t.message_count,
|
||||
created_at: t.created_at,
|
||||
last_active_at: t.last_active_at,
|
||||
|
||||
@ -32,6 +32,8 @@ pub struct TopicSummary {
|
||||
pub message_count: i64,
|
||||
pub created_at: i64,
|
||||
pub last_active_at: i64,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@ -579,6 +579,13 @@ impl SessionStore {
|
||||
params![session_id, now, if is_user_message { 1 } else { 0 }],
|
||||
)?;
|
||||
|
||||
if let Some(tid) = topic_id {
|
||||
tx.execute(
|
||||
"UPDATE topics SET message_count = message_count + 1, last_active_at = ?2 WHERE id = ?1",
|
||||
params![tid, now],
|
||||
)?;
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ChevronDown, ChevronRight, Play, Check, AlertTriangle, Terminal } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useState, useMemo } from 'react'
|
||||
import type { ChatMessage } from '../../types/protocol'
|
||||
|
||||
interface ToolPanelProps {
|
||||
@ -7,25 +7,54 @@ interface ToolPanelProps {
|
||||
}
|
||||
|
||||
interface ToolCallItem {
|
||||
id: string
|
||||
toolCallId: string
|
||||
toolName: string
|
||||
status: 'calling' | 'result' | 'pending'
|
||||
arguments?: unknown
|
||||
content: string
|
||||
resultContent: string
|
||||
callContent: string
|
||||
}
|
||||
|
||||
function mergeToolMessages(messages: ChatMessage[]): ToolCallItem[] {
|
||||
const map = new Map<string, ToolCallItem>()
|
||||
|
||||
for (const m of messages) {
|
||||
if (m.role !== 'tool' || !m.type?.startsWith('tool_')) continue
|
||||
|
||||
const key = m.toolCallId || m.id
|
||||
let entry = map.get(key)
|
||||
|
||||
if (!entry) {
|
||||
entry = {
|
||||
toolCallId: key,
|
||||
toolName: m.toolName || 'Unknown',
|
||||
status: 'calling',
|
||||
arguments: undefined,
|
||||
resultContent: '',
|
||||
callContent: '',
|
||||
}
|
||||
map.set(key, entry)
|
||||
}
|
||||
|
||||
if (m.type === 'tool_call') {
|
||||
entry.arguments = m.arguments
|
||||
entry.callContent = m.content
|
||||
} else if (m.type === 'tool_result') {
|
||||
entry.status = 'result'
|
||||
entry.resultContent = m.content
|
||||
} else if (m.type === 'tool_pending') {
|
||||
entry.status = 'pending'
|
||||
entry.resultContent = m.content
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(map.values())
|
||||
}
|
||||
|
||||
export function ToolPanel({ messages }: ToolPanelProps) {
|
||||
const [expandedTools, setExpandedTools] = useState<Set<string>>(new Set())
|
||||
|
||||
const toolCalls: ToolCallItem[] = messages
|
||||
.filter((m) => m.role === 'tool' && m.type && m.type.startsWith('tool_'))
|
||||
.map((m) => ({
|
||||
id: m.id,
|
||||
toolName: m.toolName || 'Unknown',
|
||||
status: m.type === 'tool_call' ? 'calling' : m.type === 'tool_result' ? 'result' : 'pending',
|
||||
arguments: m.arguments,
|
||||
content: m.content,
|
||||
}))
|
||||
const toolCalls = useMemo(() => mergeToolMessages(messages), [messages])
|
||||
|
||||
const toggleExpand = (id: string) => {
|
||||
setExpandedTools((prev) => {
|
||||
@ -42,7 +71,7 @@ export function ToolPanel({ messages }: ToolPanelProps) {
|
||||
const getStatusIcon = (status: ToolCallItem['status']) => {
|
||||
switch (status) {
|
||||
case 'calling':
|
||||
return <Play className="h-3 w-3 text-amber-400" />
|
||||
return <Play className="h-3 w-3 text-amber-400 animate-pulse" />
|
||||
case 'result':
|
||||
return <Check className="h-3 w-3 text-emerald-400" />
|
||||
case 'pending':
|
||||
@ -91,11 +120,11 @@ export function ToolPanel({ messages }: ToolPanelProps) {
|
||||
<div className="space-y-2">
|
||||
{toolCalls.map((tool) => (
|
||||
<div
|
||||
key={tool.id}
|
||||
key={tool.toolCallId}
|
||||
className="rounded-xl border border-white/8 bg-[#1a1a25]/50 text-sm overflow-hidden"
|
||||
>
|
||||
<button
|
||||
onClick={() => toggleExpand(tool.id)}
|
||||
onClick={() => toggleExpand(tool.toolCallId)}
|
||||
className="flex w-full items-center justify-between px-3 py-2.5 hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@ -105,13 +134,13 @@ export function ToolPanel({ messages }: ToolPanelProps) {
|
||||
{getStatusText(tool.status)}
|
||||
</span>
|
||||
</div>
|
||||
{expandedTools.has(tool.id) ? (
|
||||
{expandedTools.has(tool.toolCallId) ? (
|
||||
<ChevronDown className="h-4 w-4 text-zinc-500" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 text-zinc-500" />
|
||||
)}
|
||||
</button>
|
||||
{expandedTools.has(tool.id) && (
|
||||
{expandedTools.has(tool.toolCallId) && (
|
||||
<div className="border-t border-white/8 px-3 py-2 bg-black/20">
|
||||
{tool.arguments ? (
|
||||
<div className="mb-2">
|
||||
@ -122,7 +151,7 @@ export function ToolPanel({ messages }: ToolPanelProps) {
|
||||
<div>
|
||||
<div className="text-xs font-medium text-zinc-500 mb-1">结果:</div>
|
||||
<div className="max-h-32 overflow-y-auto rounded-lg bg-black/40 p-2 text-xs whitespace-pre-wrap text-zinc-400 font-mono">
|
||||
{tool.content}
|
||||
{tool.resultContent || tool.callContent}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -102,7 +102,7 @@ export function TopicList({
|
||||
<div className={`truncate font-medium ${
|
||||
topic.id === currentTopicId ? 'text-[#00f0ff]' : 'text-zinc-300'
|
||||
}`}>
|
||||
{topic.title}
|
||||
{topic.description || topic.title}
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mt-1.5">
|
||||
<span className="text-xs text-zinc-500 flex items-center gap-1">
|
||||
|
||||
@ -125,6 +125,7 @@ export function useChat(): UseChatReturn {
|
||||
id: t.topic_id,
|
||||
session_id: t.session_id,
|
||||
title: t.title,
|
||||
description: t.description || undefined,
|
||||
message_count: Number(t.message_count),
|
||||
created_at: t.created_at,
|
||||
updated_at: t.last_active_at,
|
||||
@ -165,6 +166,7 @@ export function useChat(): UseChatReturn {
|
||||
timestamp: Date.now(),
|
||||
type: 'tool_call',
|
||||
toolName: msg.tool_name,
|
||||
toolCallId: msg.tool_call_id,
|
||||
arguments: msg.arguments,
|
||||
},
|
||||
])
|
||||
@ -182,6 +184,7 @@ export function useChat(): UseChatReturn {
|
||||
timestamp: Date.now(),
|
||||
type: 'tool_result',
|
||||
toolName: msg.tool_name,
|
||||
toolCallId: msg.tool_call_id,
|
||||
},
|
||||
])
|
||||
break
|
||||
@ -198,6 +201,7 @@ export function useChat(): UseChatReturn {
|
||||
timestamp: Date.now(),
|
||||
type: 'tool_pending',
|
||||
toolName: msg.tool_name,
|
||||
toolCallId: msg.tool_call_id,
|
||||
},
|
||||
])
|
||||
break
|
||||
|
||||
@ -123,6 +123,7 @@ export interface TopicSummary {
|
||||
topic_id: string
|
||||
session_id: string
|
||||
title: string
|
||||
description?: string
|
||||
message_count: number
|
||||
created_at: number
|
||||
last_active_at: number
|
||||
@ -249,6 +250,7 @@ export interface ChatMessage {
|
||||
timestamp: number
|
||||
type?: 'message' | 'tool_call' | 'tool_result' | 'tool_pending'
|
||||
toolName?: string
|
||||
toolCallId?: string
|
||||
arguments?: unknown
|
||||
attachments?: Attachment[]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user