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) {

View File

@@ -14,7 +14,7 @@
document.documentElement.dataset.dividerTime = dividerTime;
})();
</script>
<link rel="stylesheet" href="style.css?v=20260616-child-agent-close-state">
<link rel="stylesheet" href="style.css?v=20260617-skill-openai-yaml">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
</head>
<body>
@@ -150,6 +150,6 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="app.js?v=20260616-child-agent-close-state"></script>
<script src="app.js?v=20260617-skill-openai-yaml"></script>
</body>
</html>

View File

@@ -1903,6 +1903,105 @@ body.session-loading-active {
border-bottom-right-radius: 4px;
padding-right: 42px;
}
.msg-mentions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.msg-mention-chip {
--mention-accent: rgba(255, 255, 255, 0.34);
display: flex;
flex-direction: column;
align-items: stretch;
gap: 5px;
max-width: 100%;
min-width: 0;
padding: 7px 9px 8px;
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: 12px;
background: rgba(255, 255, 255, 0.1);
color: inherit;
}
.msg-mention-chip.kind-skill {
width: min(290px, 100%);
}
.msg-mention-chip.kind-prompt,
.msg-mention-chip.kind-file {
background: rgba(255, 255, 255, 0.08);
}
.msg-mention-badge {
min-width: 0;
height: 18px;
width: auto;
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
align-self: flex-start;
padding: 0 7px;
border-radius: 6px;
background: color-mix(in srgb, var(--mention-accent) 68%, rgba(255, 255, 255, 0.08));
color: #fff;
font-size: 9px;
font-weight: 800;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0;
overflow: hidden;
}
.msg-mention-badge img {
display: block;
width: 18px;
height: 18px;
margin: 0 -7px;
object-fit: cover;
}
.msg-mention-body {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.msg-mention-title {
font-size: 12px;
font-weight: 800;
line-height: 1.25;
overflow-wrap: anywhere;
}
.msg-mention-desc {
font-size: 11px;
line-height: 1.3;
color: rgba(255, 255, 255, 0.84);
overflow-wrap: anywhere;
}
.msg-mention-meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 2px;
}
.msg-mention-pill {
display: inline-flex;
align-items: center;
max-width: 100%;
padding: 2px 6px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.88);
font-size: 10px;
font-weight: 700;
line-height: 1.2;
overflow-wrap: anywhere;
}
.msg-mention-pill.state-configured {
border-color: rgba(255, 255, 255, 0.28);
background: rgba(93, 138, 84, 0.24);
}
.msg-mention-pill.state-declared {
background: rgba(255, 255, 255, 0.06);
}
.msg.user.codex-steer-message .msg-bubble {
padding-bottom: 10px;
}
@@ -2051,6 +2150,30 @@ body.session-loading-active {
border-bottom-left-radius: 4px;
color: var(--text-primary);
}
.msg.assistant .msg-mention-chip {
border-color: rgba(48, 62, 82, 0.14);
background: rgba(91, 126, 161, 0.07);
color: var(--text-primary);
}
.msg.assistant .msg-mention-chip.kind-prompt,
.msg.assistant .msg-mention-chip.kind-file {
background: rgba(91, 126, 161, 0.05);
}
.msg.assistant .msg-mention-badge {
color: #fff;
}
.msg.assistant .msg-mention-desc {
color: var(--text-secondary);
}
.msg.assistant .msg-mention-pill {
border-color: rgba(48, 62, 82, 0.14);
background: rgba(91, 126, 161, 0.08);
color: var(--text-secondary);
}
.msg.assistant .msg-mention-pill.state-configured {
background: rgba(93, 138, 84, 0.14);
color: var(--success);
}
.note-meta {
margin-bottom: 6px;
color: var(--note-accent);