mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-12 04:15:25 +00:00
bug fix:统一时间戳逻辑
This commit is contained in:
parent
569e83fcbf
commit
9a7764c5d7
1 changed files with 101 additions and 60 deletions
|
|
@ -765,67 +765,102 @@ const fragmentsToContent = (fragments: any) => {
|
||||||
.join('\n\n');
|
.join('\n\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseDeepseekTimestamp = (value: any): number | null => {
|
||||||
|
// DeepSeek exports often use ISO strings; convert to Unix seconds
|
||||||
|
if (value === undefined || value === null) return null;
|
||||||
|
if (typeof value === 'number') return value;
|
||||||
|
const parsed = Date.parse(value);
|
||||||
|
if (Number.isNaN(parsed)) return null;
|
||||||
|
return Math.floor(parsed / 1000);
|
||||||
|
};
|
||||||
|
|
||||||
const convertDeepseekMessages = (convo) => {
|
const convertDeepseekMessages = (convo) => {
|
||||||
// Parse DeepSeek chat messages (mapping + fragments) into chat dictionary
|
|
||||||
const mapping = convo['mapping'];
|
const mapping = convo['mapping'];
|
||||||
const messages = [];
|
const messages = [];
|
||||||
let currentId = '';
|
// 即使缺少这个函数,这里给一个简单的补丁以防报错
|
||||||
let lastId = null;
|
const fragmentsToContent = (frags) => frags?.map(f => f.content).join('') || '';
|
||||||
|
const createdAt = parseDeepseekTimestamp(convo['create_time'] || convo['created_at']);
|
||||||
|
const updatedAt = parseDeepseekTimestamp(
|
||||||
|
convo['updated_at'] || convo['inserted_at'] || convo['create_time']
|
||||||
|
);
|
||||||
|
|
||||||
for (const message_id in mapping) {
|
// 1. 先把所有节点处理一遍,存入字典,不管顺序
|
||||||
const message = mapping[message_id];
|
const rawNodes = {};
|
||||||
currentId = message_id;
|
|
||||||
try {
|
|
||||||
const fragments = message?.message?.fragments;
|
|
||||||
const content = fragmentsToContent(fragments);
|
|
||||||
|
|
||||||
if (messages.length === 0 && (!content || content === '')) {
|
Object.keys(mapping).forEach(id => {
|
||||||
continue;
|
const node = mapping[id];
|
||||||
}
|
const fragments = node?.message?.fragments;
|
||||||
|
const content = fragments ? fragmentsToContent(fragments) : '';
|
||||||
|
|
||||||
const inferredRole = (() => {
|
// 推断角色
|
||||||
|
let inferredRole = 'user';
|
||||||
if (Array.isArray(fragments)) {
|
if (Array.isArray(fragments)) {
|
||||||
const firstType = fragments.find((f) => typeof f?.type === 'string')?.type;
|
const firstType = fragments.find((f) => typeof f?.type === 'string')?.type;
|
||||||
if (firstType === 'REQUEST') return 'user';
|
if (firstType === 'RESPONSE') inferredRole = 'assistant';
|
||||||
if (firstType === 'RESPONSE') return 'assistant';
|
} else if (node?.message?.author?.role !== 'user') {
|
||||||
|
inferredRole = 'assistant';
|
||||||
}
|
}
|
||||||
return message?.message?.author?.role !== 'user' ? 'assistant' : 'user';
|
|
||||||
})();
|
|
||||||
|
|
||||||
const new_chat = {
|
rawNodes[id] = {
|
||||||
id: message_id,
|
id: id,
|
||||||
parentId: lastId,
|
originalParent: node.parent, // 保留原始 parent
|
||||||
childrenIds: message['children'] || [],
|
childrenIds: node.children || [],
|
||||||
role: inferredRole,
|
role: inferredRole,
|
||||||
content,
|
content: content,
|
||||||
model: message?.message?.model || 'deepseek-chat',
|
model: node?.message?.model || 'deepseek-chat',
|
||||||
|
hasContent: !!content && content.trim() !== ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 按照父子关系重建顺序 (拓扑排序的简化版:顺藤摸瓜)
|
||||||
|
// 找到真正的 Root (通常是 message 为 null 的那个)
|
||||||
|
let currentNodeId = Object.keys(rawNodes).find(id => !rawNodes[id].originalParent);
|
||||||
|
|
||||||
|
// 如果找不到 null parent,尝试找 'root'
|
||||||
|
if (!currentNodeId && rawNodes['root']) currentNodeId = 'root';
|
||||||
|
|
||||||
|
let lastValidId = null; // 用于记录上一个有效的内容节点,作为 parentId
|
||||||
|
|
||||||
|
// 3. 链式遍历
|
||||||
|
while (currentNodeId) {
|
||||||
|
const node = rawNodes[currentNodeId];
|
||||||
|
if (!node) break;
|
||||||
|
|
||||||
|
// 只有当有内容时才加入 messages 列表
|
||||||
|
if (node.hasContent) {
|
||||||
|
messages.push({
|
||||||
|
id: node.id,
|
||||||
|
parentId: lastValidId, // 指向上一个有效节点,而不是原始的空 root
|
||||||
|
childrenIds: node.childrenIds,
|
||||||
|
role: node.role,
|
||||||
|
content: node.content,
|
||||||
|
model: node.model,
|
||||||
done: true,
|
done: true,
|
||||||
context: null
|
context: null
|
||||||
};
|
});
|
||||||
messages.push(new_chat);
|
lastValidId = node.id; // 更新有效节点指针
|
||||||
lastId = currentId;
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Error with DeepSeek message', message, '\nError:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const history: Record<PropertyKey, (typeof messages)[number]> = {};
|
// 移动到下一个节点 (这里假设是单线性对话,取第一个 child)
|
||||||
|
// DeepSeek 的对话通常是线性的,如果有分支,这里需要更复杂的树形处理
|
||||||
|
currentNodeId = node.childrenIds[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const history = {};
|
||||||
messages.forEach((obj) => (history[obj.id] = obj));
|
messages.forEach((obj) => (history[obj.id] = obj));
|
||||||
|
|
||||||
const chat = {
|
return {
|
||||||
history: {
|
history: {
|
||||||
currentId: currentId,
|
currentId: lastValidId, // 最后的 ID
|
||||||
messages: history
|
messages: history
|
||||||
},
|
},
|
||||||
models: [messages[0]?.model || 'deepseek-chat'],
|
models: [messages[0]?.model || 'deepseek-chat'],
|
||||||
messages: messages,
|
messages: messages,
|
||||||
options: {},
|
options: {},
|
||||||
timestamp: convo['inserted_at'] || convo['updated_at'] || convo['create_time'],
|
timestamp: updatedAt ?? createdAt ?? Math.floor(Date.now() / 1000),
|
||||||
title: convo['title'] ?? 'New Chat'
|
title: convo['title'] ?? 'New Chat'
|
||||||
};
|
};
|
||||||
return chat;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateChat = (chat) => {
|
const validateChat = (chat) => {
|
||||||
// Because ChatGPT sometimes has features we can't use like DALL-E or might have corrupted messages, need to validate
|
// Because ChatGPT sometimes has features we can't use like DALL-E or might have corrupted messages, need to validate
|
||||||
const messages = chat.messages;
|
const messages = chat.messages;
|
||||||
|
|
@ -886,6 +921,10 @@ export const convertDeepseekChats = (_chats) => {
|
||||||
|
|
||||||
for (const convo of _chats) {
|
for (const convo of _chats) {
|
||||||
const chat = convertDeepseekMessages(convo);
|
const chat = convertDeepseekMessages(convo);
|
||||||
|
const createdAt = parseDeepseekTimestamp(convo['create_time'] || convo['created_at']);
|
||||||
|
const updatedAt = parseDeepseekTimestamp(
|
||||||
|
convo['updated_at'] || convo['inserted_at'] || convo['create_time']
|
||||||
|
);
|
||||||
|
|
||||||
if (validateChat(chat)) {
|
if (validateChat(chat)) {
|
||||||
chats.push({
|
chats.push({
|
||||||
|
|
@ -893,7 +932,9 @@ export const convertDeepseekChats = (_chats) => {
|
||||||
user_id: '',
|
user_id: '',
|
||||||
title: convo['title'],
|
title: convo['title'],
|
||||||
chat: chat,
|
chat: chat,
|
||||||
timestamp: convo['inserted_at'] || convo['updated_at'] || convo['create_time']
|
timestamp: updatedAt ?? createdAt ?? null,
|
||||||
|
created_at: createdAt ?? null,
|
||||||
|
updated_at: updatedAt ?? null
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
failed++;
|
failed++;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue