feat: enrich Codex skill metadata display
This commit is contained in:
116
public/app.js
116
public/app.js
@@ -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, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
123
public/style.css
123
public/style.css
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user