98 lines
4.4 KiB
TypeScript
98 lines
4.4 KiB
TypeScript
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);
|
|
}
|