diff --git a/src/langbot/pkg/provider/runners/localagent.py b/src/langbot/pkg/provider/runners/localagent.py index be7114dd..6c29415e 100644 --- a/src/langbot/pkg/provider/runners/localagent.py +++ b/src/langbot/pkg/provider/runners/localagent.py @@ -123,6 +123,10 @@ class LocalAgentRunner(runner.RequestRunner): use_llm_model = await self.ap.model_mgr.get_model_by_uuid(query.use_llm_model_uuid) + self.ap.logger.debug( + f'localagent req: query={query.query_id} req_messages={req_messages} use_llm_model={query.use_llm_model_uuid}' + ) + if not is_stream: # 非流式输出,直接请求 @@ -235,6 +239,10 @@ class LocalAgentRunner(runner.RequestRunner): req_messages.append(err_msg) + self.ap.logger.debug( + f'localagent req: query={query.query_id} req_messages={req_messages} use_llm_model={query.use_llm_model_uuid}' + ) + if is_stream: tool_calls_map = {} msg_idx = 0 diff --git a/web/src/app/home/pipelines/components/debug-dialog/DebugDialog.tsx b/web/src/app/home/pipelines/components/debug-dialog/DebugDialog.tsx index f5432a16..139267dd 100644 --- a/web/src/app/home/pipelines/components/debug-dialog/DebugDialog.tsx +++ b/web/src/app/home/pipelines/components/debug-dialog/DebugDialog.tsx @@ -12,6 +12,9 @@ import { Image, Plain, At, + Quote, + Voice, + Source, } from '@/app/infra/entities/message'; import { toast } from 'sonner'; import AtBadge from './AtBadge'; @@ -46,6 +49,8 @@ export default function DebugDialog({ const [isUploading, setIsUploading] = useState(false); const [previewImageUrl, setPreviewImageUrl] = useState(''); const [showImagePreview, setShowImagePreview] = useState(false); + const [quotedMessage, setQuotedMessage] = useState(null); + const [hoveredMessageId, setHoveredMessageId] = useState(null); const messagesEndRef = useRef(null); const inputRef = useRef(null); const popoverRef = useRef(null); @@ -285,7 +290,13 @@ export default function DebugDialog({ }; const sendMessage = async () => { - if (!inputValue.trim() && !hasAt && selectedImages.length === 0) return; + if ( + !inputValue.trim() && + !hasAt && + selectedImages.length === 0 && + !quotedMessage + ) + return; if (!isConnected || !wsClientRef.current) { toast.error(t('pipelines.debugDialog.notConnected')); return; @@ -296,6 +307,25 @@ export default function DebugDialog({ const messageChain = []; + // 添加引用消息(如果有) + if (quotedMessage) { + // 获取被引用消息的Source组件以获取message_id + const sourceComponent = quotedMessage.message_chain.find( + (c) => c.type === 'Source', + ) as Source | undefined; + const messageId = sourceComponent + ? sourceComponent.id + : quotedMessage.id; + + messageChain.push({ + type: 'Quote', + id: messageId, + origin: quotedMessage.message_chain.filter( + (c) => c.type !== 'Source', + ), + }); + } + let text_content = inputValue.trim(); if (hasAt) { text_content = ' ' + text_content; @@ -334,9 +364,10 @@ export default function DebugDialog({ } } - // 清空输入框和图片 + // 清空输入框、图片和引用消息 setInputValue(''); setHasAt(false); + setQuotedMessage(null); selectedImages.forEach((img) => URL.revokeObjectURL(img.preview)); setSelectedImages([]); @@ -412,8 +443,53 @@ export default function DebugDialog({ ); } - case 'Voice': - return [语音]; + case 'Voice': { + const voice = component as Voice; + const voiceUrl = voice.url || (voice.base64 ? voice.base64 : ''); + + if (!voiceUrl) { + return [语音]; + } + + return ( +
+
+ + + + + {voice.length && voice.length > 0 && ( + + {voice.length}s + + )} +
+
+ ); + } + + case 'Quote': { + const quote = component as Quote; + return ( +
+
+ {quote.origin?.map((comp, idx) => + renderMessageComponent(comp as MessageChainComponent, idx), + )} +
+
+ ); + } case 'Source': // Source 不显示 @@ -424,6 +500,60 @@ export default function DebugDialog({ } }; + const getMessageTimestamp = (message: Message): number => { + // 首先尝试从message_chain中的Source组件获取时间戳 + const sourceComponent = message.message_chain.find( + (c) => c.type === 'Source', + ) as Source | undefined; + + if (sourceComponent && sourceComponent.timestamp) { + return sourceComponent.timestamp; + } + + // 如果没有Source组件,使用message.timestamp + // 假设timestamp是ISO字符串,转换为Unix时间戳(秒) + if (message.timestamp) { + return Math.floor(new Date(message.timestamp).getTime() / 1000); + } + + return 0; + }; + + const formatTimestamp = (timestamp: number): string => { + if (!timestamp) return ''; + + const date = new Date(timestamp * 1000); + const now = new Date(); + + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + + // 判断是否是今天 + const isToday = now.toDateString() === date.toDateString(); + if (isToday) { + return `${hours}:${minutes}`; + } + + // 判断是否是昨天 + const yesterday = new Date(now); + yesterday.setDate(yesterday.getDate() - 1); + const isYesterday = yesterday.toDateString() === date.toDateString(); + if (isYesterday) { + return `${t('bots.yesterday')} ${hours}:${minutes}`; + } + + // 判断是否是今年 + const isThisYear = now.getFullYear() === date.getFullYear(); + if (isThisYear) { + const month = date.getMonth() + 1; + const day = date.getDate(); + return t('bots.dateFormat', { month, day }); + } + + // 更早的日期 + return t('bots.earlier'); + }; + const renderMessageContent = (message: Message) => { return (
@@ -489,31 +619,68 @@ export default function DebugDialog({
setHoveredMessageId(message.id)} + onMouseLeave={() => setHoveredMessageId(null)} >
- {renderMessageContent(message)}
- {message.role === 'user' - ? t('pipelines.debugDialog.userMessage') - : t('pipelines.debugDialog.botMessage')} + {renderMessageContent(message)} +
+ + {message.role === 'user' + ? t('pipelines.debugDialog.userMessage') + : t('pipelines.debugDialog.botMessage')} + + + {formatTimestamp(getMessageTimestamp(message))} + +
+ {hoveredMessageId === message.id && ( + + )}
)) @@ -522,6 +689,34 @@ export default function DebugDialog({
+ {/* 引用消息预览区域 */} + {quotedMessage && ( +
+
+
+
+ {t('pipelines.debugDialog.replyTo')}{' '} + {quotedMessage.role === 'user' + ? t('pipelines.debugDialog.userMessage') + : t('pipelines.debugDialog.botMessage')} +
+
+ {quotedMessage.message_chain + .filter((c) => c.type === 'Plain') + .map((c) => (c as Plain).text) + .join('')} +
+
+ +
+
+ )} + {/* 图片预览区域 */} {selectedImages.length > 0 && (
@@ -624,7 +819,10 @@ export default function DebugDialog({