Content
# Tool List
> 🚀 **Astro Full-featured Blog Template with AI Q&A** — Built-in RAG Q&A, article reading, comments, net disk, short links, and resource station, deployed with EdgeOne Makers, ready to use out of the box.
[](https://www.qiyuan.icu)
[](./LICENSE)
A personalized blog and tech resource station template deeply customized based on [Astro](https://astro.build) (derived from [Koharu](https://github.com/koharu-org/koharu) theme), with online demo: [https://www.qiyuan.icu](https://www.qiyuan.icu).
## Function Page Screenshots

> The above image is the homepage of the **example site (qiyuan.icu, with all optional features enabled)**: top hero video section, author card, article list, AI assistant floating window, navigation bar resource/net disk/short link entrance, etc. The template disables these optional features by default, and you can enable them as needed after cloning.
| Music List | Temporary Email |
| ------------------------------- | ---------------------------------- |
|  |  |
| Net Disk | Resource Station |
| ------------------------------- | ---------------------------------- |
|  |  |
> This repository **`qiyuan-blog-template`** is a **blog template**: click **Use this template → Create a new repository** on the GitHub repository page to create your own blog repository with one click, then modify `config/site.yaml`, fill in articles, and configure environment variables to deploy. For deployment, we recommend **EdgeOne Makers**, a domestic-friendly platform, see "Using as a Template" below (the resource station is an independent sub-project and needs to be deployed separately, see "Resource Station Setup Tutorial").
## 🚀 Using as a Template
1. Go to this repository **`qiyuan-blog-template`** page, click **Use this template → Create a new repository**, and fill in your new repository name (can be named arbitrarily).
2. `git clone` to local, `npm install`.
3. Copy `.env.example` to `.env`, and fill in environment variables for each feature as needed (if not filled, the corresponding feature will be automatically disabled).
4. Edit `config/site.yaml`: change `site.title` / `url` / `avatar` / `social` to your own information.
5. Delete `src/content/blog/welcome.md`, and add your own `.md` articles.
6. Deploy to EdgeOne Makers (or other Astro-compatible platforms), and configure environment variables in the platform's environment variables that are the same as those in `.env`.
## Optional Features Disabled by Default
To keep the template pure and usable out of the box, **the following optional features are disabled by default**, and you can enable them as needed after cloning:
| Feature | Configuration Switch | Default Value | Enable Method |
| --- | --- | --- | --- |
| Comments | `comment.provider` | `none` (disabled) | Change to `giscus` / `waline` / `twikoo` / `custom` and fill in corresponding configuration |
| In-site AI Assistant (RAG) | `rag.enabled` | `false` | Set to `true`, and configure `SILICONFLOW_API_KEY` / `NEON_SERVERLESS_URL`, etc. |
| Resource Station | `resources.enabled` | `false` | Set to `true`, and deploy `resource-site/` separately (see "Resource Station Setup Tutorial") |
| Temporary Email | `tempMail.enabled` | `false` | Set to `true`, and deploy receiving Worker + D1 + Email Routing on Cloudflare |
| Article Reading | Article frontmatter `audio: true` | Not set, i.e., disabled | Add `audio: true` at the top of the article, and configure `SILICONFLOW_API_KEY` + R2 |
| Net Disk | Navigation item `enabled` | `false` | Change navigation item to `enabled: true` + deploy `/api/drive/*` (R2/S3 + KV) |
| Short Links | Navigation item `enabled` | `false` | Change navigation item to `enabled: true` + deploy `/api/shorten/*` |
> Description: comments / temporary email / article reading are "zero-dependency or light-dependency", which can be used after configuration; in-site AI assistant / resource station / net disk / short links depend on edge functions and external services (Neon / Supabase / R2 / Cloudflare), and you need to configure corresponding environment variables on the deployment platform. See "Feature Characteristics" and each `docs/*.md` for complete configuration.
## Tech Stack
- **Astro 5** (SSG) + customized theme, supporting three languages: Chinese / English / Japanese (`[lang]/` mirror routing).
- **Deployment Platform: EdgeOne Makers** (Tencent Cloud edge function / cloud function). The site `qiyuan.icu` runs on the edge, and full-text search uses Pagefind.
- **Edge Runtime**:
- `edge-functions/api/[[default]].js` — EdgeOne edge function monolith, carrying RAG chat, temporary email, comments, upload, MCP, statistics, and other APIs.
- `cloud-functions/` — Makers cloud functions (e.g., blog image upload `api/images/upload`).
- **External Dependencies**: Neon (Postgres + pgvector, blog article vector database), Supabase (resource station vector database), SiliconFlow (bge-m3 / bge-reranker embedding and re-ranking), SenseNova and ModelScope + ZhiPu (generation models), Cloudflare (KV comment storage + R2 comment images + temporary email Worker/D1).
## Feature Characteristics
- **Blog Articles**: Markdown/MDX, cover images, directories, Pagefind in-site search; support language switching.
- **In-site AI Assistant (RAG) "Ask AI about this article"**:
- Retrieval: blog articles (Neon + pgvector `rag_posts` cosine retrieval) + resource station (Supabase `products` **hybrid retrieval**: vector + lexical `ilike`, RRF fusion), each candidate is re-ranked and sent to the context.
- Embedding `BAAI/bge-m3`, re-ranking `BAAI/bge-reranker-v2-m3` (both via SiliconFlow, free).
- Generation: **優先商汤 sensenova `deepseek-v4-flash` (multi-key rotation, thinking mode disabled)**, with ModelScope / ZhiPu GLM / SiliconFlow DeepSeek as backups.
- **Used large model platforms**:
- **SiliconFlow**: embedding `BAAI/bge-m3` + re-ranking `BAAI/bge-reranker-v2-m3` (free), and used as final backup for generation (DeepSeek).
- **SenseNova**: generation priority, model `deepseek-v4-flash` (multi-key rotation, thinking mode disabled).
- **ModelScope**: generation backup, `Llama-3.3-70B` (domestic direct connection, free).
- **ZhiPu GLM**: generation backup, `GLM-4.7-Flash` (permanent free, domestic direct connection).
- Performance optimization: **Neon preheating** (request-internal `SELECT 1` and embedding concurrency, eliminating scale-to-zero cold start), **problem vector caching** (LRU 5 items, skipping repeated embedding calls for the same problem).
- **Temporary Email** `qiyuanmail.cc.cd`: Cloudflare Email Routing → Worker → D1 (`temp_addresses` / `temp_messages`); Turnstile human-machine verification; frontend supports inbox, automatic refresh, and HTML email rendering (links open in new tabs).
- ⚠️ Receiving Worker + D1 + Email Routing need to be deployed on **Cloudflare Console** (code see `cloudflare/`); this repository's edge function only handles UI and reading/generating addresses.
- **Comment System**: comments stored in **Cloudflare KV**, images stored in **Cloudflare R2** (directly written via upload Worker, no S3 key required); `config/site.yaml` `comment.provider` defaults to `none` (optional `custom` / `giscus` / `waline` / `twikoo`).
- **R2 Independent Image Bed**: comment images go through `r2.qiyuan.icu` (domestic edge return via EdgeOne to Cloudflare, browser only touches Tencent network).
- **Article Reading (Edge TTS Pre-generated Audio)**: add `audio: true` to article frontmatter to generate Chinese reading mp3. The process is to **locally pre-generate** using Edge TTS before publishing → upload to Cloudflare R2 (`audio/<slug>.mp3`, via `r2.qiyuan.icu` CDN) → frontend player clicks to play (**not** real-time synthesis during runtime, for a better experience). The manifest `src/assets/audio-manifest.json` records each `slug → { url, voice, chars, bytes, generatedAt }`, and the article page displays the player accordingly.
- Audio URL automatically brings cache-breaking parameters `?v=<mp3 content sha256 first 12 bits>`: R2/EdgeOne returns the same object for unknown query parameters, but the browser/CDN uses the complete URL as the cache key, so the URL must change after the audio content is updated, and the old mp3 is not cached for a long time (R2 default `Cache-Control: max-age=604800`).
- Generation: `npm run generate:audio` (incremental, skips if content + voice hash matches); `-- --force` re-generates all; `-- --local` forces local `public/audio/` storage. Incremental cache stored in `.cache/audio-cache.json`.
- **Short Links**: shorten long links to `{origin}/api/s/<slug>`. Stored in Cloudflare KV (same namespace as comments, prefix `short:`); supports custom short codes (3-32 bit letters, numbers, `_-`) or automatic generation of 6-bit random codes; only accepts `http/https` targets; visiting `/api/s/<slug>` jumps to the original link, and returns 404 if it does not exist. Requires `CF_KV_*` configuration.
- **Public Net Disk (Netdisk) `/drive`**: browser directly uploads to Cloudflare R2 via S3 presigned URL, file metadata stored in Cloudflare KV; single file default limit 500MB (can be overridden by `DRIVE_MAX_BYTES`). Provides upload initialization `/api/drive/upload-init`, completion callback `/api/drive/upload-complete`, list `/api/drive/list`, and share page metadata `/api/drive/meta/:token`. **Files automatically deleted after 24 hours** (KV `expirationTtl`, can be modified by `DRIVE_TTL_HOURS`); requires cooperation with Cloudflare Console R2 bucket Lifecycle rules (prefix `drive/`) to actually clean up objects. Requires `R2_*` and `CF_KV_*` configuration.
## Local Development
```bash
npm install
npm run dev # http://localhost:4321
npm run build # Build to dist/
npm run preview # Preview build results
```
> Requires **Node.js 18.17.1+** (recommended 20+) and npm 9+.
> RAG-related scripts (optional, usually run automatically during CI/deployment): `npm run index:neon` (article vector), `npm run index:resources` (resource vector backfill), `npm run generate:summaries`, etc.
> Article reading audio needs **local pre-generation**: `npm run generate:audio` synthesizes and uploads to R2, then submits `src/assets/audio-manifest.json` (deployment only reads the manifest, does not generate audio). After adding or editing `audio: true` articles, be sure to re-run this command.
## Environment Variables (EdgeOne Console)
Edge functions inject secrets through environment variables; this repository's code **does not contain any secrets**.
### RAG Required
| Variable | Description |
| --------------------------------------- | -------------------------------------------- |
| `SILICONFLOW_API_KEY` | embedding (bge-m3) / re-ranking (bge-reranker) required; also used as final backup for text generation |
| `NEON_SERVERLESS_URL` | blog article vector database `rag_posts` (pgvector) connection string, required |
| `SUPABASE_URL` / `SUPABASE_SERVICE_KEY` | resource station vector database `products` connection information, required |
### Generation Models (Priority + Backup)
| Variable | Description |
| -------------------- | --------------------------------------------------------------------------- |
| `SENSENOVA_API_KEYS` | **Generation priority**; separate multiple keys with commas or new lines, polling in order; model fixed `deepseek-v4-flash`, thinking mode disabled |
| `MODELSCOPE_API_KEY` | optional backup, ModelScope `Llama-3.3-70B` (domestic direct connection, free) |
| `ZHIPU_API_KEY` | optional backup, ZhiPu `GLM-4.7-Flash` (permanent free, domestic direct connection) |
### Comments / R2 Image Bed
| Variable | Description |
| --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `CF_KV_NAMESPACE_ID` / `CF_KV_API_TOKEN` | Cloudflare KV (**comment + short link** storage), required (otherwise comment/short link interface 503) |
| `CF_ACCOUNT_ID` | Cloudflare account ID (shared by R2/KV, default known and can be left unchanged) |
| `UPLOAD_WORKER_URL` / `UPLOAD_WORKER_KEY` | upload Worker (R2 bound direct write comment images, prioritized over S3 direct upload) |
| `R2_ACCOUNT_ID` / `R2_ACCESS_KEY_ID` / `R2_SECRET_ACCESS_KEY` / `R2_BUCKET` / `R2_PUBLIC_URL` | R2 S3 credentials used for article reading **audio** and **net disk** upload; if missing, audio falls back to local `public/audio/`, and net disk is unavailable |
### Net Disk / Short Links
| Variable | Description |
| ----------------- | -------------------------------------------------------------------------------------------- |
| `DRIVE_TTL_HOURS` | net disk file survival time (hours), default `24`; expired files are automatically marked as invalid by KV `expirationTtl`, and actual deletion requires cooperation with R2 bucket Lifecycle rules (prefix `drive/`) |
| `DRIVE_MAX_BYTES` | net disk single file size limit (bytes), default `524288000` (500MB) |
> Short links reuse the above `CF_KV_*`; net disk reuses the above `R2_*` and `CF_KV_*`.
### Temporary Mailbox
| Variable | Description |
| ------------------ | ---------------------------------- |
| `TEMP_MAIL_D1_ID` | D1 Database ID |
| `CF_D1_API_TOKEN` | D1 Read/Write Token (with D1:Edit permission) |
| `TURNSTILE_SECRET` | Turnstile Secret Key (not the site key) |
| `TEMP_MAIL_DOMAIN` | Optional, default `qiyuanmail.cc.cd` |
### Resource Site (Optional)
| Variable | Description |
| ----------------- | --------------------------------------------------------------------------- |
| `JWT_SECRET` | Resource site user login JWT signature key (**required**, otherwise `/api/auth/*` will error) |
| `PUBLISH_KEY` | MCP publish and image upload key (used for `/api/publish` and `/api/images/upload`), optional |
| `PUBLIC_BASE_URL` | Public site base URL, e.g., `https://www.qiyuan.icu`, used for generating share/SEO links |
> The resource site is an **independently deployed** project, and the blog only **references** its data. See the complete steps below in the "Resource Site Deployment Guide".
## Resource Site Deployment Guide
> ⚠️ **The resource site and blog are two mutually independent deployments**, do not confuse them:
>
> - **Resource Site** (a resource sharing site): A **separate EdgeOne project**, with its source code in the [`resource-site/`](./resource-site/) subdirectory, having its own edge functions, Supabase database, and (optional) frontend pages, requiring **separate deployment** and binding its own domain (e.g., `resources.qiyuan.icu`).
> - **Blog**: By sharing the same Supabase database and setting `RESOURCES_API_PROXY` to point to the resource site domain, it **references** resource data, displaying, searching, and obtaining disk links in the built-in "Resources" page (`/resources`).
>
> That is, **first deploy `resource-site/` as an independent project, and then the blog references it**. Both can share the same Supabase project or be independent.
### Architecture
```
┌──────────────────── Resource Site (independent deployment, resource-site/)
Browser │ EdgeOne Project A (domain like resources.qiyuan.icu)
├─ Resource Site ──────┤ ├─ edge-functions/api/[[default]].js(authentication/products/comments/images/MCP)
│ │ └─ Supabase(users / products / reviews / orders;product-images bucket)
│ └────────────────────
│
└─ Blog "Resources" Page ──→ EdgeOne Project B (blog, root directory of this repository)
/resources └─ /api references resource site via RESOURCES_API_PROXY or directly connects to the same Supabase to read products
```
### Step 1: Deploy Resource Site (Independent Project)
Source code is in [`resource-site/`](./resource-site/), and detailed documentation is in [`resource-site/README.md`](./resource-site/README.md).
1. **Create Database and RLS**: Execute in Supabase SQL Editor:
- [`resource-site/database/schema.sql`](./resource-site/database/schema.sql) (create tables: `users` / `products` / `reviews` / `orders` / `quark_orders`)
- [`resource-site/database/rls_policies.sql`](./resource-site/database/rls_policies.sql) (RLS: `users` completely invisible to anon, products anonymous read-only)
2. **Create Storage Bucket**: Create `product-images` bucket and set it to public (or proxy via `/api/images/*`).
3. **Deploy `resource-site/` separately**: Deploy this directory as an independent EdgeOne project (edge functions + optional static frontend), and configure environment variables in its console:
```
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_KEY=eyJ... # service key
JWT_SECRET=*** # resource site login JWT signature key (required)
```
> Frontend pages (`public/*.html`) are not included with the template, you can write your own or take from the original site; only API is needed, you can deploy only edge functions.
### Step 2: Let Blog Reference Resource Site
1. **Data Layer**: Blog and resource site **share the same Supabase**. If you need RAG semantic search, execute [`database/products_embedding.sql`](./database/products_embedding.sql) in Supabase to add vector columns to `products`; for multi-language categories, optionally execute [`database/resource_category_i18n.sql`](./database/resource_category_i18n.sql).
2. **Configure Proxy**: Set `RESOURCES_API_PROXY` on the blog side to point to the resource site domain (e.g., `https://resources.qiyuan.icu`), during local development, `npm run dev` will proxy `/api` accordingly.
3. **Enable Resources Page**: In `config/site.yaml`, set `resources.enabled: true` (already enabled by default), and modify `label` / `icon` / `ownerUserId` as needed.
4. **Deploy Blog**: Push to Git, and EdgeOne Makers will automatically build and release.
### Verification
```bash
# Verify resource site domain
curl https://resources.your-domain.com/api/categories
curl "https://resources.your-domain.com/api/products?limit=2"
```
Return JSON indicates the resource site is working properly. In the management backend, you can register accounts and publish products (passwords must contain uppercase + lowercase + numbers + symbols).
### More Documentation
- [Resource Site Independent Deployment Instructions](./resource-site/README.md) and [Deployment Guide](./resource-site/docs/DEPLOYMENT.md)
- [Resource Site API Interface](./resource-site/docs/API.md)
- [Resource Site Database Structure](./resource-site/docs/DATABASE.md)
- [Blog Side Resource Reference Configuration (Detailed)](./docs/resources-deployment.md)
- [AI Resource Release Prompt Words](./docs/ai-resources-publisher-prompt.md)
## Site Configuration
Main configuration is in `config/site.yaml`, common items:
| Configuration Item | Description |
| ------------------------------ | --------------------------------------------------------------------------- |
| `site.title` / `site.subtitle` | Site title and subtitle |
| `site.name` / `site.author` | Author name |
| `site.avatar` | Avatar path (currently `/img/avatar.svg`, can be replaced with `.jpg`/`.webp`) |
| `site.url` | Site URL, used for RSS and SEO |
| `site.startYear` | Site establishment year |
| `tempMail.domain` | Temporary mailbox domain (default `qiyuanmail.cc.cd`) |
| `turnstileSiteKey` | Turnstile site key (public, used for frontend rendering verification box) |
| `comment.provider` | Comment provider, default `none` (optional `custom` / `giscus` / `waline` / `twikoo`) |
Replace avatar: Place the image in `public/img/avatar.svg` (or same name `.jpg`/`.webp`), and synchronously update `public/favicon.ico` (favicon generated from avatar).
## How to Add Articles
1. Create a new Markdown/MDX file in `src/content/blog/`, e.g., `my-post.md`.
2. Write frontmatter at the top of the file:
```md
---
title: My Article Title
description: One-sentence summary
pubDate: 2026-06-05
heroImage: ./cover.png # Optional, cover image in the same directory or subdirectory
categories:
- Notes
tags:
- Tag One
audio: true # Optional: set to true to generate Edge TTS Chinese audio for this article (see "Article Reading" above)
---
Content starts here…
```
1. Cover image rules:
- Same directory: `heroImage: ./cover.png`
- Subdirectory: `heroImage: ./my-post/cover.png`
2. Save and preview locally with `npm run dev`, or build with `npm run build`.
> `pubDate` / `heroImage` will automatically map to `date` / `cover`, aligning with the schema in `src/content.config.ts`.
After image modification, consider running `npm run generate:lqips` to update placeholder gradients.
## Directory Structure
| Path | Description |
| ----------------------------------- | ------------------------------------------------------------------------------ |
| `config/site.yaml` | Main site configuration |
| `src/pages/` | Page routes (including `[lang]/` multi-language mirrors) |
| `src/content/blog/` | Article collection (Markdown/MDX) |
| `src/components/` | UI components (including `tempmail/` / `chat/`) |
| `src/layouts/` | Page layouts |
| `edge-functions/api/[[default]].js` | EdgeOne edge functions: RAG chat / temporary mailbox / comments / upload / MCP / statistics |
| `cloud-functions/` | Makers cloud functions (blog image upload, etc.) |
| `cloudflare/` | Temporary mailbox receiving Worker, D1 schema, wrangler configuration (need to deploy to Cloudflare yourself) |
| `public/` | Static resources (favicon, images, fonts, etc.) |
| `scripts/generate-audio.mjs` | Article audio generation (Edge TTS → R2/CDN, producing `src/assets/audio-manifest.json`) |
| `src/assets/audio-manifest.json` | Audio list: `slug → { url, voice, chars, bytes, generatedAt }`, `url` with `?v=<hash>` cache breaking |
## Deployment
Push to Git, and **EdgeOne Makers** will automatically build (`npm run build`) and release, with edge functions deployed along with the repository. **Ensure environment variables are filled in the EdgeOne console before deployment** (especially `SILICONFLOW_API_KEY` / `NEON_SERVERLESS_URL` / `SUPABASE_*` / `SENSENOVA_API_KEYS` / `CF_KV_*`).
Temporary mailbox functionality also requires completion in the **Cloudflare console**: deploy the receiving Worker under `cloudflare/`, create D1 tables, configure Email Routing catch-all → Worker, and configure Turnstile. See comments in each file for details.
## License
This project is open-sourced under the **MIT License** (see [LICENSE](./LICENSE)). You can freely fork, modify, and deploy it as your own blog, only retaining the copyright and license statement; commercial use is also allowed, with risks borne by yourself.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available