Skip to content

Commit 4c48eed

Browse files
committed
文章内容区添加AI 生成提示标识
1 parent efe27fa commit 4c48eed

5 files changed

Lines changed: 265 additions & 44 deletions

File tree

astro.config.mjs

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,13 @@ import rehypeCallouts from 'rehype-callouts';
1515
import rehypeKatex from 'rehype-katex';
1616
import remarkBreaks from 'remark-breaks';
1717
import remarkMath from 'remark-math';
18-
19-
20-
2118
import AstroPureIntegration from './packages/pure/index.ts';
2219
// Local integrations
2320
// Local rehype & remark plugins
2421
import rehypeAutolinkHeadings from './src/plugins/rehype-auto-link-headings.ts';
2522
import { remarkMermaid } from './src/plugins/remark-mermaid';
23+
import { remarkAiNotice } from './src/plugins/remark-ai-notice.mjs';
2624
import config from './src/site.config.ts';
27-
28-
29-
;
30-
31-
32-
33-
34-
35-
36-
37-
38-
39-
40-
41-
42-
43-
44-
45-
46-
47-
48-
49-
50-
51-
52-
53-
54-
55-
5625

5726
// https://astro.build/config
5827
export default defineConfig({
@@ -160,7 +129,8 @@ export default defineConfig({
160129
},
161130
}],
162131
remarkBreaks,
163-
remarkMermaid
132+
remarkMermaid,
133+
remarkAiNotice
164134
],
165135
rehypePlugins: [
166136
[rehypeKatex, {}],

public/styles/global.css

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,123 @@ body::-webkit-scrollbar {
235235
::-webkit-scrollbar-track {
236236
background: transparent;
237237
}
238+
239+
/* AI Notice */
240+
.ai-notice {
241+
position: relative;
242+
display: flex;
243+
flex-direction: column;
244+
padding: 16px;
245+
margin-bottom: 20px;
246+
border-radius: 8px;
247+
color: #8a6d3b;
248+
/* background-color: #fcf8e3; */
249+
background-color: hsl(var(--accent));
250+
overflow: hidden;
251+
}
252+
253+
.ai-notice::before {
254+
content: '';
255+
position: absolute;
256+
top: 0; right: 0; bottom: 0; left: 0;
257+
z-index: -1;
258+
margin: -2px;
259+
border-radius: inherit;
260+
background: linear-gradient(45deg, #f0ad4e, #f8e3a1, #f0ad4e);
261+
}
262+
263+
.ai-notice-robot-icon {
264+
position: absolute;
265+
top: 12px;
266+
right: 12px;
267+
color: #8a6d3b;
268+
opacity: 0.5;
269+
}
270+
271+
.ai-notice-text p {
272+
margin: 0;
273+
line-height: 1.6;
274+
padding-right: 30px; /* Add padding to avoid text overlapping the icon */
275+
}
276+
277+
.ai-notice-text p:first-child {
278+
font-weight: bold;
279+
}
280+
281+
.ai-notice-footer {
282+
display: flex;
283+
justify-content: flex-end; /* Align items to the right */
284+
align-items: center;
285+
flex-wrap: wrap;
286+
gap: 10px;
287+
margin-top: 12px;
288+
padding-top: 12px;
289+
border-top: 1px solid rgba(138, 109, 59, 0.2);
290+
font-size: 0.8rem;
291+
}
292+
293+
.ai-notice-models {
294+
display: flex;
295+
align-items: center;
296+
gap: 6px;
297+
flex-wrap: wrap;
298+
}
299+
300+
.ai-model-tag {
301+
padding: 2px 8px;
302+
border-radius: 4px;
303+
background-color: rgba(138, 109, 59, 0.1);
304+
color: #8a6d3b;
305+
font-weight: 500;
306+
}
307+
308+
.publish-date {
309+
color: #8a6d3b;
310+
white-space: nowrap;
311+
}
312+
313+
/* Dark Mode Styles */
314+
.dark .ai-notice {
315+
color: #c0a97a;
316+
/* background-color: #2c2a22; */
317+
background-color: hsl(var(--accent));
318+
}
319+
320+
.dark .ai-notice::before {
321+
background: linear-gradient(45deg, #f0ad4e, #5c5a52, #f0ad4e);
322+
}
323+
324+
.dark .ai-notice-robot-icon,
325+
.dark .publish-date,
326+
.dark .ai-model-tag {
327+
color: #c0a97a;
328+
}
329+
330+
.dark .ai-model-tag {
331+
background-color: rgba(192, 169, 122, 0.1);
332+
}
333+
334+
.dark .ai-notice-footer {
335+
border-top-color: rgba(192, 169, 122, 0.2);
336+
}
337+
338+
/* AI Tag Icon in Post Preview */
339+
/* .ai-tag-icon {
340+
display: inline-flex;
341+
align-items: center;
342+
margin-left: 8px;
343+
color: hsl(var(--muted-foreground));
344+
transition: color 0.3s ease-in-out;
345+
}
346+
347+
.post-preview:hover .ai-tag-icon {
348+
color: var(--preview-highlight-final, hsl(var(--primary)));
349+
}
350+
351+
.dark .ai-tag-icon {
352+
color: hsl(var(--muted-foreground));
353+
}
354+
355+
.dark .post-preview:hover .ai-tag-icon {
356+
color: var(--preview-highlight-final, hsl(var(--primary)));
357+
} */
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: 概率数据结构-Bloom Filter
3+
publishDate: 2025-07-15
4+
description: 'everything you need to know about Bloom Filter'
5+
tags:
6+
- data-structure
7+
- algorithm
8+
- bloom-filter
9+
- probability
10+
- ai-generate
11+
language: '中文'
12+
---

src/content/blog/test/a.mdx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
---
22
title: 我是 a 的标题
3-
publishDate: 2025-07-27
3+
publishDate: 2025-07-01
44
description: 'astro-theme-pure Personalized Customization Guide'
55
tags:
66
- Waline
77
- Vercel
88
- Supabase
9-
language: 'English'
9+
language: 'english'
10+
ai-model:
11+
- gemini-2.5-pro
1012
redirect_from:
1113
- hhh
1214
- ggg
@@ -97,15 +99,6 @@ redirect_from:
9799
>
98100
> after
99101
100-
<iframe
101-
style="border: 1px solid #ccc; border-radius: 0.5rem;"
102-
src="https://inscribed.app/embed?type=slider-template&gist_url=https://gist.github.com/CatCodeMe/05e95119deae1602c4870d3015ccf790"
103-
width="100%"
104-
height="500"
105-
frameborder="0"
106-
allowfullscreen
107-
></iframe>
108-
109102
---
110103

111104
<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.

src/plugins/remark-ai-notice.mjs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* @typedef {import('unist').Node} Node
3+
* @typedef {import('vfile').VFile} VFile
4+
*/
5+
6+
const notice = {
7+
en: {
8+
title: 'AI-Assisted Content',
9+
body: 'This article is AI-assisted, and the author has strived for accuracy. Please use with discretion.',
10+
dateTemplates: {
11+
today: 'Posted today',
12+
singular: 'Posted 1 day ago',
13+
plural: 'Posted {days} days ago'
14+
}
15+
},
16+
zh: {
17+
title: 'AI-Assisted Content',
18+
body: '本文由AI辅助生成,作者已尽力确保内容准确,请谨慎参考',
19+
dateTemplates: {
20+
today: '发布于今天',
21+
singular: '发布于 1 天前',
22+
plural: '发布于 {days} 天前'
23+
}
24+
}
25+
}
26+
27+
/**
28+
* A remark plugin to add a notice to posts tagged with 'ai-generation'.
29+
*
30+
* @returns {(tree: Node, file: VFile) => void}
31+
*/
32+
export function remarkAiNotice() {
33+
return (tree, file) => {
34+
const frontmatter = file.data.astro?.frontmatter;
35+
const aiModels = frontmatter?.['ai-model'];
36+
const shouldShowNotice = aiModels && ((Array.isArray(aiModels) && aiModels.length > 0) || (typeof aiModels === 'string' && aiModels.trim() !== ''));
37+
38+
if (frontmatter && shouldShowNotice) {
39+
const lang = frontmatter.language?.toLowerCase() === 'english' ? 'en' : 'zh';
40+
const noticeText = notice[lang];
41+
42+
const modelsArray = Array.isArray(aiModels) ? aiModels : [aiModels];
43+
const modelsHtml = `
44+
<div class="ai-notice-models">
45+
${modelsArray.map(model => `<span class="ai-model-tag">${model}</span>`).join('')}
46+
</div>`;
47+
48+
const pubDate = frontmatter.publishDate || frontmatter.pubDate || frontmatter.date
49+
let dateHtml = ''
50+
if (pubDate) {
51+
const date = new Date(pubDate)
52+
dateHtml = `<span class="publish-date"
53+
data-publish-date="${date.toISOString()}"
54+
data-template-today="${noticeText.dateTemplates.today}"
55+
data-template-singular="${noticeText.dateTemplates.singular}"
56+
data-template-plural="${noticeText.dateTemplates.plural}">
57+
</span>`
58+
}
59+
60+
const noticeNode = {
61+
type: 'html',
62+
value: `
63+
<div class="ai-notice not-prose">
64+
<div class="ai-notice-robot-icon">
65+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>
66+
</div>
67+
<div class="ai-notice-text">
68+
<p><strong>${noticeText.title}</strong></p>
69+
<p>${noticeText.body}</p>
70+
</div>
71+
${(modelsHtml || dateHtml) ?
72+
`<div class="ai-notice-footer">
73+
${modelsHtml}
74+
${dateHtml}
75+
</div>` : ''}
76+
</div>
77+
<script>
78+
if (!window.hasInitializedAiNoticeScript) {
79+
const calculateDays = () => {
80+
const dateElements = document.querySelectorAll('.publish-date[data-publish-date]');
81+
if (dateElements.length === 0) return;
82+
83+
const today = new Date();
84+
today.setHours(0, 0, 0, 0);
85+
86+
dateElements.forEach(el => {
87+
const pubDateStr = el.dataset.publishDate;
88+
if (!pubDateStr) return;
89+
90+
const pubDate = new Date(pubDateStr);
91+
pubDate.setHours(0, 0, 0, 0);
92+
93+
const diffTime = today.getTime() - pubDate.getTime();
94+
const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24));
95+
96+
if (diffDays < 0) return;
97+
98+
let text;
99+
if (diffDays === 0) {
100+
text = el.dataset.templateToday;
101+
} else if (diffDays === 1) {
102+
text = el.dataset.templateSingular;
103+
} else {
104+
text = el.dataset.templatePlural.replace('{days}', diffDays);
105+
}
106+
el.textContent = text;
107+
});
108+
};
109+
110+
if (document.readyState === 'loading') {
111+
document.addEventListener('DOMContentLoaded', calculateDays);
112+
} else {
113+
calculateDays();
114+
}
115+
window.hasInitializedAiNoticeScript = true;
116+
}
117+
</script>
118+
`,
119+
}
120+
121+
if (tree.children) {
122+
tree.children.unshift(noticeNode)
123+
}
124+
}
125+
}
126+
}

0 commit comments

Comments
 (0)