Skip to content

Commit 544798f

Browse files
committed
添加AI总结
1 parent 7add177 commit 544798f

5 files changed

Lines changed: 259 additions & 0 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
---
2+
export interface Props {
3+
content: string;
4+
}
5+
6+
const { content } = Astro.props;
7+
8+
// 如果没有内容,不渲染组件
9+
if (!content || content.trim() === '') {
10+
return null;
11+
}
12+
---
13+
14+
{content && (
15+
<div class="ai-summary">
16+
<div class="ai-title">
17+
<div class="ai-title-left">
18+
<i>🤖</i>
19+
<span class="ai-title-text">AI 摘要</span>
20+
</div>
21+
<div class="ai-tag">fishcpy AI</div>
22+
</div>
23+
<div class="ai-explanation" data-content={content}></div>
24+
</div>
25+
)}
26+
27+
<script>
28+
// 检查当前页面路径是否包含 "posts"
29+
function isPostsPage() {
30+
return window.location.pathname.includes('/posts/');
31+
}
32+
33+
// 全局函数,用于初始化AI打字效果
34+
function initAITyping() {
35+
// 只在包含 "posts" 的页面才执行AI总结功能
36+
if (!isPostsPage()) {
37+
return;
38+
}
39+
40+
// 查找所有AI摘要容器
41+
const aiSummaryContainers = document.querySelectorAll('.ai-summary');
42+
43+
aiSummaryContainers.forEach(container => {
44+
const textElement = container.querySelector('.ai-explanation');
45+
46+
if (!textElement) {
47+
return;
48+
}
49+
50+
// 检查是否已经初始化过
51+
if (textElement.hasAttribute('data-initialized')) {
52+
return;
53+
}
54+
55+
const content = textElement.getAttribute('data-content');
56+
if (!content) {
57+
return;
58+
}
59+
60+
// 标记为已初始化
61+
textElement.setAttribute('data-initialized', 'true');
62+
63+
// 清空文本内容,准备打字效果
64+
textElement.textContent = '';
65+
textElement.classList.remove('typing-complete');
66+
67+
let index = 0;
68+
const typeSpeed = 30; // 打字速度(毫秒)
69+
70+
function typeWriter() {
71+
if (index < content.length) {
72+
textElement.textContent += content.charAt(index);
73+
index++;
74+
setTimeout(typeWriter, typeSpeed);
75+
} else {
76+
// 打字完成后隐藏光标(通过CSS控制)
77+
textElement.classList.add('typing-complete');
78+
}
79+
}
80+
81+
// 延迟开始打字效果
82+
setTimeout(typeWriter, 800);
83+
});
84+
}
85+
86+
// 页面加载完成时初始化
87+
function handlePageLoad() {
88+
setTimeout(initAITyping, 100);
89+
}
90+
91+
// 监听页面导航事件(适用于Astro的客户端路由)
92+
function setupNavigationListeners() {
93+
// DOMContentLoaded事件
94+
if (document.readyState === 'loading') {
95+
document.addEventListener('DOMContentLoaded', handlePageLoad);
96+
} else {
97+
handlePageLoad();
98+
}
99+
100+
// 监听Astro的页面导航事件
101+
document.addEventListener('astro:page-load', handlePageLoad);
102+
103+
// 监听浏览器的popstate事件(后退/前进按钮)
104+
window.addEventListener('popstate', handlePageLoad);
105+
106+
// 监听pushstate和replacestate事件
107+
const originalPushState = history.pushState;
108+
const originalReplaceState = history.replaceState;
109+
110+
history.pushState = function() {
111+
originalPushState.apply(history, arguments);
112+
setTimeout(handlePageLoad, 100);
113+
};
114+
115+
history.replaceState = function() {
116+
originalReplaceState.apply(history, arguments);
117+
setTimeout(handlePageLoad, 100);
118+
};
119+
}
120+
121+
// 立即设置监听器
122+
setupNavigationListeners();
123+
</script>

