chore: rebuild release package
This commit is contained in:
155
public/app.js
155
public/app.js
@@ -226,6 +226,7 @@
|
||||
let loginPasswordValue = ''; // store login password for force-change flow
|
||||
let currentCwd = null;
|
||||
let currentSessionMessageCount = 0;
|
||||
let currentOutlineTitleHistory = [];
|
||||
let currentSessionRunning = false;
|
||||
let fileBrowserState = null;
|
||||
let directoryPickerState = null;
|
||||
@@ -1452,15 +1453,97 @@
|
||||
userMessageIndex.clear();
|
||||
}
|
||||
|
||||
function registerUserMessage(messageId, element, content) {
|
||||
function registerUserMessage(messageId, element, content, timestamp = '') {
|
||||
if (!messageId || !element) return;
|
||||
const previous = userMessageIndex.get(messageId);
|
||||
userMessageIndex.set(messageId, {
|
||||
id: messageId,
|
||||
element,
|
||||
content: String(content || ''),
|
||||
timestamp: String(timestamp || previous?.timestamp || ''),
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeOutlineTitleHistory(history) {
|
||||
if (!Array.isArray(history)) return [];
|
||||
const normalized = [];
|
||||
history.forEach((event) => {
|
||||
if (!event || typeof event !== 'object') return;
|
||||
const title = String(event.title || '').replace(/\s+/g, ' ').trim().slice(0, 120);
|
||||
const changedAtMs = Date.parse(String(event.changedAt || ''));
|
||||
const messageIndex = Number(event.messageIndex);
|
||||
const source = String(event.source || '').trim().toLowerCase();
|
||||
if (!title || !Number.isFinite(changedAtMs) || !Number.isFinite(messageIndex) || messageIndex < 0 || source !== 'llm') return;
|
||||
normalized.push({
|
||||
title,
|
||||
changedAt: new Date(changedAtMs).toISOString(),
|
||||
messageIndex: Math.trunc(messageIndex),
|
||||
source: 'llm',
|
||||
});
|
||||
});
|
||||
return normalized.slice(-100);
|
||||
}
|
||||
|
||||
function formatUserOutlineDate(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
if (Number.isNaN(date.getTime())) return '';
|
||||
const pad = (value) => String(value).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
||||
}
|
||||
|
||||
function buildUserOutlineTimelineItems(messageItems, titleHistory = []) {
|
||||
const timeline = [];
|
||||
(Array.isArray(messageItems) ? messageItems : []).forEach((message, order) => {
|
||||
const messageIndex = Number(message.messageIndex);
|
||||
const timestampMs = Date.parse(String(message.timestamp || ''));
|
||||
timeline.push({
|
||||
...message,
|
||||
type: 'message',
|
||||
messageNumber: order + 1,
|
||||
timestamp: String(message.timestamp || ''),
|
||||
sortPosition: Number.isFinite(messageIndex) && messageIndex >= 0 ? messageIndex * 2 : null,
|
||||
sortTime: Number.isFinite(timestampMs) ? timestampMs : null,
|
||||
sortOrder: order,
|
||||
});
|
||||
});
|
||||
normalizeOutlineTitleHistory(titleHistory).forEach((event, order) => {
|
||||
const timestampMs = Date.parse(event.changedAt);
|
||||
timeline.push({
|
||||
type: 'title',
|
||||
label: event.title,
|
||||
timestamp: event.changedAt,
|
||||
messageIndex: event.messageIndex,
|
||||
source: event.source,
|
||||
sortPosition: event.messageIndex * 2 - 1,
|
||||
sortTime: Number.isFinite(timestampMs) ? timestampMs : null,
|
||||
sortOrder: order,
|
||||
});
|
||||
});
|
||||
timeline.sort((left, right) => {
|
||||
if (Number.isFinite(left.sortPosition) && Number.isFinite(right.sortPosition) && left.sortPosition !== right.sortPosition) {
|
||||
return left.sortPosition - right.sortPosition;
|
||||
}
|
||||
if (Number.isFinite(left.sortTime) && Number.isFinite(right.sortTime) && left.sortTime !== right.sortTime) {
|
||||
return left.sortTime - right.sortTime;
|
||||
}
|
||||
if (left.type !== right.type) return left.type === 'message' ? -1 : 1;
|
||||
return left.sortOrder - right.sortOrder;
|
||||
});
|
||||
|
||||
const result = [];
|
||||
let previousDate = '';
|
||||
timeline.forEach((entry) => {
|
||||
const dateLabel = formatUserOutlineDate(entry.timestamp);
|
||||
if (dateLabel && dateLabel !== previousDate) {
|
||||
result.push({ type: 'date', label: dateLabel });
|
||||
previousDate = dateLabel;
|
||||
}
|
||||
const { sortPosition, sortTime, sortOrder, ...item } = entry;
|
||||
result.push(item);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function buildUserOutlineItems() {
|
||||
const seen = new Set();
|
||||
return Array.from(messagesDiv.querySelectorAll('.msg.user[data-message-id]')).map((element) => {
|
||||
@@ -1469,27 +1552,47 @@
|
||||
seen.add(id);
|
||||
const indexed = userMessageIndex.get(id);
|
||||
const content = indexed?.content || element.querySelector('.msg-text')?.textContent || '';
|
||||
const rawMessageIndex = String(element.dataset.messageIndex || '').trim();
|
||||
const messageIndex = rawMessageIndex ? Number(rawMessageIndex) : Number.NaN;
|
||||
return {
|
||||
id,
|
||||
targetMessageId: element.id || '',
|
||||
label: shortMessagePreview(content, 64),
|
||||
timestamp: indexed?.timestamp || '',
|
||||
messageIndex: Number.isFinite(messageIndex) ? messageIndex : null,
|
||||
};
|
||||
}).filter((entry) => entry && entry.targetMessageId);
|
||||
}
|
||||
|
||||
function updateUserOutlinePanel() {
|
||||
if (!userOutlinePanel || !userOutlineBtn) return;
|
||||
const items = buildUserOutlineItems();
|
||||
const items = buildUserOutlineTimelineItems(buildUserOutlineItems(), currentOutlineTitleHistory);
|
||||
if (items.length === 0) {
|
||||
userOutlinePanel.innerHTML = '<div class="user-outline-empty">暂无用户消息</div>';
|
||||
userOutlineBtn.disabled = true;
|
||||
} else {
|
||||
userOutlinePanel.innerHTML = items.map((item, index) => `
|
||||
<button type="button" class="user-outline-item" data-target="${escapeHtml(item.targetMessageId)}">
|
||||
<span class="user-outline-index">${index + 1}</span>
|
||||
<span class="user-outline-text">${escapeHtml(item.label)}</span>
|
||||
</button>
|
||||
`).join('');
|
||||
userOutlinePanel.innerHTML = items.map((item) => {
|
||||
if (item.type === 'date') {
|
||||
return `<div class="user-outline-date" role="separator" aria-label="${escapeHtml(item.label)}"><span>${escapeHtml(item.label)}</span></div>`;
|
||||
}
|
||||
if (item.type === 'title') {
|
||||
return `
|
||||
<div class="user-outline-title-event" role="note" aria-label="标题变更:${escapeHtml(item.label)}">
|
||||
<span class="user-outline-title-label">标题</span>
|
||||
<span class="user-outline-title-text">${escapeHtml(item.label)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (item.type === 'message') {
|
||||
return `
|
||||
<button type="button" class="user-outline-item" data-target="${escapeHtml(item.targetMessageId)}">
|
||||
<span class="user-outline-index">${item.messageNumber}</span>
|
||||
<span class="user-outline-text">${escapeHtml(item.label)}</span>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
return '';
|
||||
}).join('');
|
||||
userOutlineBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
@@ -1848,6 +1951,7 @@
|
||||
agent: snapshot.agent || '',
|
||||
cwd: snapshot.cwd || '',
|
||||
updated: snapshot.updated || '',
|
||||
titleHistory: normalizeOutlineTitleHistory(snapshot.titleHistory),
|
||||
}).length;
|
||||
return base + (snapshot.messages || []).reduce((sum, message) => sum + estimateSessionMessageWeight(message), 0);
|
||||
}
|
||||
@@ -1866,6 +1970,7 @@
|
||||
id: sessionId,
|
||||
messages,
|
||||
title: payload.title || '新会话',
|
||||
titleHistory: normalizeOutlineTitleHistory(payload.titleHistory),
|
||||
mode: payload.mode || 'yolo',
|
||||
model: payload.model || '',
|
||||
agent: normalizeAgent(payload.agent),
|
||||
@@ -3903,6 +4008,15 @@
|
||||
return cwd ? (getPathLeaf(cwd) || cwd) : '';
|
||||
}
|
||||
|
||||
function buildSessionItemTooltip(projectName, title) {
|
||||
const normalizedProjectName = String(projectName || '').trim();
|
||||
const normalizedTitle = String(title || '').trim() || 'Untitled';
|
||||
return [
|
||||
normalizedProjectName ? `项目:${normalizedProjectName}` : '',
|
||||
`标题:${normalizedTitle}`,
|
||||
].filter(Boolean).join('\n');
|
||||
}
|
||||
|
||||
function groupSessionsByProject(sessionItems) {
|
||||
const groups = [];
|
||||
const groupMap = new Map();
|
||||
@@ -4059,8 +4173,8 @@
|
||||
const waitingLabel = readyReplyCount > 0 ? `子对话已返回 ${readyReplyCount}` : `等待子对话 ${Number(session.pendingReplyCount || 0) || ''}`.trim();
|
||||
item.className = `session-item${session.id === currentSessionId ? ' active' : ''}${isPinned ? ' pinned' : ''}${isLlmCreated ? ' llm-created' : ''}${waitingOnChildren ? ' waiting-children' : ''}`;
|
||||
item.dataset.id = session.id;
|
||||
const sessionCwd = getSessionEffectiveCwd(session);
|
||||
if (sessionCwd) item.title = sessionCwd;
|
||||
const sessionProjectName = getSessionProjectName(session);
|
||||
item.title = buildSessionItemTooltip(sessionProjectName, session.title);
|
||||
item.innerHTML = `
|
||||
<div class="session-item-main">
|
||||
<span class="session-item-title">${escapeHtml(session.title || 'Untitled')}</span>
|
||||
@@ -4254,6 +4368,7 @@
|
||||
currentSessionId = null;
|
||||
loadedHistorySessionId = null;
|
||||
currentSessionMessageCount = 0;
|
||||
currentOutlineTitleHistory = [];
|
||||
clearSessionLoading();
|
||||
setCurrentSessionRunningState(false);
|
||||
currentCwd = null;
|
||||
@@ -4311,6 +4426,7 @@
|
||||
}
|
||||
currentSessionId = snapshot.sessionId;
|
||||
loadedHistorySessionId = snapshot.sessionId;
|
||||
currentOutlineTitleHistory = normalizeOutlineTitleHistory(snapshot.titleHistory);
|
||||
currentSessionMessageCount = Math.max(
|
||||
snapshot.historyTotal || 0,
|
||||
snapshot.historyBaseIndex + (snapshot.messages || []).length,
|
||||
@@ -5462,9 +5578,16 @@
|
||||
snapshot.title = msg.title;
|
||||
if (msg.titleSource !== undefined) snapshot.titleSource = msg.titleSource || null;
|
||||
if (msg.createdFromKind !== undefined) snapshot.createdFromKind = msg.createdFromKind || null;
|
||||
if (msg.titleEvent) {
|
||||
snapshot.titleHistory = normalizeOutlineTitleHistory([...(snapshot.titleHistory || []), msg.titleEvent]);
|
||||
}
|
||||
});
|
||||
if (msg.sessionId === currentSessionId) {
|
||||
chatTitle.textContent = msg.title;
|
||||
if (msg.titleEvent) {
|
||||
currentOutlineTitleHistory = normalizeOutlineTitleHistory([...currentOutlineTitleHistory, msg.titleEvent]);
|
||||
updateUserOutlinePanel();
|
||||
}
|
||||
}
|
||||
renderSessionList();
|
||||
break;
|
||||
@@ -6315,7 +6438,7 @@
|
||||
setCodexAppSteerStatusElement(div, meta.codexAppSteerStatus, meta.codexAppSteerMessage);
|
||||
}
|
||||
if (role === 'user') {
|
||||
registerUserMessage(resolvedMessageId, div, content);
|
||||
registerUserMessage(resolvedMessageId, div, content, meta.timestamp);
|
||||
}
|
||||
return div;
|
||||
}
|
||||
@@ -9086,14 +9209,15 @@
|
||||
const welcome = messagesDiv.querySelector('.welcome-msg');
|
||||
if (welcome) welcome.remove();
|
||||
const messageId = createLocalId('user');
|
||||
const element = createMsgElement('user', text, attachments, { messageId });
|
||||
const timestamp = new Date().toISOString();
|
||||
const element = createMsgElement('user', text, attachments, { messageId, timestamp });
|
||||
messagesDiv.appendChild(element);
|
||||
if (currentSessionId) {
|
||||
const messageIndex = currentSessionMessageCount;
|
||||
currentSessionMessageCount += 1;
|
||||
markSessionMessageElement(element, messageIndex);
|
||||
}
|
||||
registerUserMessage(messageId, element, text);
|
||||
registerUserMessage(messageId, element, text, timestamp);
|
||||
updateUserOutlinePanel();
|
||||
scrollToBottom();
|
||||
|
||||
@@ -9131,7 +9255,8 @@
|
||||
}
|
||||
const attachments = pendingAttachments.map((attachment) => ({ ...attachment }));
|
||||
const messageId = createLocalId('user');
|
||||
const element = createMsgElement('user', text, attachments, { messageId, codexAppSteerStatus: 'pending' });
|
||||
const timestamp = new Date().toISOString();
|
||||
const element = createMsgElement('user', text, attachments, { messageId, timestamp, codexAppSteerStatus: 'pending' });
|
||||
const streamEl = document.getElementById('streaming-msg');
|
||||
const shouldFollow = isNearBottom();
|
||||
if (streamEl && streamEl.parentNode === messagesDiv) {
|
||||
@@ -9144,7 +9269,7 @@
|
||||
currentSessionMessageCount += 1;
|
||||
markSessionMessageElement(element, messageIndex);
|
||||
}
|
||||
registerUserMessage(messageId, element, text);
|
||||
registerUserMessage(messageId, element, text, timestamp);
|
||||
updateUserOutlinePanel();
|
||||
if (shouldFollow) {
|
||||
scrollToBottom();
|
||||
|
||||
Reference in New Issue
Block a user