import type { ChatMessage } from '../../../types/bot'; import { normalizeAssistantMessageText, normalizeUserMessageText } from '../../../shared/text/messageText'; import { normalizeAttachmentPaths } from '../../../shared/workspace/utils'; import { normalizeDashboardAttachmentPath } from '../../../shared/workspace/workspaceMarkdown'; export function formatClock(ts: number) { const d = new Date(ts); const hh = String(d.getHours()).padStart(2, '0'); const mm = String(d.getMinutes()).padStart(2, '0'); const ss = String(d.getSeconds()).padStart(2, '0'); return `${hh}:${mm}:${ss}`; } export function formatConversationDate(ts: number, isZh: boolean) { const d = new Date(ts); try { return d.toLocaleDateString(isZh ? 'zh-CN' : 'en-US', { year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'short', }); } catch { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${y}-${m}-${day}`; } } export function formatDateInputValue(ts: number): string { const d = new Date(ts); if (Number.isNaN(d.getTime())) return ''; const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } export function mapBotMessageResponseRow(row: any): ChatMessage { const roleRaw = String(row?.role || '').toLowerCase(); const role: ChatMessage['role'] = roleRaw === 'user' || roleRaw === 'assistant' || roleRaw === 'system' ? roleRaw : 'assistant'; const feedbackRaw = String(row?.feedback || '').trim().toLowerCase(); const feedback: ChatMessage['feedback'] = feedbackRaw === 'up' || feedbackRaw === 'down' ? feedbackRaw : null; return { id: Number.isFinite(Number(row?.id)) ? Number(row.id) : undefined, role, text: String(row?.text || ''), attachments: normalizeAttachmentPaths(row?.media), ts: Number(row?.ts || Date.now()), feedback, kind: 'final', } as ChatMessage; } export function parseQuotedReplyBlock(input: string): { quoted: string; body: string } { const source = String(input || ''); const match = source.match(/\[Quoted Reply\]\s*([\s\S]*?)\s*\[\/Quoted Reply\]/i); const quoted = normalizeAssistantMessageText(match?.[1] || ''); const body = source.replace(/\[Quoted Reply\][\s\S]*?\[\/Quoted Reply\]\s*/gi, '').trim(); return { quoted, body }; } export function mergeConversation(messages: ChatMessage[]) { const merged: ChatMessage[] = []; messages .filter((msg) => msg.role !== 'system' && (msg.text.trim().length > 0 || (msg.attachments || []).length > 0)) .forEach((msg) => { const parsedUser = msg.role === 'user' ? parseQuotedReplyBlock(msg.text) : { quoted: '', body: msg.text }; const userQuoted = parsedUser.quoted; const userBody = parsedUser.body; const cleanText = msg.role === 'user' ? normalizeUserMessageText(userBody) : normalizeAssistantMessageText(msg.text); const attachments = normalizeAttachmentPaths(msg.attachments).map(normalizeDashboardAttachmentPath).filter(Boolean); if (!cleanText && attachments.length === 0 && !userQuoted) return; const last = merged[merged.length - 1]; if (last && last.role === msg.role) { const normalizedLast = last.role === 'user' ? normalizeUserMessageText(last.text) : normalizeAssistantMessageText(last.text); const normalizedCurrent = msg.role === 'user' ? normalizeUserMessageText(cleanText) : normalizeAssistantMessageText(cleanText); const lastKind = last.kind || 'final'; const currentKind = msg.kind || 'final'; const sameAttachmentSet = JSON.stringify(normalizeAttachmentPaths(last.attachments)) === JSON.stringify(attachments); const sameQuoted = normalizeAssistantMessageText(last.quoted_reply || '') === normalizeAssistantMessageText(userQuoted); if (lastKind === currentKind && normalizedLast === normalizedCurrent && sameAttachmentSet && sameQuoted && Math.abs(msg.ts - last.ts) < 15000) { last.ts = msg.ts; last.id = msg.id || last.id; if (typeof msg.feedback !== 'undefined') { last.feedback = msg.feedback; } return; } } merged.push({ ...msg, text: cleanText, quoted_reply: userQuoted || undefined, attachments }); }); return merged.slice(-120); }