src/content/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const postsCollection = defineCollection({
1111
tags: z.array(z.string()).optional().default([]),
1212
category: z.string().optional().nullable().default(""),
1313
lang: z.string().optional().default(""),
14+
ai: z.string().optional().default(""),
1415

1516
/* For internal use */
1617
prevTitle: z.string().default(""),

src/pages/posts/[...slug].astro

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import PostMetadata from "../../components/PostMeta.astro";
1414
import { profileConfig, siteConfig } from "../../config";
1515
import { formatDateToYYYYMMDD } from "../../utils/date-utils";
1616
import Comment from "@components/comment/index.astro";
17+
import AISummary from "@components/misc/AISummary.astro";
1718
1819
export async function getStaticPaths() {
1920
const blogEntries = await getSortedPosts();
@@ -84,6 +85,9 @@ const jsonLd = {
8485
</div>
8586
</div>
8687

88+
<!-- AI Summary -->
89+
{entry.data.description && <AISummary content={entry.data.description} class="onload-animation" />}
90+
8791
<!-- metadata -->
8892
<div class="onload-animation">
8993
<PostMetadata

src/styles/main.css

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,127 @@ nav, .navbar {
191191

192192
.collapsed {
193193
height: var(--collapsedHeight);
194+
}
195+
196+
/* =================== */
197+
/* 📘 AI 摘要模块样式 */
198+
/* =================== */
199+
200+
.ai-summary {
201+
background-color: var(--liushen-maskbg);
202+
background: var(--liushen-card-secondbg);
203+
border-radius: 12px;
204+
padding: 8px 8px 12px 8px;
205+
line-height: 1.3;
206+
flex-direction: column;
207+
margin-bottom: 16px;
208+
display: flex;
209+
gap: 5px;
210+
position: relative;
211+
}
212+
213+
.ai-summary::before {
214+
content: '';
215+
position: absolute;
216+
top: 0;
217+
left: 0;
218+
width: 100%;
219+
height: 100%;
220+
z-index: 1;
221+
filter: blur(8px);
222+
opacity: 0.4;
223+
background-image: var(--liushen-ai-bg);
224+
transform: scaleX(1) scaleY(0.95) translateY(2px);
225+
}
226+
227+
.ai-summary::after {
228+
content: '';
229+
position: absolute;
230+
top: 0;
231+
left: 0;
232+
width: 100%;
233+
height: 100%;
234+
z-index: 2;
235+
border-radius: 12px;
236+
background: var(--liushen-maskbg);
237+
}
238+
239+
.ai-summary .ai-explanation {
240+
z-index: 10;
241+
padding: 8px 12px;
242+
font-size: 15px;
243+
line-height: 1.4;
244+
color: var(--liushen-text);
245+
text-align: justify;
246+
}
247+
248+
/* ✅ 打字机光标动画 */
249+
.ai-summary .ai-explanation::after {
250+
content: '';
251+
display: inline-block;
252+
width: 8px;
253+
height: 2px;
254+
margin-left: 2px;
255+
background: var(--liushen-text);
256+
vertical-align: bottom;
257+
animation: blink-underline 1s ease-in-out infinite;
258+
transition: all 0.3s;
259+
position: relative;
260+
bottom: 3px;
261+
}
262+
263+
/* 打字完成后隐藏光标 */
264+
.ai-summary .ai-explanation.typing-complete::after {
265+
display: none;
266+
}
267+
268+
.ai-summary .ai-title {
269+
z-index: 10;
270+
font-size: 14px;
271+
display: flex;
272+
border-radius: 8px;
273+
align-items: center;
274+
position: relative;
275+
padding: 0 12px;
276+
cursor: default;
277+
user-select: none;
278+
}
279+
280+
.ai-summary .ai-title .ai-title-left {
281+
display: flex;
282+
align-items: center;
283+
color: var(--liushen-title-font-color);
284+
}
285+
286+
.ai-summary .ai-title .ai-title-left i {
287+
margin-right: 3px;
288+
display: flex;
289+
color: var(--liushen-title-font-color);
290+
border-radius: 20px;
291+
justify-content: center;
292+
align-items: center;
293+
}
294+
295+
.ai-summary .ai-title .ai-title-left .ai-title-text {
296+
font-weight: 500;
297+
}
298+
299+
.ai-summary .ai-title .ai-tag {
300+
color: var(--liushen-secondtext);
301+
font-weight: 300;
302+
margin-left: auto;
303+
display: flex;
304+
align-items: center;
305+
justify-content: center;
306+
transition: 0.3s;
307+
}
308+
309+
/* ✅ 打字机光标闪烁动画 */
310+
@keyframes blink-underline {
311+
0%, 100% {
312+
opacity: 1;
313+
}
314+
50% {
315+
opacity: 0;
316+
}
194317
}

src/styles/variables.styl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ define({
2727
--page-bg: oklch(0.95 0.01 var(--hue)) oklch(0.16 0.014 var(--hue))
2828
--card-bg: white oklch(0.23 0.015 var(--hue))
2929

30+
// AI Summary 相关变量
31+
--liushen-title-font-color: #0883b7 #0883b7
32+
--liushen-maskbg: rgba(255, 255, 255, 0.85) rgba(0, 0, 0, 0.85)
33+
--liushen-ai-bg: conic-gradient(from 1.5708rad at 50% 50%, #d6b300 0%, #42A2FF 54%, #d6b300 100%) conic-gradient(from 1.5708rad at 50% 50%, rgba(214, 178, 0, 0.46) 0%, rgba(66, 161, 255, 0.53) 54%, rgba(214, 178, 0, 0.49) 100%)
34+
--liushen-card-secondbg: #f1f3f8 #3e3f41
35+
--liushen-text: #4c4948 #ffffffb3
36+
--liushen-secondtext: #3c3c43cc #a1a2b8
37+
3038
--btn-content: oklch(0.55 0.12 var(--hue)) oklch(0.75 0.1 var(--hue))
3139

3240
--btn-regular-bg: oklch(0.95 0.025 var(--hue)) oklch(0.33 0.035 var(--hue))

0 commit comments

Comments
 (0)