refactor(memory-ui): replace emoji icons with inline SVG line-art

This commit is contained in:
matevip 2026-05-18 13:51:20 +08:00
parent 3157dde1f4
commit 7a650dc667
4 changed files with 77 additions and 27 deletions

View File

@ -39,7 +39,11 @@
<!-- Empty -->
<div v-else-if="facts.length === 0" class="fact-empty">
<div class="empty-icon">📦</div>
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="21 8 21 21 3 21 3 8"/>
<rect x="1" y="3" width="22" height="5"/>
<line x1="10" y1="12" x2="14" y2="12"/>
</svg>
<p>{{ t('memory.facts.empty') }}</p>
</div>
@ -58,9 +62,22 @@
<FactTrustBar :trust="fact.trust" />
<span class="fact-use-count" v-if="fact.useCount > 0">{{ t('memory.facts.used', { n: fact.useCount }) }}</span>
<div class="fact-actions">
<button class="action-btn helpful" @click="feedback(fact.id, 'HELPFUL')" :title="t('memory.facts.helpful')">👍</button>
<button class="action-btn unhelpful" @click="feedback(fact.id, 'UNHELPFUL')" :title="t('memory.facts.unhelpful')">👎</button>
<button class="action-btn forget" @click="forget(fact.id)" :title="t('memory.facts.forget')">🗑</button>
<button class="action-btn helpful" @click="feedback(fact.id, 'HELPFUL')" :title="t('memory.facts.helpful')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/>
</svg>
</button>
<button class="action-btn unhelpful" @click="feedback(fact.id, 'UNHELPFUL')" :title="t('memory.facts.unhelpful')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"/>
</svg>
</button>
<button class="action-btn forget" @click="forget(fact.id)" :title="t('memory.facts.forget')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</svg>
</button>
</div>
</div>
</div>
@ -187,11 +204,13 @@ async function resolve(contradictionId: number, resolution: string) {
.fact-card:hover .fact-actions { opacity: 1; }
.action-btn {
width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
border: none; border-radius: 6px; background: transparent; font-size: 13px;
cursor: pointer; transition: background 0.12s;
border: none; border-radius: 6px; background: transparent;
color: var(--mc-text-tertiary); cursor: pointer; transition: all 0.12s;
}
.action-btn:hover { background: var(--mc-bg-elevated); }
.action-btn.forget:hover { background: rgba(255,59,48,0.1); }
.action-btn:hover { background: var(--mc-bg-elevated); color: var(--mc-text-secondary); }
.action-btn.helpful:hover { color: #34c759; }
.action-btn.unhelpful:hover { color: #ff9f0a; }
.action-btn.forget:hover { background: rgba(255,59,48,0.1); color: #ff3b30; }
/* States */
.fact-loading { padding: 8px 0; }
@ -199,7 +218,7 @@ async function resolve(contradictionId: number, resolution: string) {
.skeleton-line { height: 10px; border-radius: 4px; background: var(--mc-border-light); margin-bottom: 8px; }
.skeleton-line.short { width: 60%; }
.fact-empty { display: flex; flex-direction: column; align-items: center; padding: 40px 0; color: var(--mc-text-tertiary); }
.empty-icon { font-size: 28px; margin-bottom: 8px; }
.empty-icon { width: 32px; height: 32px; margin-bottom: 10px; opacity: 0.7; }
.slide-down-enter-active, .slide-down-leave-active { transition: all 0.2s ease; }
.slide-down-enter-from, .slide-down-leave-to { opacity: 0; transform: translateY(-8px); }

View File

@ -6,7 +6,7 @@
class="file-nav-btn" :class="{ active: currentFile === f.filename }"
:title="f.filename"
@click="loadFile(f.filename)">
<span class="file-nav-icon">{{ fileIcon(f.filename) }}</span>
<span class="file-nav-icon" v-html="fileIcon(f.filename)" />
<span class="file-nav-name">{{ fileLabel(f.filename) }}</span>
</button>
</div>
@ -54,7 +54,12 @@
<!-- Empty -->
<div v-else class="browser-empty">
<div class="empty-icon">📝</div>
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/>
</svg>
<p>{{ t('memory.memoryBrowser.empty') }}</p>
</div>
</div>
@ -203,14 +208,24 @@ function renderMd(body: string): string {
.join('')
}
// Inline line-art icons (Feather style, 14px) keep the file tabs visually
// consistent with the sidebar nav and the rest of the admin UI.
function svgIcon(inner: string): string {
return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${inner}</svg>`
}
function fileIcon(filename: string): string {
if (filename === 'MEMORY.md') return '🧠'
if (filename === 'PROFILE.md') return '👤'
if (filename === 'SOUL.md') return '💫'
if (filename === 'structured/user.md') return '📋'
if (filename === 'structured/reference.md') return '🔗'
if (filename.startsWith('structured/')) return '📋'
return '📄'
if (filename === 'MEMORY.md')
return svgIcon('<path d="M12 2a4 4 0 0 1 4 4v2a4 4 0 0 1-8 0V6a4 4 0 0 1 4-4z"/><path d="M16 14H8a4 4 0 0 0-4 4v2h16v-2a4 4 0 0 0-4-4z"/><line x1="12" y1="11" x2="12" y2="14"/>')
if (filename === 'PROFILE.md')
return svgIcon('<circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/>')
if (filename === 'SOUL.md')
return svgIcon('<path d="M12 3l1.9 5.8a2 2 0 0 0 1.3 1.3L21 12l-5.8 1.9a2 2 0 0 0-1.3 1.3L12 21l-1.9-5.8a2 2 0 0 0-1.3-1.3L3 12l5.8-1.9a2 2 0 0 0 1.3-1.3L12 3z"/>')
if (filename === 'structured/reference.md')
return svgIcon('<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>')
if (filename.startsWith('structured/'))
return svgIcon('<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>')
return svgIcon('<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>')
}
// Rank for display order: 0 = primary brain, 1 = persona, 2 = extracted facts.
@ -258,7 +273,7 @@ function fileLabel(filename: string): string {
border-color: var(--mc-primary);
color: var(--mc-text-primary);
}
.file-nav-icon { font-size: 14px; line-height: 1; }
.file-nav-icon { display: inline-flex; align-items: center; }
.file-nav-name { letter-spacing: 0.1px; }
/* Section cards */
@ -327,5 +342,5 @@ function fileLabel(filename: string): string {
.skeleton-line { height: 10px; border-radius: 4px; background: var(--mc-border-light); margin-bottom: 8px; }
.skeleton-line.short { width: 60%; }
.browser-empty { display: flex; flex-direction: column; align-items: center; padding: 40px 0; color: var(--mc-text-tertiary); }
.empty-icon { font-size: 28px; margin-bottom: 8px; }
.empty-icon { width: 32px; height: 32px; margin-bottom: 10px; color: var(--mc-text-tertiary); opacity: 0.7; }
</style>

View File

@ -2,7 +2,14 @@
<Transition name="slide-down">
<div v-if="card" class="morning-card">
<div class="morning-card__header">
<span class="morning-card__icon">🌅</span>
<span class="morning-card__icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 18a5 5 0 0 0-10 0"/><line x1="12" y1="2" x2="12" y2="9"/>
<line x1="4.22" y1="10.22" x2="5.64" y2="11.64"/><line x1="1" y1="18" x2="3" y2="18"/>
<line x1="21" y1="18" x2="23" y2="18"/><line x1="18.36" y1="11.64" x2="19.78" y2="10.22"/>
<line x1="23" y1="22" x2="1" y2="22"/><polyline points="8 6 12 2 16 6"/>
</svg>
</span>
<span class="morning-card__title">{{ t('memory.morningCard.title') }}</span>
<button class="morning-card__close" @click="dismiss">&times;</button>
</div>
@ -54,7 +61,7 @@ async function dismiss() {
align-items: center;
gap: 6px;
}
.morning-card__icon { font-size: 16px; }
.morning-card__icon { display: inline-flex; align-items: center; color: var(--mc-primary); }
.morning-card__title { flex: 1; font-size: 13px; font-weight: 600; color: var(--mc-text-primary); }
.morning-card__close {
background: none; border: none; font-size: 16px; color: var(--mc-text-tertiary);

View File

@ -44,14 +44,19 @@
</template>
<template v-else-if="store.error">
<div class="empty-state error-state">
<div class="empty-icon"></div>
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
<line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>
<p>{{ store.error }}</p>
<button class="retry-btn" @click="loadReports">{{ t('memory.retry') }}</button>
</div>
</template>
<template v-else-if="store.reports.length === 0">
<div class="empty-state">
<div class="empty-icon">🌙</div>
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
<p>{{ t('memory.noReports') }}</p>
</div>
</template>
@ -145,7 +150,11 @@
</template>
<template v-else>
<div class="detail-empty">
<div class="detail-empty-icon">🧠</div>
<svg class="detail-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2a4 4 0 0 1 4 4v2a4 4 0 0 1-8 0V6a4 4 0 0 1 4-4z"/>
<path d="M16 14H8a4 4 0 0 0-4 4v2h16v-2a4 4 0 0 0-4-4z"/>
<line x1="12" y1="11" x2="12" y2="14"/>
</svg>
<p>{{ t('memory.desc') }}</p>
</div>
</template>
@ -361,7 +370,7 @@ function fmtTime(iso: string) {
.skeleton-line.short { width: 60%; }
.empty-state { display: flex; flex-direction: column; align-items: center; padding: 40px 0; color: var(--mc-text-tertiary); }
.empty-icon { font-size: 28px; margin-bottom: 8px; }
.empty-icon { width: 30px; height: 30px; margin-bottom: 10px; opacity: 0.7; }
.empty-state p { font-size: 13px; }
.error-state p { color: var(--mc-text-secondary); }
.retry-btn {
@ -447,7 +456,7 @@ function fmtTime(iso: string) {
display: flex; flex-direction: column; align-items: center; justify-content: center;
height: 100%; color: var(--mc-text-tertiary); text-align: center;
}
.detail-empty-icon { font-size: 36px; margin-bottom: 12px; }
.detail-empty-icon { width: 40px; height: 40px; margin-bottom: 14px; opacity: 0.6; }
.detail-empty p { font-size: 14px; max-width: 260px; line-height: 1.5; }
/* ========== Transitions ========== */