docs: add docs for release versions (#669)

This commit is contained in:
reonokiy
2025-10-22 12:48:48 +08:00
committed by GitHub
parent ad82a6ef83
commit bc9f4ddeee
9 changed files with 640 additions and 0 deletions
@@ -0,0 +1,147 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { data as releases } from '../data/releases.data'
const { t } = useI18n()
interface ReleaseCardConfig {
icon: string
titleKey: string
tagColorClass: string
fallbackUrl: string
useCases: string[]
}
// Get latest stable release
const latestStable = computed(() => {
return releases.stable.length > 0 ? releases.stable[0] : null
})
// Get latest prerelease
const latestPrerelease = computed(() => {
return releases.prerelease.length > 0 ? releases.prerelease[0] : null
})
// Card configurations
const stableConfig: ReleaseCardConfig = {
icon: 'i-lucide:package-check',
titleKey: 'docs.versions.download-buttons.latest-stable',
tagColorClass: 'bg-green-400/10 text-green-900 dark:bg-green-600/10 dark:text-green-400',
fallbackUrl: 'https://github.com/moeru-ai/airi/releases/latest',
useCases: [
'docs.versions.download-buttons.stable-use-case-1',
'docs.versions.download-buttons.stable-use-case-2',
'docs.versions.download-buttons.stable-use-case-3',
],
}
const prereleaseConfig: ReleaseCardConfig = {
icon: 'i-lucide:package',
titleKey: 'docs.versions.download-buttons.latest-prerelease',
tagColorClass: 'bg-yellow-400/10 text-yellow-900 dark:bg-yellow-600/10 dark:text-yellow-400',
fallbackUrl: 'https://github.com/moeru-ai/airi/releases',
useCases: [
'docs.versions.download-buttons.prerelease-use-case-1',
'docs.versions.download-buttons.prerelease-use-case-2',
'docs.versions.download-buttons.prerelease-use-case-3',
],
}
</script>
<template>
<div class="cards-container">
<!-- Stable Release Card -->
<div class="release-card">
<div flex items-center gap-2 text-5xl>
<div :class="stableConfig.icon" />
</div>
<span>{{ t(stableConfig.titleKey) }}</span>
<div
v-if="latestStable"
class="version-tag"
:class="stableConfig.tagColorClass"
>
{{ latestStable.tag_name }}
</div>
<!-- Use Cases -->
<div class="use-cases">
<ul my-0 flex flex-col list-disc gap-1 pl-5 op-70>
<li v-for="useCase in stableConfig.useCases" :key="useCase">
{{ t(useCase) }}
</li>
</ul>
</div>
<a
:href="latestStable?.html_url || stableConfig.fallbackUrl"
target="_blank"
class="download-button"
>
{{ t('docs.versions.download-buttons.download') }}
</a>
</div>
<!-- Prerelease Card -->
<div class="release-card">
<div flex items-center gap-2 text-5xl>
<div :class="prereleaseConfig.icon" />
</div>
<span>{{ t(prereleaseConfig.titleKey) }}</span>
<div
v-if="latestPrerelease"
class="version-tag"
:class="prereleaseConfig.tagColorClass"
>
{{ latestPrerelease.tag_name }}
</div>
<!-- Use Cases -->
<div class="use-cases">
<ul my-0 flex flex-col list-disc gap-1 pl-5 op-70>
<li v-for="useCase in prereleaseConfig.useCases" :key="useCase">
{{ t(useCase) }}
</li>
</ul>
</div>
<a
:href="latestPrerelease?.html_url || prereleaseConfig.fallbackUrl"
target="_blank"
class="download-button"
>
{{ t('docs.versions.download-buttons.download') }}
</a>
</div>
</div>
</template>
<style scoped>
.cards-container {
--at-apply: w-full flex flex-col justify-center gap-2 text-xl;
}
@media (min-width: 768px) {
.cards-container {
--at-apply: flex-row;
}
}
.release-card {
--at-apply: w-full flex flex-col items-center gap-3 rounded-lg px-4 pb-4 pt-6 border-2 border-solid border-gray-500/10 flex-1;
}
.version-tag {
--at-apply: rounded-full px-3 py-1 text-sm font-medium;
}
.use-cases {
--at-apply: my-2 w-full flex flex-col items-center text-sm flex-1;
}
.download-button {
--at-apply: not-prose block rounded-lg px-4 py-2 text-base decoration-none transition-all duration-200 ease-in-out active:scale-95 bg-primary-400/10 text-primary-900 dark:bg-primary-600/10 dark:text-primary-400;
}
</style>
+177
View File
@@ -0,0 +1,177 @@
<script setup lang="ts">
import type { NightlyBuild } from '../data/releases.data'
import { useData } from 'vitepress'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { data as releases } from '../data/releases.data'
const props = defineProps<{
limit?: number
locale?: string
type?: 'releases' | 'nightly-builds'
}>()
const { lang } = useData()
const { t } = useI18n()
// Combined releases for display
const displayReleases = computed(() => {
if (props.type === 'releases') {
const allReleases = [...releases.stable, ...releases.prerelease]
allReleases.sort((a, b) => new Date(b.published_at).getTime() - new Date(a.published_at).getTime())
return (props.limit ? allReleases.slice(0, props.limit) : allReleases).map(release => ({
key: release.tag_name,
title: release.name || release.tag_name,
url: release.html_url,
date: release.published_at,
type: (release.prerelease ? 'prerelease' : 'stable') as 'prerelease' | 'stable',
dateLabel: 'docs.versions.releases-list.released-on',
}))
}
else if (props.type === 'nightly-builds') {
const nightlyBuilds = releases.nightly as NightlyBuild[]
nightlyBuilds.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
return (props.limit ? nightlyBuilds.slice(0, props.limit) : nightlyBuilds).map(build => ({
key: build.id,
title: build.name,
url: build.html_url,
date: build.created_at,
shortHash: build.head_sha,
type: 'nightly' as const,
dateLabel: 'docs.versions.releases-list.built-on',
}))
}
return []
})
function formatDate(dateString: string, locale?: string) {
const date = new Date(dateString)
const currentLang = locale || lang.value || 'en'
const localeMap: Record<string, string> = {
'en': 'en-US',
'zh-Hans': 'zh-CN',
'zh-Hant': 'zh-TW',
}
return date.toLocaleDateString(localeMap[currentLang] || currentLang, {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
function getVersionBadgeClass(type: 'stable' | 'prerelease' | 'nightly') {
const classes = {
nightly: 'bg-blue-400/10 text-blue-900 dark:bg-blue-600/10 dark:text-blue-400',
prerelease: 'bg-yellow-400/10 text-yellow-900 dark:bg-yellow-600/10 dark:text-yellow-400',
stable: 'bg-green-400/10 text-green-900 dark:bg-green-600/10 dark:text-green-400',
}
return classes[type]
}
function getVersionLabel(item: { type: 'stable' | 'prerelease' | 'nightly', shortHash?: string }) {
const labels = {
nightly: `${t('docs.versions.releases-list.nightly')}-${item.shortHash}`,
prerelease: t('docs.versions.releases-list.prerelease'),
stable: t('docs.versions.releases-list.stable'),
}
return labels[item.type]
}
const emptyStateConfig = computed(() => {
if (props.type === 'nightly-builds') {
return {
messageKey: 'docs.versions.releases-list.no-nightly',
linkUrl: 'https://github.com/moeru-ai/airi/releases',
linkTextKey: 'docs.versions.releases-list.workflow-page',
}
}
return {
messageKey: 'docs.versions.releases-list.no-releases',
linkUrl: 'https://github.com/moeru-ai/airi/actions/workflows/release-tamagotchi.yml',
linkTextKey: 'docs.versions.releases-list.releases-page',
}
})
</script>
<template>
<div v-if="displayReleases.length > 0" class="releases-list">
<div v-for="item in displayReleases" :key="item.key" class="release-item">
<div class="release-header">
<a :href="item.url" target="_blank" class="release-title">
{{ item.title }}
</a>
<span :class="['release-badge', getVersionBadgeClass(item.type)]">
{{ getVersionLabel(item) }}
</span>
</div>
<div class="release-date">
{{ t(item.dateLabel) }} {{ formatDate(item.date) }}
</div>
</div>
</div>
<div v-else class="no-releases">
<p>
<i18n-t :keypath="emptyStateConfig.messageKey">
<template #link>
<a :href="emptyStateConfig.linkUrl" target="_blank">{{ t(emptyStateConfig.linkTextKey) }}</a>
</template>
</i18n-t>
</p>
</div>
</template>
<style scoped>
.releases-list {
--at-apply: flex flex-col gap-3 my-4;
}
.release-item {
--at-apply: p-4 rounded-lg transition-all duration-200 ease;
border: 1px solid var(--vp-c-divider);
}
.release-item:hover {
border-color: var(--vp-c-brand-1);
background: var(--vp-c-bg-soft);
}
.release-header {
--at-apply: flex items-center gap-3 flex-wrap;
}
.release-title {
--at-apply: font-semibold text-base decoration-none;
color: var(--vp-c-brand-1);
}
.release-title:hover {
--at-apply: underline;
}
.release-badge {
--at-apply: inline-block px-2 py-0.5 rounded text-xs font-semibold uppercase tracking-wider;
}
.release-date {
--at-apply: mt-2 text-sm;
color: var(--vp-c-text-2);
}
.no-releases {
--at-apply: p-6 text-center rounded-lg;
color: var(--vp-c-text-2);
border: 1px dashed var(--vp-c-divider);
}
.no-releases a {
--at-apply: decoration-none;
color: var(--vp-c-brand-1);
}
.no-releases a:hover {
--at-apply: underline;
}
</style>
+148
View File
@@ -0,0 +1,148 @@
import { defineLoader } from 'vitepress'
export interface Release {
name: string
tag_name: string
html_url: string
published_at: string
prerelease: boolean
draft: boolean
body: string
}
export interface NightlyBuild {
id: number
name: string
html_url: string
created_at: string
updated_at: string
status: string
conclusion: string
workflow_name: string
head_sha: string
head_commit_message: string
}
export interface ReleasesData {
stable: Release[]
prerelease: Release[]
nightly: NightlyBuild[]
nightlyUrl: string
}
declare const data: ReleasesData
export { data }
export default defineLoader({
async load(): Promise<ReleasesData> {
const nightlyUrl = 'https://github.com/moeru-ai/airi/actions/workflows/release-tamagotchi.yml'
try {
// Fetch releases from GitHub API
const releasesResponse = await fetch('https://api.github.com/repos/moeru-ai/airi/releases', {
headers: {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'VitePress',
},
})
if (!releasesResponse.ok) {
throw new Error(`GitHub API request failed: ${releasesResponse.statusText}`)
}
const releases: Release[] = await releasesResponse.json()
// Filter out drafts and mark beta/alpha as prereleases
const publishedReleases = releases.filter(r => !r.draft).map((r) => {
// Mark releases with beta or alpha in tag_name as prereleases
const isPrerelease = r.prerelease
|| r.tag_name.includes('-beta')
|| r.tag_name.includes('-alpha')
return {
...r,
prerelease: isPrerelease,
}
})
// Separate stable and prerelease
const stable = publishedReleases
.filter(r => !r.prerelease)
.slice(0, 10) // Get latest 10 stable releases
const prerelease = publishedReleases
.filter(r => r.prerelease)
.slice(0, 10) // Get latest 10 prereleases
// Fetch nightly builds from GitHub Actions
let nightlyBuilds: NightlyBuild[] = []
try {
// https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository
const actionsResponse = await fetch(
'https://api.github.com/repos/moeru-ai/airi/actions/workflows/release-tamagotchi.yml/runs?status=success&per_page=10',
{
headers: {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'VitePress',
},
},
)
if (actionsResponse.ok) {
const actionsData = await actionsResponse.json()
nightlyBuilds = actionsData.workflow_runs?.map((run: {
id: number
name: string
head_sha: string
html_url: string
created_at: string
updated_at: string
status: string
conclusion: string
head_commit?: {
message: string
}
}) => {
const shortSha = run.head_sha.substring(0, 7)
// Get first line of commit message
const commitMessage = run.head_commit?.message || 'Nightly Build'
const firstLine = commitMessage.split('\n')[0]
return {
id: run.id,
name: firstLine,
html_url: run.html_url,
created_at: run.created_at,
updated_at: run.updated_at,
status: run.status,
conclusion: run.conclusion,
workflow_name: run.name,
head_sha: shortSha,
head_commit_message: commitMessage,
}
}) || []
}
}
catch (nightlyError) {
console.warn('Failed to fetch nightly builds:', nightlyError)
}
return {
stable,
prerelease,
nightly: nightlyBuilds,
nightlyUrl,
}
}
catch (error) {
console.error('Failed to fetch releases:', error)
// Return empty data if fetch fails
return {
stable: [],
prerelease: [],
nightly: [],
nightlyUrl,
}
}
},
})
@@ -0,0 +1,60 @@
---
title: Versions
description: Different versions of AIRI and how to get them
---
<script setup>
import ReleaseDownloads from '../../../../../.vitepress/components/ReleaseDownloads.vue'
import ReleasesList from '../../../../../.vitepress/components/ReleasesList.vue'
</script>
AIRI offers multiple release channels to suit different needs. Whether you want the most stable experience or the latest features, we've got you covered.
## Stable Releases & Pre-releases
Official releases include both **stable** versions (thoroughly tested and recommended for most users) and **pre-releases** (beta and alpha versions with new features ready for testing).
Pre-releases are tagged with `-beta.X` or `-alpha.X` suffixes (e.g., `v0.7.2-beta.3`).
<ReleaseDownloads />
### Recent Stable & Pre-release Versions
<ReleasesList type="releases" :limit="5" />
[View all releases on GitHub →](https://github.com/moeru-ai/airi/releases)
## Nightly Builds
::: warning EXPERIMENTAL
Nightly builds are **experimental** and may contain bugs or unstable features. Use them at your own risk and always keep a stable version as backup.
:::
Nightly builds are automatically generated every day from the latest `main` branch code. They contain the absolute newest features and bug fixes.
**Best for:**
- Developers and contributors
- Testing the latest bug fixes
- Users who need a specific fix that hasn't been released yet
### How to Get Nightly Builds
1. Visit the [Nightly Build Workflow](https://github.com/moeru-ai/airi/actions/workflows/release-tamagotchi.yml) page
2. Click on the most recent successful run (indicated by a green checkmark ✓)
3. Scroll down to the **Artifacts** section
4. Download the build for your platform:
- **Windows**: `AIRI_*_x64_en-US.exe`
- **macOS (Intel)**: `AIRI_*_x64.dmg`
- **macOS (Apple Silicon)**: `AIRI_*_arm64.dmg`
- **Linux (x64)**: `airi_*_amd64.deb` or `airi-*.x86_64.rpm`
- **Linux (ARM64)**: `airi_*_arm64.deb` or `airi-*.aarch64.rpm`
::: tip
Nightly builds run automatically at **00:00 UTC** every day. If you encounter an issue with the latest stable release, try the latest nightly build to see if it has been fixed.
:::
### Recent Nightly Builds
<ReleasesList type="nightly-builds" :limit="5" />
[View all nightly builds →](https://github.com/moeru-ai/airi/actions/workflows/release-tamagotchi.yml)
@@ -0,0 +1,60 @@
---
title: 不同的版本
description: AIRI 的不同版本以及如何获取它们
---
<script setup>
import ReleaseDownloads from '../../../../../.vitepress/components/ReleaseDownloads.vue'
import ReleasesList from '../../../../../.vitepress/components/ReleasesList.vue'
</script>
AIRI 提供多个发布渠道以满足不同需求。无论你想要最稳定的体验还是最新的功能,我们都能满足你的需求。
## 稳定版本 & 预发布版本
正式发布版本包括**稳定版本**(经过充分测试,推荐大多数用户使用)和**预发布版本**(包含已准备好测试的新功能的 beta 和 alpha 版本)。
预发布版本会带有 `-beta.X``-alpha.X` 后缀标识(例如 `v0.7.2-beta.3`)。
<ReleaseDownloads />
### 最近的稳定版本和预发布版本
<ReleasesList type="releases" :limit="5" />
[在 GitHub 上查看所有版本 →](https://github.com/moeru-ai/airi/releases)
## 每夜构建版本 (Nightly Builds)
每夜构建版本每天自动从最新的 \`main\` 分支代码生成。它们包含最新的功能和错误修复。
::: warning 实验性功能
每夜构建版本是**实验性的**,可能包含错误或不稳定的功能。使用时请自行承担风险,并始终保留一个稳定版本作为备份。
:::
**适合以下用户:**
- 开发者和贡献者
- 测试最新的错误修复
- 需要特定修复但尚未发布的用户
### 如何获取每夜构建版本
1. 访问 [每夜构建工作流](https://github.com/moeru-ai/airi/actions/workflows/release-tamagotchi.yml) 页面
2. 点击最近一次成功的运行(以绿色对勾 ✓ 标识)
3. 向下滚动到 **Artifacts** (构建产物) 部分
4. 下载适合你平台的构建版本:
- **Windows**: \`AIRI_*_x64_en-US.exe\`
- **macOS (Intel)**: \`AIRI_*_x64.dmg\`
- **macOS (Apple Silicon)**: \`AIRI_*_arm64.dmg\`
- **Linux (x64)**: \`airi_*_amd64.deb\` 或 \`airi-*.x86_64.rpm\`
- **Linux (ARM64)**: \`airi_*_arm64.deb\` 或 \`airi-*.aarch64.rpm\`
::: tip 提示
每夜构建版本在每天 **UTC 时间 00:00** 自动运行。如果你在最新稳定版本中遇到问题,可以尝试最新的每夜构建版本,看看问题是否已经被修复。
:::
### 最近的每夜构建版本
<ReleasesList type="nightly-builds" :limit="5" />
[查看所有每夜构建版本 →](https://github.com/moeru-ai/airi/actions/workflows/release-tamagotchi.yml)
@@ -1,5 +1,7 @@
import theme from './theme.yaml'
import versions from './versions.yaml'
export default {
theme,
versions,
}
@@ -0,0 +1,22 @@
download-buttons:
latest-stable: Latest Stable Release
latest-prerelease: Latest Pre-release
latest-nightly: Latest Nightly Build
download: Download
view-release: View Release
stable-use-case-1: Production use
stable-use-case-2: Users who want a stable experience
stable-use-case-3: Live streaming and content creationr
prerelease-use-case-1: Early adopters who want to try new features
prerelease-use-case-2: Testing and providing feedback to developers
prerelease-use-case-3: Users comfortable with occasional bugs
releases-list:
stable: Stable
prerelease: Pre-release
nightly: Nightly
released-on: Released on
built-on: Built on
no-releases: 'No releases available at the moment. Check the {link} directly.'
no-nightly: 'No nightly builds available at the moment. Check the {link} directly.'
releases-page: releases page
workflow-page: workflow page
@@ -1,5 +1,7 @@
import theme from './theme.yaml'
import versions from './versions.yaml'
export default {
theme,
versions,
}
@@ -0,0 +1,22 @@
download-buttons:
latest-stable: 最新稳定版本
latest-prerelease: 最新预发布版本
latest-nightly: 最新每夜构建
download: 下载
view-release: 查看发布
stable-use-case-1: 生产环境使用
stable-use-case-2: 需要稳定体验的用户
stable-use-case-3: 直播和内容创作
prerelease-use-case-1: 想要尝试新功能的早期采用者
prerelease-use-case-2: 测试并向开发者提供反馈
prerelease-use-case-3: 能够接受偶尔出现问题的用户
releases-list:
stable: 稳定版
prerelease: 预发布版
nightly: 每夜构建
released-on: 发布于
built-on: 构建于
no-releases: 暂无可用版本。请直接查看{link}。
no-nightly: 暂无可用的每夜构建版本。请直接查看{link}。
releases-page: 发布页面
workflow-page: 工作流页面