feat: enrich Codex skill metadata display

This commit is contained in:
shiyue
2026-06-18 08:42:57 +08:00
parent 216f87e3b4
commit c50ee527ea
5 changed files with 731 additions and 31 deletions

View File

@@ -3587,6 +3587,120 @@
}, ttl);
}
function normalizeMentionList(value) {
return Array.isArray(value) ? value.filter((item) => item && typeof item === 'object') : [];
}
function escapeHtmlAttr(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function shortPreviewText(text, maxLength = 140) {
const normalized = String(text || '').trim().replace(/\s+/g, ' ');
if (!normalized) return '';
return normalized.length > maxLength ? `${normalized.slice(0, Math.max(0, maxLength - 3))}...` : normalized;
}
function mentionDependencyStateLabel(state) {
return state === 'configured' ? '已配置' : state === 'declared' ? '未配置' : '';
}
function mentionDependencyLabel(dep) {
const name = dep?.value || dep?.name || dep?.server || '';
const state = mentionDependencyStateLabel(dep?.state);
return state ? `${name} · ${state}` : name;
}
function buildMentionTooltip(mention) {
const lines = [];
const title = mention.title || mention.name || mention.label || '';
const description = shortPreviewText(mention.description || '');
if (title) lines.push(title);
if (description) lines.push(description);
if (mention.defaultPromptPreview) lines.push(`默认提示: ${shortPreviewText(mention.defaultPromptPreview, 180)}`);
const dependencies = Array.isArray(mention.dependencies) ? mention.dependencies : [];
for (const dep of dependencies.slice(0, 4)) {
const label = mentionDependencyLabel(dep);
if (label) lines.push(`MCP: ${label}`);
}
return lines.join('\n');
}
function createMentionChip(mention) {
const chip = document.createElement('div');
const kind = String(mention.kind || '').trim() || 'mention';
chip.className = `msg-mention-chip kind-${kind}`;
if (mention.brandColor) chip.style.setProperty('--mention-accent', mention.brandColor);
const tooltip = buildMentionTooltip(mention);
if (tooltip) chip.title = tooltip;
if (kind === 'skill') {
const badge = document.createElement('span');
badge.className = 'msg-mention-badge';
if (mention.iconSmall && /^https?:\/\//i.test(String(mention.iconSmall))) {
const img = document.createElement('img');
img.src = mention.iconSmall;
img.alt = mention.title || mention.name || 'skill';
img.loading = 'lazy';
badge.appendChild(img);
} else {
badge.textContent = 'Skill';
}
chip.appendChild(badge);
}
const body = document.createElement('div');
body.className = 'msg-mention-body';
const title = document.createElement('div');
title.className = 'msg-mention-title';
if (kind === 'skill') {
title.textContent = mention.title || mention.name || mention.label || '';
} else if (kind === 'prompt') {
title.textContent = mention.title || mention.label || mention.name || '';
} else {
title.textContent = mention.label || mention.name || '';
}
body.appendChild(title);
const descriptionText = mention.description || (kind === 'prompt' ? 'Prompt 模板' : '');
if (descriptionText) {
const desc = document.createElement('div');
desc.className = 'msg-mention-desc';
desc.textContent = shortPreviewText(descriptionText, kind === 'skill' ? 92 : 72);
body.appendChild(desc);
}
const dependencies = Array.isArray(mention.dependencies) ? mention.dependencies : [];
if (dependencies.length > 0) {
const meta = document.createElement('div');
meta.className = 'msg-mention-meta';
dependencies.slice(0, 2).forEach((dep) => {
const pill = document.createElement('span');
pill.className = `msg-mention-pill state-${dep.state || 'declared'}`;
pill.textContent = mentionDependencyLabel(dep);
meta.appendChild(pill);
});
body.appendChild(meta);
}
chip.appendChild(body);
return chip;
}
function renderComposerMentionsStrip(meta) {
const mentions = normalizeMentionList(meta?.composerMentions);
if (mentions.length === 0) return null;
const wrap = document.createElement('div');
wrap.className = 'msg-mentions';
mentions.forEach((mention) => wrap.appendChild(createMentionChip(mention)));
return wrap;
}
function createMsgElement(role, content, attachments = [], meta = {}) {
const div = document.createElement('div');
const isCrossConversation = !!meta.crossConversation;
@@ -3705,6 +3819,8 @@
if (attachments.length > 0) {
bubble.insertAdjacentHTML('beforeend', renderAttachmentPreviews(attachments));
}
const mentionsStrip = renderComposerMentionsStrip(meta);
if (mentionsStrip) bubble.appendChild(mentionsStrip);
} else {
renderAssistantContent(bubble, content);
if (attachments.length > 0) {