diff --git a/scripts/debug_render.py b/scripts/debug_render.py index 6180fe6..f755f02 100644 --- a/scripts/debug_render.py +++ b/scripts/debug_render.py @@ -77,16 +77,19 @@ async def debug_render( content="代码写得好,下班走得早。", sender="张三", reason="深刻揭示了程序员的生存法则", + qq=123456789, ), GoldenQuote( content="这个Bug我不修,它就是个Feature。", sender="李四", reason="经典的开发辩解", + qq=987654321, ), GoldenQuote( content="PHP是世界上最好的语言!", sender="王五", reason="引发了长达3小时的群聊大讨论", + qq=112233445, ), ], emoji_count=156, diff --git a/src/analysis/analyzers/golden_quote_analyzer.py b/src/analysis/analyzers/golden_quote_analyzer.py index e7587c8..f717a3b 100644 --- a/src/analysis/analyzers/golden_quote_analyzer.py +++ b/src/analysis/analyzers/golden_quote_analyzer.py @@ -152,7 +152,12 @@ class GoldenQuoteAnalyzer(BaseAnalyzer): ("http", "www", "/") ): 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 @@ -183,7 +188,22 @@ class GoldenQuoteAnalyzer(BaseAnalyzer): return [], TokenUsage() 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: logger.error(f"金句分析失败: {e}") diff --git a/src/models/data_models.py b/src/models/data_models.py index d39240b..0625be9 100644 --- a/src/models/data_models.py +++ b/src/models/data_models.py @@ -33,6 +33,7 @@ class GoldenQuote: content: str sender: str reason: str + qq: int = 0 @dataclass diff --git a/src/reports/generators.py b/src/reports/generators.py index 206c1b9..16fa16e 100644 --- a/src/reports/generators.py +++ b/src/reports/generators.py @@ -214,11 +214,15 @@ class ReportGenerator: max_golden_quotes = self.config_manager.get_max_golden_quotes() quotes_list = [] 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( { "content": quote.content, "sender": quote.sender, "reason": quote.reason, + "avatar_url": avatar_url, } ) diff --git a/src/reports/templates/scrapbook/image_template.html b/src/reports/templates/scrapbook/image_template.html index 555341e..6fec4af 100644 --- a/src/reports/templates/scrapbook/image_template.html +++ b/src/reports/templates/scrapbook/image_template.html @@ -603,10 +603,110 @@ .quote-wrapper { display: flex; 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; } + .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) { align-self: flex-start; } @@ -619,62 +719,59 @@ .q-bubble { background: #fff; border: 2px solid var(--ink-primary); - padding: 20px 25px; + padding: 15px 25px; border-radius: 20px; - box-shadow: 5px 5px 0 var(--color-blue); + box-shadow: 4px 4px 0 var(--color-blue); position: relative; z-index: 2; + width: fit-content; + /* Dynamic width */ + max-width: 100%; } .quote-wrapper:nth-child(odd) .q-bubble { - border-bottom-left-radius: 0; + border-top-left-radius: 4px; } .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); - box-shadow: -5px 5px 0 var(--color-pink); + box-shadow: -4px 4px 0 var(--color-pink); text-align: right; + border-bottom-left-radius: 20px; } .q-content { font-family: var(--font-title); font-size: 1.3rem; line-height: 1.4; - margin-bottom: 10px; + margin-bottom: 5px; position: relative; } .q-quote-mark { - position: absolute; - font-family: serif; - 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; + display: none; + /* Hide quote mark for cleaner chat look */ } .q-analysis-note { background: #fffde7; - padding: 12px 15px; + padding: 10px 15px; font-size: 0.9rem; color: #666; - width: 95%; - margin-top: -8px; + width: fit-content; + /* Fit content */ + max-width: 100%; + margin-top: -5px; box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); border: 1px solid #ddd; position: relative; z-index: 1; font-family: var(--font-body); line-height: 1.5; + border-radius: 0 0 10px 10px; } .quote-wrapper:nth-child(odd) .q-analysis-note { diff --git a/src/reports/templates/scrapbook/pdf_template.html b/src/reports/templates/scrapbook/pdf_template.html index 555341e..aecbf02 100644 --- a/src/reports/templates/scrapbook/pdf_template.html +++ b/src/reports/templates/scrapbook/pdf_template.html @@ -603,10 +603,110 @@ .quote-wrapper { display: flex; 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; } + .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) { align-self: flex-start; } @@ -619,62 +719,59 @@ .q-bubble { background: #fff; border: 2px solid var(--ink-primary); - padding: 20px 25px; + padding: 15px 25px; border-radius: 20px; - box-shadow: 5px 5px 0 var(--color-blue); + box-shadow: 4px 4px 0 var(--color-blue); position: relative; z-index: 2; + width: fit-content; + /* Dynamic width */ + max-width: 100%; } .quote-wrapper:nth-child(odd) .q-bubble { - border-bottom-left-radius: 0; + border-top-left-radius: 4px; } .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); - box-shadow: -5px 5px 0 var(--color-pink); + box-shadow: -4px 4px 0 var(--color-pink); text-align: right; + border-bottom-left-radius: 20px; } .q-content { font-family: var(--font-title); font-size: 1.3rem; line-height: 1.4; - margin-bottom: 10px; + margin-bottom: 5px; position: relative; } .q-quote-mark { - position: absolute; - font-family: serif; - 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; + display: none; + /* Hide quote mark for cleaner chat look */ } .q-analysis-note { background: #fffde7; - padding: 12px 15px; + padding: 10px 15px; font-size: 0.9rem; color: #666; - width: 95%; - margin-top: -8px; + width: fit-content; + /* Fit content */ + max-width: 100%; + margin-top: -5px; box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); border: 1px solid #ddd; position: relative; z-index: 1; font-family: var(--font-body); line-height: 1.5; + border-radius: 0 0 10px 10px; } .quote-wrapper:nth-child(odd) .q-analysis-note { diff --git a/src/reports/templates/scrapbook/quote_item.html b/src/reports/templates/scrapbook/quote_item.html index 79d7529..7d31037 100644 --- a/src/reports/templates/scrapbook/quote_item.html +++ b/src/reports/templates/scrapbook/quote_item.html @@ -8,14 +8,26 @@ {% for quote in quotes %}
-
-
"
-
{{ quote.content }}
-
— {{ quote.sender }}
-
-
- 🤣👉 AI 锐评: - {{ quote.reason }} +
+
+ {% if quote.avatar_url %} +
+ +
+ {% endif %} +
+ +
+
{{ quote.sender }}
+
+
"
+
{{ quote.content }}
+
+
+ 🤣👉 AI 锐评: + {{ quote.reason }} +
+
{% endfor %}