chore: rebuild release package
This commit is contained in:
188
public/app.js
188
public/app.js
@@ -161,6 +161,19 @@
|
||||
desc: '炭黑底配低饱和玫瑰色,暗色里保留一点温度。',
|
||||
swatches: ['#151112', '#2a2022', '#e68193', '#67c587'],
|
||||
},
|
||||
{
|
||||
value: 'gilded',
|
||||
label: 'Warframe Wasteland',
|
||||
desc: '暖象牙与焦土铜铺开 Orokin 荒野档案,冷青能量只作细节点缀。',
|
||||
swatches: ['#fbf3e5', '#efe4d2', '#7a3f20', '#147276'],
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
value: 'wasteland',
|
||||
label: '暗金荒野',
|
||||
desc: '黑铁面板悬浮于荒野骑士主视觉之上,以旧金、余烬与状态绿标记工作层级。',
|
||||
swatches: ['#050707', '#171511', '#c49a5a', '#6f783f'],
|
||||
},
|
||||
];
|
||||
|
||||
// --- State ---
|
||||
@@ -310,9 +323,39 @@
|
||||
window.addEventListener('resize', setVH);
|
||||
window.addEventListener('orientationchange', () => setTimeout(setVH, 100));
|
||||
|
||||
function buildWelcomeMarkup(agent) {
|
||||
const label = AGENT_LABELS[agent] || AGENT_LABELS.claude;
|
||||
return `<div class="welcome-msg"><div class="welcome-icon">✿</div><h3>欢迎使用 CC-Web</h3><p>开始与 ${label} 对话</p></div>`;
|
||||
function getWelcomeProjectName(cwd = currentCwd) {
|
||||
return getPathLeaf(cwd) || '当前项目';
|
||||
}
|
||||
|
||||
function getWelcomeCopy(cwd = currentCwd) {
|
||||
return `你正在操作 ${getWelcomeProjectName(cwd)}`;
|
||||
}
|
||||
|
||||
function syncWelcomeCopy(cwd = currentCwd) {
|
||||
const copy = messagesDiv.querySelector('[data-welcome-project-copy]');
|
||||
if (copy) copy.textContent = getWelcomeCopy(cwd);
|
||||
}
|
||||
|
||||
function buildWelcomeMarkup(cwd = currentCwd) {
|
||||
return `<div class="welcome-msg">
|
||||
<section class="warframe-hero" aria-labelledby="warframe-welcome-title">
|
||||
<div class="warframe-hero-art" aria-hidden="true">
|
||||
<img class="warframe-hero-image" src="assets/themes/gilded-wasteland.png" alt="" draggable="false" decoding="async">
|
||||
<span class="warframe-hero-orbit"></span>
|
||||
<span class="warframe-hero-emblem"></span>
|
||||
</div>
|
||||
<div class="wasteland-welcome-art" aria-hidden="true">
|
||||
<div class="wasteland-welcome-surface"></div>
|
||||
<img class="wasteland-welcome-frame" src="assets/themes/wasteland/frames/welcome-card.png" alt="" draggable="false" decoding="async">
|
||||
</div>
|
||||
<div class="welcome-icon">✿</div>
|
||||
<div class="welcome-copy">
|
||||
<div class="welcome-kicker">OROKIN FIELD ARCHIVE · CC-WEB</div>
|
||||
<h3 id="warframe-welcome-title" data-welcome-project-copy>${escapeHtml(getWelcomeCopy(cwd))}</h3>
|
||||
<p>本次你要构建什么?</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function normalizeAgent(agent) {
|
||||
@@ -526,6 +569,8 @@
|
||||
const text = document.createElement('div');
|
||||
text.className = 'note-text';
|
||||
text.textContent = message.text;
|
||||
const attachmentSummary = renderAttachmentPreviews(message.attachments);
|
||||
if (attachmentSummary) text.insertAdjacentHTML('beforeend', attachmentSummary);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'note-actions';
|
||||
@@ -738,16 +783,21 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
function getQueuedMessageValidationError(content) {
|
||||
function getQueuedMessageValidationError(content, attachments = []) {
|
||||
const text = String(content || '').trim();
|
||||
const attachmentList = Array.isArray(attachments) ? attachments : [];
|
||||
if (!supportsQueuedSend()) return '排队发送仅支持 Codex App。';
|
||||
if (!String(content || '').trim()) return '排队内容不能为空。';
|
||||
if (String(content || '').trim().startsWith('/')) return '排队发送暂不支持 slash 指令。';
|
||||
if (!text && attachmentList.length === 0) return '排队内容不能为空。';
|
||||
if (text.startsWith('/')) return '排队发送暂不支持 slash 指令。';
|
||||
return '';
|
||||
}
|
||||
|
||||
function addQueuedMessage(text, options = {}) {
|
||||
const content = String(text || '').trim();
|
||||
const validationError = getQueuedMessageValidationError(content);
|
||||
const attachments = Array.isArray(options.attachments)
|
||||
? options.attachments.map((attachment) => ({ ...attachment }))
|
||||
: [];
|
||||
const validationError = getQueuedMessageValidationError(content, attachments);
|
||||
if (validationError) {
|
||||
appendError(validationError);
|
||||
return false;
|
||||
@@ -755,6 +805,7 @@
|
||||
const message = {
|
||||
id: `queued-${Date.now().toString(36)}-${++queuedMessageSeq}`,
|
||||
text: content,
|
||||
attachments,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
getCurrentQueue(true).push(message);
|
||||
@@ -765,15 +816,14 @@
|
||||
|
||||
function queueMessageFromInput() {
|
||||
const text = msgInput.value.trim();
|
||||
if (!text || isBlockingSessionLoad()) return;
|
||||
if ((!text && pendingAttachments.length === 0) || isBlockingSessionLoad()) return;
|
||||
hideCmdMenu();
|
||||
hideOptionPicker();
|
||||
if (pendingAttachments.length > 0) {
|
||||
appendError('排队发送暂不支持图片附件,请先移除图片。');
|
||||
return;
|
||||
}
|
||||
if (addQueuedMessage(text)) {
|
||||
const attachments = pendingAttachments.map((attachment) => ({ ...attachment }));
|
||||
if (addQueuedMessage(text, { attachments })) {
|
||||
msgInput.value = '';
|
||||
pendingAttachments = [];
|
||||
renderPendingAttachments();
|
||||
autoResize();
|
||||
}
|
||||
}
|
||||
@@ -782,7 +832,7 @@
|
||||
const found = findPendingNote(noteId);
|
||||
if (!found) return;
|
||||
const text = String(found.note.text || '').trim();
|
||||
const validationError = getQueuedMessageValidationError(text);
|
||||
const validationError = getQueuedMessageValidationError(text, []);
|
||||
if (validationError) {
|
||||
appendError(validationError);
|
||||
return;
|
||||
@@ -809,7 +859,10 @@
|
||||
}
|
||||
|
||||
function removeQueuedMessage(queueId) {
|
||||
if (!dropQueuedMessage(queueId)) return;
|
||||
const message = dropQueuedMessage(queueId);
|
||||
if (!message) return;
|
||||
(Array.isArray(message.attachments) ? message.attachments : [])
|
||||
.forEach((attachment) => deleteUploadedAttachment(attachment?.id));
|
||||
renderPendingNotes({ scroll: false });
|
||||
}
|
||||
|
||||
@@ -917,7 +970,7 @@
|
||||
|
||||
const save = () => {
|
||||
const next = editor.value.trim();
|
||||
const validationError = getQueuedMessageValidationError(next);
|
||||
const validationError = getQueuedMessageValidationError(next, found.message.attachments);
|
||||
if (validationError) {
|
||||
appendError(validationError);
|
||||
editor.focus();
|
||||
@@ -980,15 +1033,18 @@
|
||||
renderPendingNotes({ scroll: false });
|
||||
|
||||
const text = String(message?.text || '').trim();
|
||||
if (!text) {
|
||||
const attachments = Array.isArray(message?.attachments)
|
||||
? message.attachments.map((attachment) => ({ ...attachment }))
|
||||
: [];
|
||||
if (!text && attachments.length === 0) {
|
||||
scheduleQueuedMessageDrain();
|
||||
return;
|
||||
}
|
||||
submitUserMessage(text);
|
||||
submitUserMessage(text, attachments);
|
||||
}
|
||||
|
||||
function normalizeTheme(theme) {
|
||||
return THEME_OPTIONS.some((item) => item.value === theme) ? theme : 'washi';
|
||||
return THEME_OPTIONS.some((item) => item.value === theme && !item.hidden) ? theme : 'washi';
|
||||
}
|
||||
|
||||
function getThemeOption(theme) {
|
||||
@@ -1034,7 +1090,7 @@
|
||||
return `
|
||||
${showSectionTitle ? '<div class="settings-section-title">界面主题</div>' : ''}
|
||||
<div class="theme-grid">
|
||||
${THEME_OPTIONS.map((theme) => `
|
||||
${THEME_OPTIONS.filter((theme) => !theme.hidden).map((theme) => `
|
||||
<button class="theme-card${theme.value === currentTheme ? ' active' : ''}" type="button" data-theme-value="${theme.value}">
|
||||
<div class="theme-card-preview">
|
||||
${theme.swatches.map((color) => `<span class="theme-card-swatch" style="background:${color}"></span>`).join('')}
|
||||
@@ -4099,6 +4155,7 @@
|
||||
}
|
||||
chatCwd.disabled = !currentCwd;
|
||||
chatCwd.hidden = !currentCwd;
|
||||
syncWelcomeCopy(currentCwd);
|
||||
}
|
||||
|
||||
function currentSessionWaitState() {
|
||||
@@ -4212,7 +4269,7 @@
|
||||
updateSessionIdBadge();
|
||||
updateCwdBadge();
|
||||
updateReloadMcpButtonUI();
|
||||
messagesDiv.innerHTML = buildWelcomeMarkup(currentAgent);
|
||||
messagesDiv.innerHTML = buildWelcomeMarkup(currentCwd);
|
||||
setStatsDisplay(null);
|
||||
renderPendingAttachments();
|
||||
renderPendingNotes({ scroll: false });
|
||||
@@ -6541,6 +6598,48 @@
|
||||
return title.length > 36 ? `${title.slice(0, 36)}…` : title;
|
||||
}
|
||||
|
||||
const COLLAB_AGENT_AUTO_TITLE_TOKEN_LABELS = {
|
||||
plan: '计划',
|
||||
review: '审查',
|
||||
reviewer: '审查',
|
||||
backend: '后端',
|
||||
frontend: '前端',
|
||||
state: '状态',
|
||||
implement: '实现',
|
||||
implementation: '实现',
|
||||
check: '检查',
|
||||
test: '测试',
|
||||
research: '调研',
|
||||
design: '设计',
|
||||
release: '发布',
|
||||
runtime: '运行时',
|
||||
audit: '审计',
|
||||
fix: '修复',
|
||||
document: '文档',
|
||||
docs: '文档',
|
||||
quality: '质量',
|
||||
trellis: 'Trellis',
|
||||
};
|
||||
|
||||
function joinCollabAgentAutoTitleParts(parts) {
|
||||
return parts.reduce((title, part) => {
|
||||
if (!title) return part;
|
||||
const shouldJoinDirectly = /[\u4e00-\u9fff]$/.test(title) && /^[\u4e00-\u9fff]/.test(part);
|
||||
return shouldJoinDirectly ? `${title}${part}` : `${title} ${part}`;
|
||||
}, '');
|
||||
}
|
||||
|
||||
function readableCollabAgentAutoTitle(value) {
|
||||
const text = cleanCollabAgentText(value);
|
||||
if (!/^[a-z0-9]+(?:[_-][a-z0-9]+)+$/.test(text)) return text;
|
||||
const parts = text.split(/[_-]+/).map((token) => {
|
||||
const mapped = COLLAB_AGENT_AUTO_TITLE_TOKEN_LABELS[token];
|
||||
if (mapped) return mapped;
|
||||
return token ? `${token.charAt(0).toUpperCase()}${token.slice(1)}` : '';
|
||||
}).filter(Boolean);
|
||||
return joinCollabAgentAutoTitleParts(parts);
|
||||
}
|
||||
|
||||
function collabAgentTaskDescription(state = {}, fallbackPrompt = '') {
|
||||
return cleanCollabAgentText(
|
||||
state.taskDescription
|
||||
@@ -6556,6 +6655,7 @@
|
||||
|
||||
function pickCollabAgentTitle(state, id, index) {
|
||||
const taskDescription = cleanCollabAgentText(state?.taskDescription || state?.prompt || '');
|
||||
const canReadAutomaticTitle = state?.hasReadableSourceTitle !== false;
|
||||
const titleCandidates = [
|
||||
state.label,
|
||||
state.title,
|
||||
@@ -6564,7 +6664,9 @@
|
||||
];
|
||||
for (const value of titleCandidates) {
|
||||
const candidate = cleanCollabAgentText(value);
|
||||
if (candidate && !isGenericCollabAgentLabel(candidate, id)) return candidate;
|
||||
if (candidate && !isGenericCollabAgentLabel(candidate, id)) {
|
||||
return canReadAutomaticTitle ? readableCollabAgentAutoTitle(candidate) : candidate;
|
||||
}
|
||||
}
|
||||
const promptTitle = collabAgentTitleFromPrompt(taskDescription);
|
||||
if (promptTitle) return promptTitle;
|
||||
@@ -6589,6 +6691,7 @@
|
||||
const incomingHasReadableSourceTitle = incomingState.hasReadableSourceTitle == null
|
||||
? hasReadableCollabAgentTitle(incomingState, id)
|
||||
: incomingState.hasReadableSourceTitle === true;
|
||||
const hasReadableSourceTitle = incomingHasReadableSourceTitle || previousState.hasReadableSourceTitle === true;
|
||||
if (incomingHasReadableSourceTitle) {
|
||||
nextState.label = pickCollabAgentTitle(incomingState, id, index);
|
||||
} else {
|
||||
@@ -6599,6 +6702,7 @@
|
||||
}
|
||||
return {
|
||||
...nextState,
|
||||
hasReadableSourceTitle,
|
||||
label: pickCollabAgentTitle(nextState, id, index),
|
||||
taskDescription,
|
||||
};
|
||||
@@ -6727,7 +6831,10 @@
|
||||
return Object.entries(states).map(([id, value], index) => {
|
||||
const state = value && typeof value === 'object' ? value : { status: value };
|
||||
const taskDescription = collabAgentTaskDescription(state, data?.prompt || '');
|
||||
const label = pickCollabAgentTitle({ ...state, taskDescription }, id, index);
|
||||
const hasReadableSourceTitle = state.hasReadableSourceTitle == null
|
||||
? hasReadableCollabAgentTitle(state, id)
|
||||
: state.hasReadableSourceTitle === true;
|
||||
const label = pickCollabAgentTitle({ ...state, taskDescription, hasReadableSourceTitle }, id, index);
|
||||
const role = cleanCollabAgentText(state.role || state.agent || state.agentType || '');
|
||||
let status = cleanCollabAgentText(state.status || state.state || 'pending') || 'pending';
|
||||
if (state.closedAt && collabStateTone(status) !== 'closed') status = 'closed';
|
||||
@@ -6742,7 +6849,7 @@
|
||||
status,
|
||||
detail,
|
||||
taskDescription,
|
||||
hasReadableSourceTitle: hasReadableCollabAgentTitle(state, id),
|
||||
hasReadableSourceTitle,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -7190,11 +7297,14 @@
|
||||
stateEntries.forEach((entry, index) => {
|
||||
const tone = collabStateTone(entry.status);
|
||||
const displayTitle = pickCollabAgentTitle(entry, entry.id, index);
|
||||
const descriptionText = summarizePrompt(entry.taskDescription);
|
||||
const fallbackDescription = `子代理任务:${displayTitle}`;
|
||||
const descriptionText = summarizePrompt(entry.taskDescription) || fallbackDescription;
|
||||
const descriptionTitle = cleanCollabAgentText(entry.taskDescription) || fallbackDescription;
|
||||
const item = document.createElement('div');
|
||||
item.className = 'collab-agent-item';
|
||||
item.title = [
|
||||
displayTitle,
|
||||
descriptionTitle,
|
||||
entry.role ? `角色: ${entry.role}` : '',
|
||||
entry.detail ? `结果: ${entry.detail}` : '',
|
||||
entry.id ? `ID: ${entry.id}` : '',
|
||||
@@ -7218,7 +7328,7 @@
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'collab-agent-item-label';
|
||||
label.textContent = pickCollabAgentTitle(entry, entry.id, index);
|
||||
label.textContent = displayTitle;
|
||||
row.appendChild(label);
|
||||
|
||||
const chip = document.createElement('span');
|
||||
@@ -7246,13 +7356,11 @@
|
||||
}
|
||||
item.appendChild(row);
|
||||
|
||||
if (descriptionText) {
|
||||
const description = document.createElement('div');
|
||||
description.className = 'collab-agent-item-description';
|
||||
description.textContent = descriptionText;
|
||||
description.title = entry.taskDescription;
|
||||
item.appendChild(description);
|
||||
}
|
||||
const description = document.createElement('div');
|
||||
description.className = 'collab-agent-item-description';
|
||||
description.textContent = descriptionText;
|
||||
description.title = descriptionTitle;
|
||||
item.appendChild(description);
|
||||
|
||||
if (entry.id || entry.role) {
|
||||
const footer = document.createElement('div');
|
||||
@@ -7392,7 +7500,7 @@
|
||||
messagesDiv.innerHTML = '';
|
||||
clearUserMessageIndex();
|
||||
if (messages.length === 0) {
|
||||
messagesDiv.innerHTML = buildWelcomeMarkup(currentAgent);
|
||||
messagesDiv.innerHTML = buildWelcomeMarkup(currentCwd);
|
||||
updateUserOutlinePanel();
|
||||
renderPendingNotes({ scroll: false });
|
||||
scrollToBottom();
|
||||
@@ -8142,6 +8250,7 @@
|
||||
if (!isDragging) scrollbarEl.classList.remove('scrolling');
|
||||
}, 1200);
|
||||
}, { passive: true });
|
||||
|
||||
new ResizeObserver(updateScrollbar).observe(messagesDiv);
|
||||
|
||||
// Drag logic
|
||||
@@ -8809,16 +8918,13 @@
|
||||
hideOptionPicker();
|
||||
|
||||
if (runtimeInsert) {
|
||||
if (pendingAttachments.length > 0) {
|
||||
appendError('Codex App 运行中插入暂不支持图片附件,请先移除图片。');
|
||||
return;
|
||||
}
|
||||
if (isKnownSlashCommandText(text)) {
|
||||
appendError('Codex App 运行中暂不支持 slash 指令插入。');
|
||||
return;
|
||||
}
|
||||
const attachments = pendingAttachments.map((attachment) => ({ ...attachment }));
|
||||
const messageId = createLocalId('user');
|
||||
const element = createMsgElement('user', text, [], { messageId, codexAppSteerStatus: 'pending' });
|
||||
const element = createMsgElement('user', text, attachments, { messageId, codexAppSteerStatus: 'pending' });
|
||||
const streamEl = document.getElementById('streaming-msg');
|
||||
const shouldFollow = isNearBottom();
|
||||
if (streamEl && streamEl.parentNode === messagesDiv) {
|
||||
@@ -8838,8 +8944,10 @@
|
||||
} else {
|
||||
updateScrollbar();
|
||||
}
|
||||
send({ type: 'message', text, sessionId: currentSessionId, mode: currentMode, agent: currentAgent, clientMessageId: messageId });
|
||||
send({ type: 'message', text, attachments, sessionId: currentSessionId, mode: currentMode, agent: currentAgent, clientMessageId: messageId });
|
||||
msgInput.value = '';
|
||||
pendingAttachments = [];
|
||||
renderPendingAttachments();
|
||||
autoResize();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user