@astro-minimax/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/package.json +41 -0
- package/src/assets/icons/IconArchive.svg +1 -0
- package/src/assets/icons/IconArrowLeft.svg +1 -0
- package/src/assets/icons/IconArrowNarrowUp.svg +1 -0
- package/src/assets/icons/IconArrowRight.svg +1 -0
- package/src/assets/icons/IconArticle.svg +1 -0
- package/src/assets/icons/IconBrandX.svg +1 -0
- package/src/assets/icons/IconCalendar.svg +1 -0
- package/src/assets/icons/IconChevronLeft.svg +1 -0
- package/src/assets/icons/IconChevronRight.svg +1 -0
- package/src/assets/icons/IconEdit.svg +1 -0
- package/src/assets/icons/IconFacebook.svg +1 -0
- package/src/assets/icons/IconGitHub.svg +1 -0
- package/src/assets/icons/IconHash.svg +1 -0
- package/src/assets/icons/IconHome.svg +1 -0
- package/src/assets/icons/IconLinkedin.svg +1 -0
- package/src/assets/icons/IconMail.svg +1 -0
- package/src/assets/icons/IconMenuDeep.svg +1 -0
- package/src/assets/icons/IconMoon.svg +1 -0
- package/src/assets/icons/IconPinterest.svg +1 -0
- package/src/assets/icons/IconProject.svg +1 -0
- package/src/assets/icons/IconRss.svg +1 -0
- package/src/assets/icons/IconSearch.svg +1 -0
- package/src/assets/icons/IconSeries.svg +1 -0
- package/src/assets/icons/IconSunHigh.svg +1 -0
- package/src/assets/icons/IconTag.svg +1 -0
- package/src/assets/icons/IconTelegram.svg +1 -0
- package/src/assets/icons/IconUser.svg +1 -0
- package/src/assets/icons/IconWhatsapp.svg +1 -0
- package/src/assets/icons/IconX.svg +1 -0
- package/src/components/ai/AIChatWidget.astro +377 -0
- package/src/components/blog/Comments.astro +527 -0
- package/src/components/blog/Copyright.astro +152 -0
- package/src/components/blog/EditPost.astro +59 -0
- package/src/components/blog/FloatingTOC.astro +260 -0
- package/src/components/blog/InlineTOC.astro +223 -0
- package/src/components/blog/PostActions.astro +306 -0
- package/src/components/blog/RelatedPosts.astro +60 -0
- package/src/components/blog/SeriesNav.astro +176 -0
- package/src/components/blog/ShareLinks.astro +26 -0
- package/src/components/nav/BackButton.astro +37 -0
- package/src/components/nav/BackToTopButton.astro +223 -0
- package/src/components/nav/Breadcrumb.astro +57 -0
- package/src/components/nav/FloatingActions.astro +206 -0
- package/src/components/nav/Footer.astro +107 -0
- package/src/components/nav/Header.astro +252 -0
- package/src/components/nav/Pagination.astro +45 -0
- package/src/components/social/Socials.astro +19 -0
- package/src/components/social/Sponsors.astro +34 -0
- package/src/components/social/Sponsorship.astro +44 -0
- package/src/components/ui/Alert.astro +28 -0
- package/src/components/ui/Card.astro +206 -0
- package/src/components/ui/Collapse.astro +82 -0
- package/src/components/ui/ColorPreview.astro +29 -0
- package/src/components/ui/Datetime.astro +61 -0
- package/src/components/ui/GithubCard.astro +191 -0
- package/src/components/ui/LinkButton.astro +21 -0
- package/src/components/ui/Tag.astro +37 -0
- package/src/components/ui/TagCloud.astro +69 -0
- package/src/components/ui/Timeline.astro +39 -0
- package/src/layouts/AboutLayout.astro +24 -0
- package/src/layouts/Layout.astro +329 -0
- package/src/layouts/Main.astro +42 -0
- package/src/layouts/PostDetails.astro +445 -0
- package/src/plugins/rehype-autolink-headings.ts +46 -0
- package/src/plugins/rehype-external-links.ts +35 -0
- package/src/plugins/rehype-table-scroll.ts +35 -0
- package/src/plugins/remark-add-zoomable.ts +28 -0
- package/src/plugins/remark-reading-time.ts +18 -0
- package/src/plugins/shiki-transformers.ts +212 -0
- package/src/scripts/lightbox.ts +63 -0
- package/src/scripts/reading-position.ts +56 -0
- package/src/scripts/theme-utils.ts +19 -0
- package/src/scripts/theme.ts +179 -0
- package/src/scripts/web-vitals.ts +96 -0
- package/src/styles/code-blocks.css +194 -0
- package/src/styles/components.css +252 -0
- package/src/styles/global.css +403 -0
- package/src/styles/typography.css +149 -0
- package/src/types.ts +89 -0
- package/src/utils/generateOgImages.ts +38 -0
- package/src/utils/getCategoryPath.ts +23 -0
- package/src/utils/getPath.ts +52 -0
- package/src/utils/getPostsByCategory.ts +17 -0
- package/src/utils/getPostsByGroupCondition.ts +25 -0
- package/src/utils/getPostsByLang.ts +27 -0
- package/src/utils/getPostsByTag.ts +10 -0
- package/src/utils/getReadingTime.ts +33 -0
- package/src/utils/getRelatedPosts.ts +59 -0
- package/src/utils/getSeriesData.ts +57 -0
- package/src/utils/getSortedPosts.ts +18 -0
- package/src/utils/getTagsWithCount.ts +38 -0
- package/src/utils/getUniqueCategories.ts +81 -0
- package/src/utils/getUniqueTags.ts +23 -0
- package/src/utils/i18n.ts +249 -0
- package/src/utils/loadGoogleFont.ts +38 -0
- package/src/utils/og-templates/post.js +229 -0
- package/src/utils/og-templates/site.js +128 -0
- package/src/utils/pathUtils.ts +17 -0
- package/src/utils/postFilter.ts +11 -0
- package/src/utils/slugify.ts +23 -0
- package/src/utils/toc.ts +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @astro-minimax/core
|
|
2
|
+
|
|
3
|
+
Core theme package for [astro-minimax](https://github.com/souloss/astro-minimax).
|
|
4
|
+
|
|
5
|
+
Includes layouts, navigation components, blog components, UI components, social components, styles, utilities, and remark/rehype plugins.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @astro-minimax/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```astro
|
|
16
|
+
---
|
|
17
|
+
import Layout from '@astro-minimax/core/layouts/Layout.astro';
|
|
18
|
+
import Header from '@astro-minimax/core/components/nav/Header.astro';
|
|
19
|
+
import Footer from '@astro-minimax/core/components/nav/Footer.astro';
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<Layout title="My Page">
|
|
23
|
+
<Header />
|
|
24
|
+
<main><slot /></main>
|
|
25
|
+
<Footer />
|
|
26
|
+
</Layout>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
See the [documentation](https://github.com/souloss/astro-minimax) for full usage details.
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@astro-minimax/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Core theme package for astro-minimax — layouts, components, styles, utilities and plugins.",
|
|
6
|
+
"author": "Souloss",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"astro",
|
|
10
|
+
"astro-theme",
|
|
11
|
+
"blog",
|
|
12
|
+
"minimalist",
|
|
13
|
+
"tailwindcss"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/souloss/astro-minimax#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/souloss/astro-minimax/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/souloss/astro-minimax",
|
|
22
|
+
"directory": "packages/core"
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
"./layouts/*.astro": "./src/layouts/*.astro",
|
|
26
|
+
"./components/*.astro": "./src/components/*.astro",
|
|
27
|
+
"./styles/*.css": "./src/styles/*.css",
|
|
28
|
+
"./assets/icons/*.svg": "./src/assets/icons/*.svg",
|
|
29
|
+
"./utils/*": "./src/utils/*.ts",
|
|
30
|
+
"./plugins/*": "./src/plugins/*.ts",
|
|
31
|
+
"./scripts/*": "./src/scripts/*.ts",
|
|
32
|
+
"./types": "./src/types.ts"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"src/"
|
|
36
|
+
],
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"astro": "^5.0.0",
|
|
39
|
+
"tailwindcss": "^4.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-archive"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10" /><path d="M10 12l4 0" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M5 12l6 6" /><path d="M5 12l6 -6" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-narrow-up"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 5l0 14" /><path d="M16 9l-4 -4" /><path d="M8 9l4 -4" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M13 18l6 -6" /><path d="M13 6l6 6" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-article"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M7 8h10" /><path d="M7 12h10" /><path d="M7 16h10" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-x"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4l11.733 16h4.267l-11.733 -16z" /><path d="M4 20l6.768 -6.768m2.46 -2.46l6.772 -6.772" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-calendar-week"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12z" /><path d="M16 3v4" /><path d="M8 3v4" /><path d="M4 11h16" /><path d="M7 14h.013" /><path d="M10.01 14h.005" /><path d="M13.01 14h.005" /><path d="M16.015 14h.005" /><path d="M13.015 17h.005" /><path d="M7.01 17h.005" /><path d="M10.01 17h.005" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 6l-6 6l6 6" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 6l6 6l-6 6" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-edit"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1" /><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z" /><path d="M16 5l3 3" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-facebook"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 10v4h3v7h4v-7h3l1 -4h-4v-2a1 1 0 0 1 1 -1h3v-4h-3a5 5 0 0 0 -5 5v2h-3" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-github"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-hash"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 9l14 0" /><path d="M5 15l14 0" /><path d="M11 4l-4 16" /><path d="M17 4l-4 16" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-home"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l-2 0l9 -9l9 9l-2 0" /><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" /><path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-linkedin"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 11v5" /><path d="M8 8v.01" /><path d="M12 16v-5" /><path d="M16 16v-3a2 2 0 1 0 -4 0" /><path d="M3 7a4 4 0 0 1 4 -4h10a4 4 0 0 1 4 4v10a4 4 0 0 1 -4 4h-10a4 4 0 0 1 -4 -4z" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-mail"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z" /><path d="M3 7l9 6l9 -6" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-menu-deep"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6h16" /><path d="M7 12h13" /><path d="M10 18h10" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-moon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-pinterest"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 20l4 -9" /><path d="M10.7 14c.437 1.263 1.43 2 2.55 2c2.071 0 3.75 -1.554 3.75 -4a5 5 0 1 0 -9.7 1.7" /><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-code"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><polyline points="7 8 3 12 7 16" /><polyline points="17 8 21 12 17 16" /><line x1="14" y1="4" x2="10" y2="20" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-rss"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 19m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M4 4a16 16 0 0 1 16 16" /><path d="M4 11a9 9 0 0 1 9 9" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-search"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" /><path d="M21 21l-6 -6" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-books"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M9 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M5 8h4" /><path d="M9 16h4" /><path d="M13.803 4.56l2.184 -.56c.562 -.144 1.074 .245 1.144 .868l2.07 14.576c.07 .623 -.351 1.19 -.94 1.266l-2.184 .56c-.562 .144 -1.074 -.245 -1.144 -.868l-2.07 -14.576c-.07 -.623 .351 -1.19 .94 -1.266z" /><path d="M14 9.3l3.832 -.983" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-sun-high"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14.828 14.828a4 4 0 1 0 -5.656 -5.656a4 4 0 0 0 5.656 5.656z" /><path d="M6.343 17.657l-1.414 1.414" /><path d="M6.343 6.343l-1.414 -1.414" /><path d="M17.657 6.343l1.414 -1.414" /><path d="M17.657 17.657l1.414 1.414" /><path d="M4 12h-2" /><path d="M12 4v-2" /><path d="M20 12h2" /><path d="M12 20v2" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-tag"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7.5 7.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M3 6v5.172a2 2 0 0 0 .586 1.414l7.71 7.71a2.41 2.41 0 0 0 3.408 0l5.592 -5.592a2.41 2.41 0 0 0 0 -3.408l-7.71 -7.71a2 2 0 0 0 -1.414 -.586h-5.172a3 3 0 0 0 -3 3z" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-telegram"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 10l-4 4l6 6l4 -16l-18 7l4 2l2 6l3 -4" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-user"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-whatsapp"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 21l1.65 -3.8a9 9 0 1 1 3.4 2.9l-5.05 .9" /><path d="M9 10a.5 .5 0 0 0 1 0v-1a.5 .5 0 0 0 -1 0v1a5 5 0 0 0 5 5h1a.5 .5 0 0 0 0 -1h-1a.5 .5 0 0 0 0 1" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<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" class="icon icon-tabler icons-tabler-outline icon-tabler-x"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 6l-12 12" /><path d="M6 6l12 12" /></svg>
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SITE } from "@/config";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
lang?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { lang = "zh" } = Astro.props;
|
|
9
|
+
const aiEnabled = SITE.ai?.enabled ?? false;
|
|
10
|
+
const aiConfig = {
|
|
11
|
+
apiEndpoint: SITE.ai?.apiEndpoint || "",
|
|
12
|
+
apiKey: SITE.ai?.apiKey || "",
|
|
13
|
+
model: SITE.ai?.model || "gpt-4o-mini",
|
|
14
|
+
maxTokens: SITE.ai?.maxTokens || 1024,
|
|
15
|
+
systemPrompt: SITE.ai?.systemPrompt || "",
|
|
16
|
+
mockMode: SITE.ai?.mockMode ?? true,
|
|
17
|
+
vectorSearch: SITE.ai?.vectorSearch ?? false,
|
|
18
|
+
};
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
aiEnabled && (
|
|
23
|
+
<div id="ai-chat-root" data-lang={lang}>
|
|
24
|
+
<div
|
|
25
|
+
id="ai-chat-panel"
|
|
26
|
+
class="fixed right-6 bottom-20 z-50 hidden w-[360px] max-w-[calc(100vw-2rem)] flex-col overflow-hidden rounded-2xl border border-border bg-background shadow-2xl"
|
|
27
|
+
style="height: min(520px, calc(100vh - 8rem)); bottom: 5rem;"
|
|
28
|
+
>
|
|
29
|
+
{/* Header */}
|
|
30
|
+
<div class="flex items-center justify-between border-b border-border px-4 py-3">
|
|
31
|
+
<div class="flex items-center gap-2">
|
|
32
|
+
<svg
|
|
33
|
+
class="size-5 text-accent"
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
fill="none"
|
|
36
|
+
stroke="currentColor"
|
|
37
|
+
stroke-width="2"
|
|
38
|
+
stroke-linecap="round"
|
|
39
|
+
stroke-linejoin="round"
|
|
40
|
+
>
|
|
41
|
+
<path d="M12 8V4H8" />
|
|
42
|
+
<rect width="16" height="12" x="4" y="8" rx="2" />
|
|
43
|
+
<path d="M2 14h2" />
|
|
44
|
+
<path d="M20 14h2" />
|
|
45
|
+
<path d="M15 13v2" />
|
|
46
|
+
<path d="M9 13v2" />
|
|
47
|
+
</svg>
|
|
48
|
+
<span class="text-sm font-semibold text-foreground">
|
|
49
|
+
AI Assistant
|
|
50
|
+
</span>
|
|
51
|
+
<span class:list={[
|
|
52
|
+
"rounded-full px-2 py-0.5 text-[10px] font-medium",
|
|
53
|
+
aiConfig.apiEndpoint ? "bg-green-500/15 text-green-600 dark:text-green-400" : "bg-accent/15 text-accent"
|
|
54
|
+
]}>
|
|
55
|
+
{aiConfig.apiEndpoint ? "Live" : "Mock"}
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="flex items-center gap-1">
|
|
59
|
+
<button
|
|
60
|
+
id="ai-chat-clear"
|
|
61
|
+
class="text-foreground-soft rounded-md p-1.5 transition-colors hover:bg-muted/60 hover:text-foreground"
|
|
62
|
+
aria-label="Clear conversation"
|
|
63
|
+
title="Clear conversation"
|
|
64
|
+
>
|
|
65
|
+
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
66
|
+
<path d="M3 6h18" /><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" /><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
|
67
|
+
</svg>
|
|
68
|
+
</button>
|
|
69
|
+
<button
|
|
70
|
+
id="ai-chat-close"
|
|
71
|
+
class="text-foreground-soft rounded-md p-1.5 transition-colors hover:bg-muted/60 hover:text-foreground"
|
|
72
|
+
aria-label="Close chat"
|
|
73
|
+
title="Close"
|
|
74
|
+
>
|
|
75
|
+
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
76
|
+
<path d="M18 6 6 18" /><path d="m6 6 12 12" />
|
|
77
|
+
</svg>
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{/* Messages */}
|
|
83
|
+
<div id="ai-chat-messages" class="flex-1 space-y-3 overflow-y-auto px-4 py-3">
|
|
84
|
+
<div class="flex gap-2" data-role="assistant">
|
|
85
|
+
<div class="mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full bg-accent/15">
|
|
86
|
+
<svg class="size-3.5 text-accent" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
87
|
+
<path d="M12 8V4H8" /><rect width="16" height="12" x="4" y="8" rx="2" />
|
|
88
|
+
<path d="M2 14h2" /><path d="M20 14h2" /><path d="M15 13v2" /><path d="M9 13v2" />
|
|
89
|
+
</svg>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="rounded-lg bg-muted/40 px-3 py-2 text-sm leading-relaxed text-foreground" id="ai-welcome-msg" />
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Input */}
|
|
96
|
+
<div class="border-t border-border px-3 py-3">
|
|
97
|
+
<form id="ai-chat-form" class="flex gap-2">
|
|
98
|
+
<input
|
|
99
|
+
id="ai-chat-input"
|
|
100
|
+
type="text"
|
|
101
|
+
placeholder=""
|
|
102
|
+
class="placeholder:text-foreground-soft flex-1 rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground focus:border-accent focus:ring-1 focus:ring-accent focus:outline-none"
|
|
103
|
+
autocomplete="off"
|
|
104
|
+
/>
|
|
105
|
+
<button
|
|
106
|
+
type="submit"
|
|
107
|
+
id="ai-chat-send"
|
|
108
|
+
class="shrink-0 rounded-lg bg-accent px-3 py-2 text-sm font-medium text-background transition-colors hover:bg-accent/90 disabled:opacity-50"
|
|
109
|
+
disabled
|
|
110
|
+
>
|
|
111
|
+
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
112
|
+
<path d="m22 2-7 20-4-9-9-4Z" /><path d="M22 2 11 13" />
|
|
113
|
+
</svg>
|
|
114
|
+
</button>
|
|
115
|
+
</form>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<script is:inline define:vars={{ aiConfig }}>
|
|
121
|
+
function initAIChat() {
|
|
122
|
+
const root = document.getElementById("ai-chat-root");
|
|
123
|
+
if (!root) return;
|
|
124
|
+
|
|
125
|
+
const lang = root.dataset.lang || "zh";
|
|
126
|
+
const toggle = document.getElementById("ai-chat-toggle-fab");
|
|
127
|
+
const panel = document.getElementById("ai-chat-panel");
|
|
128
|
+
const close = document.getElementById("ai-chat-close");
|
|
129
|
+
const clear = document.getElementById("ai-chat-clear");
|
|
130
|
+
const form = document.getElementById("ai-chat-form");
|
|
131
|
+
const input = document.getElementById("ai-chat-input");
|
|
132
|
+
const send = document.getElementById("ai-chat-send");
|
|
133
|
+
const messages = document.getElementById("ai-chat-messages");
|
|
134
|
+
const welcomeMsg = document.getElementById("ai-welcome-msg");
|
|
135
|
+
|
|
136
|
+
if (!toggle || !panel || !close || !clear || !form || !input || !send || !messages || !welcomeMsg) return;
|
|
137
|
+
|
|
138
|
+
const i18n = {
|
|
139
|
+
zh: {
|
|
140
|
+
welcome: "你好!我是 AI 助手,可以帮你了解这个博客的内容。试试问我关于文章、技术或任何你感兴趣的话题吧!",
|
|
141
|
+
placeholder: "输入你的问题...",
|
|
142
|
+
thinking: "正在思考...",
|
|
143
|
+
},
|
|
144
|
+
en: {
|
|
145
|
+
welcome: "Hi! I'm the AI assistant. I can help you explore the blog content. Try asking me about articles, tech topics, or anything you're curious about!",
|
|
146
|
+
placeholder: "Type your question...",
|
|
147
|
+
thinking: "Thinking...",
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const t = lang === "zh" ? i18n.zh : i18n.en;
|
|
152
|
+
welcomeMsg.textContent = t.welcome;
|
|
153
|
+
input.placeholder = t.placeholder;
|
|
154
|
+
|
|
155
|
+
let isOpen = false;
|
|
156
|
+
let isStreaming = false;
|
|
157
|
+
const chatHistory = [];
|
|
158
|
+
|
|
159
|
+
toggle.addEventListener("click", () => {
|
|
160
|
+
isOpen = !isOpen;
|
|
161
|
+
panel.classList.toggle("hidden", !isOpen);
|
|
162
|
+
panel.classList.toggle("flex", isOpen);
|
|
163
|
+
if (isOpen) input.focus();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
close.addEventListener("click", () => {
|
|
167
|
+
isOpen = false;
|
|
168
|
+
panel.classList.add("hidden");
|
|
169
|
+
panel.classList.remove("flex");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
clear.addEventListener("click", () => {
|
|
173
|
+
const welcome = messages.querySelector("[data-role='assistant']");
|
|
174
|
+
messages.innerHTML = "";
|
|
175
|
+
if (welcome) messages.appendChild(welcome);
|
|
176
|
+
chatHistory.length = 0;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
input.addEventListener("input", () => {
|
|
180
|
+
send.disabled = !input.value.trim() || isStreaming;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
form.addEventListener("submit", async (e) => {
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
const question = input.value.trim();
|
|
186
|
+
if (!question || isStreaming) return;
|
|
187
|
+
|
|
188
|
+
appendMessage("user", question);
|
|
189
|
+
chatHistory.push({ role: "user", content: question });
|
|
190
|
+
input.value = "";
|
|
191
|
+
send.disabled = true;
|
|
192
|
+
isStreaming = true;
|
|
193
|
+
|
|
194
|
+
const useMock = aiConfig.mockMode || !aiConfig.apiEndpoint;
|
|
195
|
+
|
|
196
|
+
if (useMock) {
|
|
197
|
+
await streamMockResponse(question, lang);
|
|
198
|
+
} else {
|
|
199
|
+
await streamRealResponse(question, lang);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
isStreaming = false;
|
|
203
|
+
send.disabled = !input.value.trim();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
function appendMessage(role, content) {
|
|
207
|
+
const wrapper = document.createElement("div");
|
|
208
|
+
wrapper.className = "flex gap-2" + (role === "user" ? " justify-end" : "");
|
|
209
|
+
wrapper.dataset.role = role;
|
|
210
|
+
|
|
211
|
+
if (role === "user") {
|
|
212
|
+
wrapper.innerHTML = '<div class="rounded-lg bg-accent px-3 py-2 text-sm text-background leading-relaxed max-w-[85%]">' + escapeHtml(content) + '</div>';
|
|
213
|
+
} else {
|
|
214
|
+
wrapper.innerHTML =
|
|
215
|
+
'<div class="shrink-0 mt-0.5 flex size-6 items-center justify-center rounded-full bg-accent/15">' +
|
|
216
|
+
'<svg class="size-3.5 text-accent" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"></path><rect width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg>' +
|
|
217
|
+
'</div>' +
|
|
218
|
+
'<div class="rounded-lg bg-muted/40 px-3 py-2 text-sm text-foreground leading-relaxed max-w-[85%]">' + content + '</div>';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
messages.appendChild(wrapper);
|
|
222
|
+
messages.scrollTop = messages.scrollHeight;
|
|
223
|
+
return wrapper;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ===== Real API streaming via SSE =====
|
|
227
|
+
async function streamRealResponse(_question, lang) {
|
|
228
|
+
const msgEl = appendMessage("assistant", "");
|
|
229
|
+
const textEl = msgEl.querySelector(".rounded-lg.bg-muted\\/40");
|
|
230
|
+
if (!textEl) return;
|
|
231
|
+
|
|
232
|
+
textEl.innerHTML = '<span class="inline-flex gap-1 text-foreground-soft"><span class="animate-pulse">●</span><span class="animate-pulse" style="animation-delay:0.2s">●</span><span class="animate-pulse" style="animation-delay:0.4s">●</span></span>';
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const systemMessages = [
|
|
236
|
+
{ role: "system", content: aiConfig.systemPrompt || "You are a helpful blog assistant." }
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
const recentHistory = chatHistory.slice(-10);
|
|
240
|
+
|
|
241
|
+
const requestBody = {
|
|
242
|
+
model: aiConfig.model,
|
|
243
|
+
messages: [...systemMessages, ...recentHistory],
|
|
244
|
+
max_tokens: aiConfig.maxTokens,
|
|
245
|
+
stream: true,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const headers = {
|
|
249
|
+
"Content-Type": "application/json",
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (aiConfig.apiKey) {
|
|
253
|
+
headers["Authorization"] = "Bearer " + aiConfig.apiKey;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const response = await fetch(aiConfig.apiEndpoint, {
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers,
|
|
259
|
+
body: JSON.stringify(requestBody),
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
if (!response.ok) {
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
textEl.textContent = (lang === "zh" ? "API 错误: " : "API Error: ") + response.status;
|
|
266
|
+
chatHistory.push({ role: "assistant", content: textEl.textContent });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const reader = response.body.getReader();
|
|
271
|
+
const decoder = new TextDecoder();
|
|
272
|
+
let fullText = "";
|
|
273
|
+
let buffer = "";
|
|
274
|
+
|
|
275
|
+
while (true) {
|
|
276
|
+
const { done, value } = await reader.read();
|
|
277
|
+
if (done) break;
|
|
278
|
+
|
|
279
|
+
buffer += decoder.decode(value, { stream: true });
|
|
280
|
+
const lines = buffer.split("\n");
|
|
281
|
+
buffer = lines.pop() || "";
|
|
282
|
+
|
|
283
|
+
for (const line of lines) {
|
|
284
|
+
const trimmed = line.trim();
|
|
285
|
+
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
286
|
+
const data = trimmed.slice(6);
|
|
287
|
+
if (data === "[DONE]") break;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const parsed = JSON.parse(data);
|
|
291
|
+
const delta = parsed.choices?.[0]?.delta?.content;
|
|
292
|
+
if (delta) {
|
|
293
|
+
fullText += delta;
|
|
294
|
+
textEl.textContent = fullText;
|
|
295
|
+
messages.scrollTop = messages.scrollHeight;
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
// skip malformed JSON chunks
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!fullText) {
|
|
304
|
+
textEl.textContent = lang === "zh" ? "未收到回复" : "No response received";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
chatHistory.push({ role: "assistant", content: fullText || textEl.textContent });
|
|
308
|
+
|
|
309
|
+
} catch (err) {
|
|
310
|
+
textEl.textContent = (lang === "zh" ? "连接失败: " : "Connection failed: ") + (err.message || err);
|
|
311
|
+
chatHistory.push({ role: "assistant", content: textEl.textContent });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ===== Mock streaming =====
|
|
316
|
+
async function streamMockResponse(question, lang) {
|
|
317
|
+
const response = getMockResponse(question, lang);
|
|
318
|
+
const msgEl = appendMessage("assistant", "");
|
|
319
|
+
const textEl = msgEl.querySelector(".rounded-lg.bg-muted\\/40");
|
|
320
|
+
if (!textEl) return;
|
|
321
|
+
|
|
322
|
+
textEl.innerHTML = '<span class="inline-flex gap-1 text-foreground-soft"><span class="animate-pulse">●</span><span class="animate-pulse" style="animation-delay:0.2s">●</span><span class="animate-pulse" style="animation-delay:0.4s">●</span></span>';
|
|
323
|
+
|
|
324
|
+
await sleep(600);
|
|
325
|
+
|
|
326
|
+
let displayed = "";
|
|
327
|
+
for (let i = 0; i < response.length; i++) {
|
|
328
|
+
displayed += response[i];
|
|
329
|
+
textEl.textContent = displayed;
|
|
330
|
+
messages.scrollTop = messages.scrollHeight;
|
|
331
|
+
await sleep(15 + Math.random() * 25);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
chatHistory.push({ role: "assistant", content: response });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function getMockResponse(question, lang) {
|
|
338
|
+
const q = question.toLowerCase();
|
|
339
|
+
const isZh = lang === "zh";
|
|
340
|
+
|
|
341
|
+
if (q.includes("astro") || q.includes("框架")) {
|
|
342
|
+
return isZh
|
|
343
|
+
? "Astro 是一个现代化的静态站点生成器,特别适合内容驱动的网站。它的核心优势是「岛屿架构」——默认零 JavaScript,只在需要交互的组件上加载 JS。本博客就是基于 Astro 构建的,使用了 Tailwind CSS 进行样式处理,支持 MDX、代码高亮、数学公式等丰富功能。"
|
|
344
|
+
: "Astro is a modern static site generator perfect for content-driven websites. Its core feature is the 'Islands Architecture' — zero JavaScript by default, loading JS only for interactive components. This blog is built with Astro, using Tailwind CSS for styling, with support for MDX, code highlighting, math equations, and more.";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (q.includes("博客") || q.includes("blog") || q.includes("文章") || q.includes("post")) {
|
|
348
|
+
return isZh
|
|
349
|
+
? "这个博客使用 astro-minimax 主题,支持多种功能:Markdown/MDX 文章、代码语法高亮、数学公式(KaTeX)、Mermaid 图表、标签分类系统、全文搜索(Pagefind)、评论系统(Waline)、深色模式等。文章按中文和英文分别存放在不同目录中。"
|
|
350
|
+
: "This blog uses the astro-minimax theme with many features: Markdown/MDX posts, syntax highlighting, math equations (KaTeX), Mermaid diagrams, tag/category system, full-text search (Pagefind), comments (Waline), dark mode, and more. Articles are organized in separate directories for Chinese and English.";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (q.includes("主题") || q.includes("theme") || q.includes("暗色") || q.includes("dark")) {
|
|
354
|
+
return isZh
|
|
355
|
+
? "本博客支持亮色和暗色两种主题模式,可以通过右下角的主题切换按钮自由切换。主题颜色方案可以在配置文件中自定义,支持多种预定义配色方案。系统会自动检测你的系统偏好设置。"
|
|
356
|
+
: "This blog supports both light and dark theme modes, switchable via the theme toggle button in the bottom right. Color schemes can be customized in the config file with multiple predefined options. The system automatically detects your OS preference.";
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return isZh
|
|
360
|
+
? "感谢你的提问!作为 AI 助手,我目前运行在 Mock 模式下,能回答关于本博客技术栈、功能特性等基本问题。如需更详细的回答,站长可以配置真实的 AI API 来获得更智能的对话体验。你可以尝试问我关于 Astro、博客功能、主题配置等话题。"
|
|
361
|
+
: "Thanks for your question! I'm currently running in mock mode and can answer basic questions about this blog's tech stack and features. For more detailed answers, the site admin can configure a real AI API for a smarter conversation experience. Try asking about Astro, blog features, or theme configuration.";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function escapeHtml(str) {
|
|
365
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function sleep(ms) {
|
|
369
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
initAIChat();
|
|
374
|
+
document.addEventListener("astro:after-swap", initAIChat);
|
|
375
|
+
</script>
|
|
376
|
+
)
|
|
377
|
+
}
|