Make memory UI dynamic and content-aware
This commit is contained in:
@@ -84,26 +84,59 @@ function getEntryCount(content) {
|
||||
}
|
||||
|
||||
const TAG_STOPWORDS = new Set([
|
||||
'about', 'after', 'again', 'also', 'been', 'being', 'between', 'could', 'daily', 'does', 'done', 'from', 'have', 'into', 'just', 'like', 'main', 'make', 'maybe', 'memory', 'more', 'most', 'much', 'need', 'note', 'notes', 'only', 'other', 'over', 'really', 'same', 'some', 'still', 'than', 'that', 'their', 'them', 'then', 'there', 'these', 'they', 'this', 'today', 'very', 'want', 'were', 'what', 'when', 'with', 'work', 'would', 'your', 'yours', 'jimmi', 'rook', 'said', 'todo', 'todos'
|
||||
'about', 'after', 'again', 'also', 'been', 'being', 'between', 'could', 'daily', 'does', 'done', 'from', 'have', 'into', 'just', 'like', 'main', 'make', 'maybe', 'memory', 'more', 'most', 'much', 'need', 'note', 'notes', 'only', 'other', 'over', 'really', 'same', 'some', 'still', 'than', 'that', 'their', 'them', 'then', 'there', 'these', 'they', 'this', 'today', 'very', 'want', 'were', 'what', 'when', 'with', 'work', 'would', 'your', 'yours', 'jimmi', 'rook', 'said', 'todo', 'todos', 'session', 'update', 'updated', 'using', 'used', 'kind', 'actually', 'awesome', 'maybe', 'seems'
|
||||
])
|
||||
|
||||
const SMART_TAG_RULES = [
|
||||
{ tag: 'appwrite', patterns: [/appwrite/i, /tablesdb/i, /database/i, /collection/i] },
|
||||
{ tag: 'agents', patterns: [/openclaw/i, /agent/i, /subagent/i, /codex/i, /claude code/i] },
|
||||
{ tag: 'design', patterns: [/design/i, /ui\b/i, /visual/i, /theme/i, /scandinavian/i, /gradient/i] },
|
||||
{ tag: 'memory', patterns: [/memory\.md/i, /memory app/i, /memories/i, /archive/i] },
|
||||
{ tag: 'infra', patterns: [/cloudflare/i, /systemd/i, /service/i, /deploy/i, /domain/i, /proxy/i] },
|
||||
{ tag: 'auth', patterns: [/auth/i, /login/i, /session cookie/i, /oauth/i, /permission/i] },
|
||||
{ tag: 'product', patterns: [/idea/i, /validation/i, /launch/i, /mvp/i, /portfolio/i] },
|
||||
{ tag: 'kidsstories', patterns: [/kidsstories/i, /stories/i, /child/i] },
|
||||
{ tag: 'dashboard', patterns: [/dashboard/i, /command center/i, /pixel office/i] },
|
||||
{ tag: 'family', patterns: [/kids/i, /family/i, /home/i] },
|
||||
]
|
||||
|
||||
const THEME_PROFILES = {
|
||||
nord: { name: 'Nord', accent: '#8fbcbb', accentSoft: 'rgba(143,188,187,0.18)', accentStrong: '#a3d5d3', glow: 'rgba(143,188,187,0.28)' },
|
||||
plum: { name: 'Plum', accent: '#b48ead', accentSoft: 'rgba(180,142,173,0.18)', accentStrong: '#d1a9ca', glow: 'rgba(180,142,173,0.28)' },
|
||||
amber: { name: 'Amber', accent: '#d0a86e', accentSoft: 'rgba(208,168,110,0.18)', accentStrong: '#e5bf8c', glow: 'rgba(208,168,110,0.28)' },
|
||||
ice: { name: 'Ice', accent: '#81a1c1', accentSoft: 'rgba(129,161,193,0.18)', accentStrong: '#a9c2db', glow: 'rgba(129,161,193,0.28)' },
|
||||
}
|
||||
|
||||
function isProbablyNoiseTag(value) {
|
||||
return /^\d{4}-\d{2}-\d{2}$/.test(value) || /^\d+$/.test(value)
|
||||
}
|
||||
|
||||
function generateTags(content, filename) {
|
||||
const tags = []
|
||||
const scores = new Map()
|
||||
const addScore = (tag, amount) => scores.set(tag, (scores.get(tag) || 0) + amount)
|
||||
const pushTag = (value) => {
|
||||
const cleaned = String(value || '').toLowerCase().replace(/[^a-z0-9+-]+/g, '').trim()
|
||||
if (!cleaned || cleaned.length < 3 || TAG_STOPWORDS.has(cleaned) || tags.includes(cleaned)) return
|
||||
if (!cleaned || cleaned.length < 3 || TAG_STOPWORDS.has(cleaned) || tags.includes(cleaned) || isProbablyNoiseTag(cleaned)) return
|
||||
tags.push(cleaned)
|
||||
}
|
||||
|
||||
if (filename === 'MEMORY.md') pushTag('core')
|
||||
if (filename === 'MEMORY.md') addScore('core', 5)
|
||||
|
||||
for (const rule of SMART_TAG_RULES) {
|
||||
for (const pattern of rule.patterns) {
|
||||
const matches = content.match(new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`)) || []
|
||||
if (matches.length) addScore(rule.tag, matches.length * 3)
|
||||
}
|
||||
}
|
||||
|
||||
for (const line of content.split('\n')) {
|
||||
const trimmed = line.trim()
|
||||
if (trimmed.startsWith('#')) {
|
||||
trimmed.replace(/^#+\s*/, '').split(/[^a-zA-Z0-9+-]+/).forEach(pushTag)
|
||||
trimmed.replace(/^#+\s*/, '').split(/[^a-zA-Z0-9+-]+/).forEach((token) => addScore(token.toLowerCase(), 2))
|
||||
}
|
||||
const hashtagMatches = trimmed.match(/#[a-zA-Z0-9+-]+/g) || []
|
||||
hashtagMatches.forEach((tag) => pushTag(tag.slice(1)))
|
||||
hashtagMatches.forEach((tag) => addScore(tag.slice(1).toLowerCase(), 4))
|
||||
}
|
||||
|
||||
const frequency = new Map()
|
||||
@@ -113,16 +146,28 @@ function generateTags(content, filename) {
|
||||
.replace(/[^a-z0-9+\-\s]/g, ' ')
|
||||
|
||||
for (const token of normalized.split(/\s+/)) {
|
||||
if (!token || token.length < 4 || TAG_STOPWORDS.has(token)) continue
|
||||
if (!token || token.length < 4 || TAG_STOPWORDS.has(token) || isProbablyNoiseTag(token)) continue
|
||||
frequency.set(token, (frequency.get(token) || 0) + 1)
|
||||
}
|
||||
|
||||
Array.from(frequency.entries())
|
||||
for (const [token, count] of frequency.entries()) {
|
||||
if (count >= 2) addScore(token, count)
|
||||
}
|
||||
|
||||
Array.from(scores.entries())
|
||||
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
||||
.slice(0, 8)
|
||||
.forEach(([token]) => pushTag(token))
|
||||
|
||||
return tags.slice(0, 4)
|
||||
return tags.slice(0, 5)
|
||||
}
|
||||
|
||||
function buildThemeProfile(content = '', tags = []) {
|
||||
const haystack = `${content} ${(tags || []).join(' ')}`.toLowerCase()
|
||||
if (/design|theme|visual|scandinavian|style/.test(haystack)) return THEME_PROFILES.plum
|
||||
if (/infra|deploy|cloudflare|service|proxy|appwrite/.test(haystack)) return THEME_PROFILES.ice
|
||||
if (/kidsstories|family|home|story/.test(haystack)) return THEME_PROFILES.amber
|
||||
return THEME_PROFILES.nord
|
||||
}
|
||||
|
||||
function formatDateLabel(filename) {
|
||||
@@ -293,7 +338,9 @@ app.get('/api/meta', async (req, res) => {
|
||||
return acc
|
||||
}, { dailyFileCount: 0, totalDailyEntries: 0, totalDailyWords: 0 })
|
||||
|
||||
res.json({ mainMemory, dailyFiles, summary })
|
||||
const theme = buildThemeProfile(mainDoc?.content || '', mainMemory?.tags || [])
|
||||
|
||||
res.json({ mainMemory, dailyFiles, summary, theme })
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user