feat(scrapbook): 群贤毕至添加头像

This commit is contained in:
SXP-Simon
2026-01-13 16:29:34 +08:00
committed by Helian Nuits
parent 131a411a70
commit fd5d4b978d
7 changed files with 292 additions and 58 deletions
+3
View File
@@ -77,16 +77,19 @@ async def debug_render(
content="代码写得好,下班走得早。", content="代码写得好,下班走得早。",
sender="张三", sender="张三",
reason="深刻揭示了程序员的生存法则", reason="深刻揭示了程序员的生存法则",
qq=123456789,
), ),
GoldenQuote( GoldenQuote(
content="这个Bug我不修,它就是个Feature。", content="这个Bug我不修,它就是个Feature。",
sender="李四", sender="李四",
reason="经典的开发辩解", reason="经典的开发辩解",
qq=987654321,
), ),
GoldenQuote( GoldenQuote(
content="PHP是世界上最好的语言!", content="PHP是世界上最好的语言!",
sender="王五", sender="王五",
reason="引发了长达3小时的群聊大讨论", reason="引发了长达3小时的群聊大讨论",
qq=112233445,
), ),
], ],
emoji_count=156, emoji_count=156,
@@ -152,7 +152,12 @@ class GoldenQuoteAnalyzer(BaseAnalyzer):
("http", "www", "/") ("http", "www", "/")
): ):
interesting_messages.append( interesting_messages.append(
{"sender": nickname, "time": msg_time, "content": text} {
"sender": nickname,
"time": msg_time,
"content": text,
"qq": sender.get("user_id", 0),
}
) )
return interesting_messages return interesting_messages
@@ -183,7 +188,22 @@ class GoldenQuoteAnalyzer(BaseAnalyzer):
return [], TokenUsage() return [], TokenUsage()
logger.info(f"开始从 {len(interesting_messages)} 条圣经消息中提取金句") logger.info(f"开始从 {len(interesting_messages)} 条圣经消息中提取金句")
return await self.analyze(interesting_messages, umo) logger.info(f"开始从 {len(interesting_messages)} 条圣经消息中提取金句")
quotes, usage = await self.analyze(interesting_messages, umo)
# 回填QQ号
for quote in quotes:
for msg in interesting_messages:
# 尝试匹配内容和发送者
# 注意:LLM 可能会微调内容,这里使用包含匹配或精确匹配
if (
quote.content in msg["content"]
or msg["content"] in quote.content
) and quote.sender == msg["sender"]:
quote.qq = msg.get("qq", 0)
break
return quotes, usage
except Exception as e: except Exception as e:
logger.error(f"金句分析失败: {e}") logger.error(f"金句分析失败: {e}")
+1
View File
@@ -33,6 +33,7 @@ class GoldenQuote:
content: str content: str
sender: str sender: str
reason: str reason: str
qq: int = 0
@dataclass @dataclass
+4
View File
@@ -214,11 +214,15 @@ class ReportGenerator:
max_golden_quotes = self.config_manager.get_max_golden_quotes() max_golden_quotes = self.config_manager.get_max_golden_quotes()
quotes_list = [] quotes_list = []
for quote in stats.golden_quotes[:max_golden_quotes]: for quote in stats.golden_quotes[:max_golden_quotes]:
avatar_url = (
await self._get_user_avatar(str(quote.qq)) if quote.qq else None
)
quotes_list.append( quotes_list.append(
{ {
"content": quote.content, "content": quote.content,
"sender": quote.sender, "sender": quote.sender,
"reason": quote.reason, "reason": quote.reason,
"avatar_url": avatar_url,
} }
) )
@@ -603,10 +603,110 @@
.quote-wrapper { .quote-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: 85%; width: 100%;
/* Occupy full width to allow internal flex to handle sizing */
max-width: 90%;
/* Limit max width */
position: relative; position: relative;
} }
.q-flex-container {
display: flex;
gap: 15px;
align-items: flex-start;
}
.q-user-col {
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
width: 70px;
/* Slightly narrower */
}
.q-avatar-box {
position: relative;
margin-bottom: 5px;
}
.q-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
border: 3px solid #fff;
background: #fff;
object-fit: cover;
/* Default base shadow */
box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.1);
}
/* Left/Odd: Blue Shadow */
.quote-wrapper:nth-child(odd) .q-avatar {
box-shadow: 0 0 0 2px var(--color-blue);
}
/* Right/Even: Orange Shadow */
.quote-wrapper:nth-child(even) .q-avatar {
box-shadow: 0 0 0 2px var(--accent-orange);
}
.q-content-col {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
align-items: flex-start;
/* Default align left */
}
.q-sender-name {
font-family: var(--font-hand);
font-size: 1.25rem;
/* Larger size for cursive font */
color: var(--ink-primary);
margin-bottom: 2px;
/* Reduce margin as underline takes space */
margin-left: 10px;
line-height: 1.2;
position: relative;
display: inline-block;
}
/* Underline Element */
.q-sender-name::after {
content: "";
display: block;
width: 100%;
height: 4px;
background: var(--color-blue);
opacity: 0.4;
/* Marker pen feel */
border-radius: 2px;
margin-top: -6px;
/* Overlap with text bottom */
}
/* Right-aligned layout for even items */
.quote-wrapper:nth-child(even) .q-flex-container {
flex-direction: row-reverse;
}
.quote-wrapper:nth-child(even) .q-content-col {
align-items: flex-end;
/* Align right */
}
.quote-wrapper:nth-child(even) .q-sender-name {
margin-left: 0;
margin-right: 15px;
text-align: right;
}
.quote-wrapper:nth-child(even) .q-sender-name::after {
background: var(--accent-orange);
}
.quote-wrapper:nth-child(odd) { .quote-wrapper:nth-child(odd) {
align-self: flex-start; align-self: flex-start;
} }
@@ -619,62 +719,59 @@
.q-bubble { .q-bubble {
background: #fff; background: #fff;
border: 2px solid var(--ink-primary); border: 2px solid var(--ink-primary);
padding: 20px 25px; padding: 15px 25px;
border-radius: 20px; border-radius: 20px;
box-shadow: 5px 5px 0 var(--color-blue); box-shadow: 4px 4px 0 var(--color-blue);
position: relative; position: relative;
z-index: 2; z-index: 2;
width: fit-content;
/* Dynamic width */
max-width: 100%;
} }
.quote-wrapper:nth-child(odd) .q-bubble { .quote-wrapper:nth-child(odd) .q-bubble {
border-bottom-left-radius: 0; border-top-left-radius: 4px;
} }
.quote-wrapper:nth-child(even) .q-bubble { .quote-wrapper:nth-child(even) .q-bubble {
border-bottom-right-radius: 0; border-top-right-radius: 4px;
border-bottom-right-radius: 20px;
/* Reset bottom right */
background: var(--color-yellow); background: var(--color-yellow);
box-shadow: -5px 5px 0 var(--color-pink); box-shadow: -4px 4px 0 var(--color-pink);
text-align: right; text-align: right;
border-bottom-left-radius: 20px;
} }
.q-content { .q-content {
font-family: var(--font-title); font-family: var(--font-title);
font-size: 1.3rem; font-size: 1.3rem;
line-height: 1.4; line-height: 1.4;
margin-bottom: 10px; margin-bottom: 5px;
position: relative; position: relative;
} }
.q-quote-mark { .q-quote-mark {
position: absolute; display: none;
font-family: serif; /* Hide quote mark for cleaner chat look */
font-size: 4rem;
color: rgba(0, 0, 0, 0.05);
top: -20px;
left: -10px;
pointer-events: none;
}
.q-author {
font-family: var(--font-hand);
font-weight: bold;
color: var(--ink-secondary);
font-size: 0.95rem;
} }
.q-analysis-note { .q-analysis-note {
background: #fffde7; background: #fffde7;
padding: 12px 15px; padding: 10px 15px;
font-size: 0.9rem; font-size: 0.9rem;
color: #666; color: #666;
width: 95%; width: fit-content;
margin-top: -8px; /* Fit content */
max-width: 100%;
margin-top: -5px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
border: 1px solid #ddd; border: 1px solid #ddd;
position: relative; position: relative;
z-index: 1; z-index: 1;
font-family: var(--font-body); font-family: var(--font-body);
line-height: 1.5; line-height: 1.5;
border-radius: 0 0 10px 10px;
} }
.quote-wrapper:nth-child(odd) .q-analysis-note { .quote-wrapper:nth-child(odd) .q-analysis-note {
+121 -24
View File
@@ -603,10 +603,110 @@
.quote-wrapper { .quote-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: 85%; width: 100%;
/* Occupy full width to allow internal flex to handle sizing */
max-width: 90%;
/* Limit max width */
position: relative; position: relative;
} }
.q-flex-container {
display: flex;
gap: 15px;
align-items: flex-start;
}
.q-user-col {
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
width: 70px;
/* Slightly narrower */
}
.q-avatar-box {
position: relative;
margin-bottom: 5px;
}
.q-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
border: 3px solid #fff;
background: #fff;
object-fit: cover;
/* Default base shadow */
box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.1);
}
/* Left/Odd: Blue Shadow */
.quote-wrapper:nth-child(odd) .q-avatar {
box-shadow: 0 0 0 2px var(--color-blue);
}
/* Right/Even: Orange Shadow */
.quote-wrapper:nth-child(even) .q-avatar {
box-shadow: 0 0 0 2px var(--accent-orange);
}
.q-content-col {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
align-items: flex-start;
/* Default align left */
}
.q-sender-name {
font-family: 'Long Cang', cursive;
font-size: 1.4rem;
/* Larger size for cursive font */
color: var(--ink-primary);
margin-bottom: 2px;
/* Reduce margin as underline takes space */
margin-left: 10px;
line-height: 1.2;
position: relative;
display: inline-block;
}
/* Underline Element */
.q-sender-name::after {
content: "";
display: block;
width: 100%;
height: 4px;
background: var(--color-blue);
opacity: 0.4;
/* Marker pen feel */
border-radius: 2px;
margin-top: -6px;
/* Overlap with text bottom */
}
/* Right-aligned layout for even items */
.quote-wrapper:nth-child(even) .q-flex-container {
flex-direction: row-reverse;
}
.quote-wrapper:nth-child(even) .q-content-col {
align-items: flex-end;
/* Align right */
}
.quote-wrapper:nth-child(even) .q-sender-name {
margin-left: 0;
margin-right: 15px;
text-align: right;
}
.quote-wrapper:nth-child(even) .q-sender-name::after {
background: var(--accent-orange);
}
.quote-wrapper:nth-child(odd) { .quote-wrapper:nth-child(odd) {
align-self: flex-start; align-self: flex-start;
} }
@@ -619,62 +719,59 @@
.q-bubble { .q-bubble {
background: #fff; background: #fff;
border: 2px solid var(--ink-primary); border: 2px solid var(--ink-primary);
padding: 20px 25px; padding: 15px 25px;
border-radius: 20px; border-radius: 20px;
box-shadow: 5px 5px 0 var(--color-blue); box-shadow: 4px 4px 0 var(--color-blue);
position: relative; position: relative;
z-index: 2; z-index: 2;
width: fit-content;
/* Dynamic width */
max-width: 100%;
} }
.quote-wrapper:nth-child(odd) .q-bubble { .quote-wrapper:nth-child(odd) .q-bubble {
border-bottom-left-radius: 0; border-top-left-radius: 4px;
} }
.quote-wrapper:nth-child(even) .q-bubble { .quote-wrapper:nth-child(even) .q-bubble {
border-bottom-right-radius: 0; border-top-right-radius: 4px;
border-bottom-right-radius: 20px;
/* Reset bottom right */
background: var(--color-yellow); background: var(--color-yellow);
box-shadow: -5px 5px 0 var(--color-pink); box-shadow: -4px 4px 0 var(--color-pink);
text-align: right; text-align: right;
border-bottom-left-radius: 20px;
} }
.q-content { .q-content {
font-family: var(--font-title); font-family: var(--font-title);
font-size: 1.3rem; font-size: 1.3rem;
line-height: 1.4; line-height: 1.4;
margin-bottom: 10px; margin-bottom: 5px;
position: relative; position: relative;
} }
.q-quote-mark { .q-quote-mark {
position: absolute; display: none;
font-family: serif; /* Hide quote mark for cleaner chat look */
font-size: 4rem;
color: rgba(0, 0, 0, 0.05);
top: -20px;
left: -10px;
pointer-events: none;
}
.q-author {
font-family: var(--font-hand);
font-weight: bold;
color: var(--ink-secondary);
font-size: 0.95rem;
} }
.q-analysis-note { .q-analysis-note {
background: #fffde7; background: #fffde7;
padding: 12px 15px; padding: 10px 15px;
font-size: 0.9rem; font-size: 0.9rem;
color: #666; color: #666;
width: 95%; width: fit-content;
margin-top: -8px; /* Fit content */
max-width: 100%;
margin-top: -5px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
border: 1px solid #ddd; border: 1px solid #ddd;
position: relative; position: relative;
z-index: 1; z-index: 1;
font-family: var(--font-body); font-family: var(--font-body);
line-height: 1.5; line-height: 1.5;
border-radius: 0 0 10px 10px;
} }
.quote-wrapper:nth-child(odd) .q-analysis-note { .quote-wrapper:nth-child(odd) .q-analysis-note {
@@ -8,14 +8,26 @@
</div> </div>
{% for quote in quotes %} {% for quote in quotes %}
<div class="quote-wrapper"> <div class="quote-wrapper">
<div class="q-bubble"> <div class="q-flex-container">
<div class="q-quote-mark">"</div> <div class="q-user-col">
<div class="q-content">{{ quote.content }}</div> {% if quote.avatar_url %}
<div class="q-author">— {{ quote.sender }}</div> <div class="q-avatar-box">
</div> <img src="{{ quote.avatar_url }}" class="q-avatar">
<div class="q-analysis-note"> </div>
<span class="note-label">🤣👉 AI 锐评:</span> {% endif %}
{{ quote.reason }} </div>
<div class="q-content-col">
<div class="q-sender-name">{{ quote.sender }}</div>
<div class="q-bubble">
<div class="q-quote-mark">"</div>
<div class="q-content">{{ quote.content }}</div>
</div>
<div class="q-analysis-note">
<span class="note-label">🤣👉 AI 锐评:</span>
{{ quote.reason }}
</div>
</div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}