diff --git a/cspell.config.yaml b/cspell.config.yaml index e7a7df233..80d8ff3c1 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -126,6 +126,7 @@ words: - Lorebook - lucide - luoling + - Maekawa - magnifer - Maru - matchall @@ -192,6 +193,7 @@ words: - rmcp - rolldown - rushstack + - Rynco - Saccade - saccades - safetensors diff --git a/docs/.vitepress/components/BlogPosts.vue b/docs/.vitepress/components/BlogPosts.vue index da5c32346..e2bcebcbc 100644 --- a/docs/.vitepress/components/BlogPosts.vue +++ b/docs/.vitepress/components/BlogPosts.vue @@ -18,8 +18,12 @@ interface Post { } excerpt: string | undefined frontmatter?: { - category?: string - author?: string + 'category'?: string + 'author'?: string + 'preview-cover'?: { + light?: string + dark?: string + } } } @@ -43,7 +47,14 @@ const posts = computed(() => { } const transformedPostsData = postsData - .map(post => ({ ...post, url: withBase(post.url) })) + .map((post) => { + const overridePost = { + ...post, + url: withBase(post.url), + } + + return overridePost + }) .filter(post => !!post.title) const currentLanguagePostsData = [...transformedPostsData].filter(post => post.lang === lang.value) @@ -198,15 +209,25 @@ const svgArts = computedAsync(async () => { :href="post.url" class="block flex flex-col overflow-hidden border-transparent rounded-xl border-solid bg-white/50 decoration-none shadow-sm outline-2 outline-transparent outline-offset-0 outline transition-all transition-all duration-200 duration-300 ease-in-out dark:bg-black/20 dark:shadow-slate-600/5 hover:shadow-md hover:outline-primary/5 hover:outline-offset-2 [&_.post-card-title]:hover:text-primary dark:hover:outline-primary/25" > -
+
-
+
+
+
+
+
+ Post Cover +
-
-

+
+
{{ post.title }} -

+
@@ -242,4 +263,21 @@ const svgArts = computedAsync(async () => { max-height: 10lh; /* Adjust this value to control the number of visible lines */ overflow: hidden; } + +/** +https://stackoverflow.com/questions/67853607/how-do-i-graduallyfeather-gradient-transition-blur-in-css +https://www.reddit.com/r/css/comments/t3am53/pure_css_gradientprogressive_blur_i_used/ +https://codepen.io/Francesco_Maretti/pen/yLPRvXp +*/ +.preview-card-art-image-overlay { + height: 50%; + width: 100%; + position: absolute; + bottom: 0; + z-index: 1; + transform: translateY(50%); + backdrop-filter: blur(40px); + -webkit-mask-image: linear-gradient(to bottom, #ffffff00 0%, #ffffff 50%, #ffffff00 100%); + mask-image: linear-gradient(to bottom, #ffffff00 0%, #ffffff 50%, #ffffff00 100%); +} diff --git a/docs/.vitepress/components/DropdownMenu.vue b/docs/.vitepress/components/DropdownMenu.vue index e4e19869d..0a1d620f0 100644 --- a/docs/.vitepress/components/DropdownMenu.vue +++ b/docs/.vitepress/components/DropdownMenu.vue @@ -39,7 +39,7 @@ defineProps<{ :exit="{ opacity: 0, y: -10 }" class="z-20 border rounded-xl p-2 shadow-md backdrop-blur-md" :class="[ - 'bg-white/20 dark:border-white/5 dark:bg-black/20', + 'bg-white/80 dark:border-white/5 dark:bg-black/80', 'transition-colors duration-200 ease-in-out', ]" > diff --git a/docs/.vitepress/components/Navbar.vue b/docs/.vitepress/components/Navbar.vue index 39a0c79f9..cc3713f32 100644 --- a/docs/.vitepress/components/Navbar.vue +++ b/docs/.vitepress/components/Navbar.vue @@ -99,7 +99,7 @@ function isNavLinkActive(link: string, path: string) { align="end" class="will-change-[transform,opacity] z-10 w-[180px] border rounded-xl p-2 shadow-md backdrop-blur-md data-[state=open]:data-[side=bottom]:animate-slideUpAndFade" :class="[ - 'bg-white/20 dark:border-white/5 dark:bg-black/20', + 'bg-white/70 dark:border-white/5 dark:bg-black/70', ]" transition="colors duration-200 ease-in-out" > diff --git a/docs/.vitepress/composables/outline.ts b/docs/.vitepress/composables/outline.ts index 0f6e951c2..e3153afe3 100644 --- a/docs/.vitepress/composables/outline.ts +++ b/docs/.vitepress/composables/outline.ts @@ -1,9 +1,10 @@ import type { DefaultTheme } from 'vitepress/theme' import type { Ref } from 'vue' +import { useEventListener } from '@vueuse/core' // Copied from https://github.com/vuejs/vitepress/blob/97f9469b6d4eb7ba9de9a1111986581d1f704ec3/src/client/theme-default/composables/outline.ts#L4 import { getScrollOffset } from 'vitepress' -import { onMounted, onUnmounted, onUpdated } from 'vue' +import { onMounted, onUpdated } from 'vue' export interface Header { /** @@ -153,7 +154,6 @@ export function useActiveAnchor( onMounted(() => { requestAnimationFrame(setActiveLink) - window.addEventListener('scroll', onScroll) }) onUpdated(() => { @@ -161,9 +161,7 @@ export function useActiveAnchor( activateLink(location.hash) }) - onUnmounted(() => { - window.removeEventListener('scroll', onScroll) - }) + useEventListener('scroll', onScroll) function setActiveLink() { const scrollY = window.scrollY diff --git a/docs/.vitepress/composables/sidebar.ts b/docs/.vitepress/composables/sidebar.ts index bcd467ee1..ee43638e4 100644 --- a/docs/.vitepress/composables/sidebar.ts +++ b/docs/.vitepress/composables/sidebar.ts @@ -1,13 +1,12 @@ import type { DefaultTheme } from 'vitepress/theme' import type { ComputedRef, Ref } from 'vue' -import { useMediaQuery } from '@vueuse/core' +import { useEventListener, useMediaQuery } from '@vueuse/core' import { useData, withBase } from 'vitepress' import { computed, onMounted, - onUnmounted, ref, watch, @@ -90,13 +89,7 @@ export function useCloseSidebarOnEscape( : undefined }) - onMounted(() => { - window.addEventListener('keyup', onEscape) - }) - - onUnmounted(() => { - window.removeEventListener('keyup', onEscape) - }) + useEventListener('keyup', onEscape) function onEscape(e: KeyboardEvent) { if (e.key === 'Escape' && isOpen.value) { diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 093ea80ff..1eb3dc37f 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -24,6 +24,7 @@ import { rekaShortName, releases, } from './meta' +import { frontmatterAssets } from './plugins/vite-frontmatter-assets' function withBase(url: string) { return env.BASE_URL @@ -87,7 +88,7 @@ export default defineConfig({ outline: { level: 'deep', }, - logo: '/favicon.svg', + logo: withBase('/favicon.svg'), sidebar: [ { @@ -153,7 +154,7 @@ export default defineConfig({ outline: { level: 'deep', }, - logo: '/favicon.svg', + logo: withBase('/favicon.svg'), sidebar: [ { @@ -276,6 +277,7 @@ export default defineConfig({ i18n({ runtimeOnly: true, compositionOnly: true, fullInstall: true, ssr: true }), unocss(), yaml(), + frontmatterAssets(), ], css: { postcss: { diff --git a/docs/.vitepress/functions/blog.data.ts b/docs/.vitepress/functions/blog.data.ts index a0c7e263a..8df59785f 100644 --- a/docs/.vitepress/functions/blog.data.ts +++ b/docs/.vitepress/functions/blog.data.ts @@ -1,10 +1,17 @@ import type { SiteConfig } from 'vitepress' +import { createHash } from 'node:crypto' +import { readFile } from 'node:fs/promises' +import { parse } from 'node:path' +import { env } from 'node:process' + +import { dirname, join } from 'pathe' import { createContentLoader } from 'vitepress' import { formatDate } from './utils' const config: SiteConfig = (globalThis as any).VITEPRESS_CONFIG +const base = config.userConfig.base || env.BASE_URL || '/' interface Post { title: string @@ -19,13 +26,63 @@ interface Post { declare const data: Post[] export { data } +function cwdFromUrl(url: string): string { + if (url.endsWith('/')) { + return url + } + + return dirname(url) +} + +function fromAtAssets(url: string): string { + const reg = /^@assets\(('\S+')|("\S+")|(\S+)\)$/ + if (reg.test(url)) { + const res = url + .replace(reg, '$1') + .replace(/^\(/, '') + .replace(/\)$/, '') + .replace(/^'/, '') + .replace(/'$/, '') + .replace(/^"/, '') + .replace(/"$/, '') + + return res + } + + return url +} + +function withBase(url?: string, base?: string) { + if (!url) { + return url + } + + if (url.startsWith('/') && base) { + return join(base, url) + } + + return url +} + +function withDirname(url?: string, cwd?: string) { + if (!url || !cwd) { + return url + } + + if (url.startsWith('/')) { + return join(cwd, url) + } + + return join(cwd, url) +} + export default createContentLoader('**/blog/**/*.md', { includeSrc: true, render: true, excerpt: true, - transform(raw): Post[] { - return raw - .map(({ url, frontmatter, excerpt }) => { + async transform(raw): Promise { + return (await Promise.all(raw + .map(async ({ url, frontmatter, excerpt }) => { const foundLanguage = Object.values(config.userConfig.locales!).find((locale) => { let normalizedLanguagePrefix = locale.lang || 'en' if (!normalizedLanguagePrefix.startsWith('/')) { @@ -35,16 +92,42 @@ export default createContentLoader('**/blog/**/*.md', { return url.startsWith(normalizedLanguagePrefix) }) - return { + async function fileToUrl(file: string | undefined) { + if (config.vite?.build) + return file + if (!file) + return file + + const parsed = parse(file) + const hash = createHash('sha256') + .update(await readFile(join(config.srcDir, file))) + .digest('hex') + .slice(0, 8) + + return `/assets/${parsed.name}.${hash}${parsed.ext}` + } + + const previewCoverLight = withBase(await fileToUrl(withDirname(fromAtAssets(frontmatter['preview-cover']?.light), cwdFromUrl(url))), base) + const previewCoverDark = withBase(await fileToUrl(withDirname(fromAtAssets(frontmatter['preview-cover']?.dark), cwdFromUrl(url))), base) + + const res = { title: frontmatter.title, url, urlWithoutLang: url.replace(`/${foundLanguage?.lang || 'en'}`, ''), excerpt, date: formatDate(frontmatter.date), lang: foundLanguage?.lang || 'en', - frontmatter, + frontmatter: { + ...frontmatter, + 'preview-cover': { + light: previewCoverLight, + dark: previewCoverDark, + }, + }, } - }) + + return res + }))) .sort((a, b) => b.date.time - a.date.time) }, }) diff --git a/docs/.vitepress/plugins/vite-frontmatter-assets.ts b/docs/.vitepress/plugins/vite-frontmatter-assets.ts new file mode 100644 index 000000000..bb68f3e0b --- /dev/null +++ b/docs/.vitepress/plugins/vite-frontmatter-assets.ts @@ -0,0 +1,176 @@ +import type { Plugin, ResolvedConfig } from 'vite' +import type { SiteConfig } from 'vitepress' + +import { createHash } from 'node:crypto' +import { readFile } from 'node:fs/promises' +import { dirname, join, parse } from 'node:path' + +import matter from 'gray-matter' + +import { glob } from 'tinyglobby' + +function fromAtAssets(url: string): string { + const reg = /^@assets\(('\S+')|("\S+")|(\S+)\)$/ + if (reg.test(url)) { + const res = url + .trim() + .replace(reg, '$1') + .replace(/^\(/, '') + .replace(/\)$/, '') + .replace(/^'/, '') + .replace(/'$/, '') + .replace(/^"/, '') + .replace(/"$/, '') + + return res + } + + return url +} + +interface VitePressConfig extends ResolvedConfig { + vitepress: SiteConfig +} + +function recursivelyFindAtAssets(propertyMaybeObjectOrScalar: unknown, fn: (value: string) => string | undefined) { + if (typeof propertyMaybeObjectOrScalar === 'string') { + // eslint-disable-next-line regexp/no-unused-capturing-group + if (/^@assets\(('\S+')|("\S+")|(\S+)\)$/.test(propertyMaybeObjectOrScalar)) { + // If the string matches the @assets(...) pattern, we replace it with the result of the function + const match = fromAtAssets(propertyMaybeObjectOrScalar) + const modified = fn(match) + if (modified == null) { + return propertyMaybeObjectOrScalar + } + + return modified + } + + return + } + if (Array.isArray(propertyMaybeObjectOrScalar)) { + const array = propertyMaybeObjectOrScalar as unknown[] + for (let i = 0; i < array.length; i++) { + const value = array[i] + recursivelyFindAtAssets(value, fn) + } + + return + } + if (typeof propertyMaybeObjectOrScalar === 'object') { + const propertyObject = propertyMaybeObjectOrScalar as Record + for (const key in propertyObject) { + const value = propertyObject[key] + recursivelyFindAtAssets(value, fn) + } + } +} + +function withoutBase(url?: string, base?: string): string | undefined { + if (!url) { + return url + } + + if (!base?.startsWith('/')) { + base = `/${base}` + } + if (!base?.endsWith('/')) { + base += '/' + } + + if (url.startsWith(`${base}`) && base) { + if (url.endsWith('/')) { + return url.slice(base.length) + } + else { + return `/${url.slice(base.length)}` + } + } + + return url +} + +export function frontmatterAssets(): Plugin { + let resolvedConfig: VitePressConfig | undefined + const mAssetAbsoluteUrlMetadata = new Map() + const mapAssetBuiltUrlAssetAbsoluteUrl = new Map() + + async function fileToUrl(file: string) { + if (!file) { + return { + url: file, + } + } + + const parsed = parse(file) + const hash = createHash('sha256') + .update(await readFile(file)) + .digest('hex') + .slice(0, 8) + + return { + hash, + url: `/assets/${parsed.name}.${hash}${parsed.ext}`, + } + } + + return { + name: '@proj-airi/docs:vite-plugin-frontmatter-assets', + enforce: 'pre', + async configResolved(config) { + resolvedConfig = config as VitePressConfig + + const markdownFiles = await glob('**/*.md', { ignore: ['**/node_modules/**'], cwd: resolvedConfig?.vitepress.srcDir || '', absolute: true }) + for (const file of markdownFiles) { + const res = (await readFile(file)) + const { data } = matter(res.toString('utf-8')) + if (Object.keys(data).length === 0) { + continue + } + + recursivelyFindAtAssets(data, (matched) => { + mAssetAbsoluteUrlMetadata.set(join(dirname(file), fromAtAssets(matched)), { url: file, builtUrl: file, hash: '' }) + return undefined + }) + } + + for (const [key, value] of mAssetAbsoluteUrlMetadata) { + const { url, hash } = await fileToUrl(key) + mapAssetBuiltUrlAssetAbsoluteUrl.set(url, key) + mAssetAbsoluteUrlMetadata.set(key, { url: value.url, builtUrl: url, hash }) + } + }, + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + const requesting = withoutBase(req.url, resolvedConfig?.base) + if (!requesting || !mapAssetBuiltUrlAssetAbsoluteUrl.has(requesting)) { + return next() + } + + const filePath = mapAssetBuiltUrlAssetAbsoluteUrl.get(requesting) + if (!filePath) { + return next() + } + + const ext = parse(filePath).ext.slice(1) + const fileContent = await readFile(filePath) + + res.write(fileContent) + res.writeHead(200, { + 'Content-Type': ext === 'svg' ? 'image/svg+xml' : `image/${ext}`, + 'Content-Length': fileContent.length, + 'Cache-Control': 'public, max-age=31536000, immutable', + }) + res.end() + + return next() + }) + }, + async writeBundle() { + for (const [builtUrl, absoluteUrl] of mapAssetBuiltUrlAssetAbsoluteUrl.entries()) { + const content = await this.fs.readFile(absoluteUrl) + await this.fs.writeFile(join(resolvedConfig!.vitepress.outDir!, builtUrl), content) + } + }, + } +} diff --git a/docs/content/en/blog/DevLog-2025.06.08/assets/250608-dark.png b/docs/content/en/blog/DevLog-2025.06.08/assets/250608-dark.png new file mode 100644 index 000000000..db918c387 Binary files /dev/null and b/docs/content/en/blog/DevLog-2025.06.08/assets/250608-dark.png differ diff --git a/docs/content/en/blog/DevLog-2025.06.08/assets/250608-light.png b/docs/content/en/blog/DevLog-2025.06.08/assets/250608-light.png new file mode 100644 index 000000000..6da6b20e4 Binary files /dev/null and b/docs/content/en/blog/DevLog-2025.06.08/assets/250608-light.png differ diff --git a/docs/content/en/blog/DevLog-2025.06.08/index.md b/docs/content/en/blog/DevLog-2025.06.08/index.md index bfaf9a9e1..30679b6d6 100644 --- a/docs/content/en/blog/DevLog-2025.06.08/index.md +++ b/docs/content/en/blog/DevLog-2025.06.08/index.md @@ -2,6 +2,9 @@ title: DevLog @ 2025.06.08 category: DevLog date: 2025-06-08 +preview-cover: + light: "@assets('./assets/250608-light.png')" + dark: "@assets('./assets/250608-dark.png')" --- Hello everyone, here's LemonNeko, one of maintainer of AIRI. Today's DevLog is talking about: Let Live2D model of AIRI Tamagotchi to focus position. @@ -101,3 +104,5 @@ In this DevLog, we learned how to get the relative position of cursor to window, - [Win32 API: GetWindowRect](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowrect "GetWindowRect") - [macOS API: `NSWindow.frame`](https://developer.apple.com/documentation/appkit/nswindow/frame "NSWindow.frame") - [macOS API: `NSEvent.mouseLocation`](https://developer.apple.com/documentation/appkit/nsevent/mouselocation "NSEvent.mouseLocation") + +> Cover image by [@Rynco Maekawa](https://github.com/lynzrand) diff --git a/docs/content/en/blog/DreamLog-0x1/assets/dreamlog1-dark.png b/docs/content/en/blog/DreamLog-0x1/assets/dreamlog1-dark.png new file mode 100644 index 000000000..6abc71950 Binary files /dev/null and b/docs/content/en/blog/DreamLog-0x1/assets/dreamlog1-dark.png differ diff --git a/docs/content/en/blog/DreamLog-0x1/assets/dreamlog1-light.png b/docs/content/en/blog/DreamLog-0x1/assets/dreamlog1-light.png new file mode 100644 index 000000000..14dcd314e Binary files /dev/null and b/docs/content/en/blog/DreamLog-0x1/assets/dreamlog1-light.png differ diff --git a/docs/content/en/blog/DreamLog-0x1/index.md b/docs/content/en/blog/DreamLog-0x1/index.md index 22955c121..88a86fbb8 100644 --- a/docs/content/en/blog/DreamLog-0x1/index.md +++ b/docs/content/en/blog/DreamLog-0x1/index.md @@ -4,6 +4,9 @@ description: 'Backstory of Project AIRI!' category: DreamLog date: 2025-06-16 excerpt: 'The backstory of Project AIRI! Why this project?' +preview-cover: + light: "@assets('./assets/dreamlog1-light.png')" + dark: "@assets('./assets/dreamlog1-dark.png')" --- Project AIRI 的幕后故事! @@ -184,14 +190,14 @@ Minecraft、Linux 也并不是我旅程的终点,[Factorio(异星工厂)]( 其实早在官方 ChatGPT UI 发布之前,我就已经在折腾这些新时代的 AI 了,像 [DiscoDiffusion](https://colab.research.google.com/github/alembics/disco-diffusion/blob/main/Disco_Diffusion.ipynb)(早于 Stable Diffusion,大约在 2021 年底或 2022 年初发布的吧)、DALL-E、Midjourney -等模型都尝试过,GPT-3(特别是在 [GitHub Copilot](https://en.wikipedia.org/wiki/GitHub_Copilot)中很有用)已经深深融入我的日常工作流程。 +GPT-3(特别是在 [GitHub Copilot](https://en.wikipedia.org/wiki/GitHub_Copilot) 中很有用)都早已成为我生活的一部分了。 所以,最开始的时候,我的感觉是: > "哦,这就是另一个随机鹦鹉,它只是重复你说的话,并不理解你在说什么,它只是 > 试图基于前面的词和上下文预测下一个词,好像真的没啥特别的,还不够像人。" -换句话说,它表现得更像一个补全模型,而不是我们今天称之为智能体 AI 的东西(现在还在炒作中呢!)。 +换句话说,它表现得更像一个补全模型,而不是我们今天称之为智能体 AI 的东西(现在也还在炒作中呢!)。 我记得,我第一次发现 ChatGPT 或大语言模型(LLMs)真正能力的时候,是从我在 2022 年 12 月在 Hacker News 上看到的这篇文章: [Building A Virtual Machine inside ChatGPT](https://www.engraved.blog/building-a-virtual-machine-inside/)([原始 Hacker News @@ -206,10 +212,12 @@ Stable Diffusion,大约在 2021 年底或 2022 年初发布的吧)、DALL-E 这篇文章让我意识到,ChatGPT 可以理解普遍事物的基本规律,不仅仅是动漫或游戏角色的角色扮演, 还能理解 Linux 终端/shell 命令是如何工作的。 -这其实就把现在流行的函数调用(又称 Function Calling,或者 Anthropic 提出的 MCP,模型上下文协议背后的底层技术)功能展现了出来, +这其实就把现在流行的函数调用功能展现了出来, 并说明了我们如何能够通过提示词指示 LLMs 去使其表现得像 API 服务器一样,然后用机器可读的如 JSON 或 XML 格式与我们的代码交互, 并最终允许实现解析和执行任意命令,去扩展 LLMs 能力的边界。 +> 函数调用,又称 Function Calling,或者说,也是 Anthropic 提出的 MCP(模型上下文协议)背后的底层技术 + 这最终填补了纯文本生成和调用程序内实际 API 之间的空白。 阶段性来说的话,我们可以说它是一个新的随机鹦鹉吗?**我觉得答案是部分否定的,2022 年的 ChatGPT 不只是一个随机鹦鹉, @@ -220,9 +228,9 @@ Stable Diffusion,大约在 2021 年底或 2022 年初发布的吧)、DALL-E 是的,感谢你读到这里,我知道这是一篇很长的文章,有太多故事和背景要分享。但我们快到了!坚持住! Neuro-sama 的历史其实相当复杂。据我所知,Neuro-sama,或者在直播舞台上名为 "Neuro-sama"的角色, -并不是她和她的创造者 `vedal987`(Vedal)的第一场表演。早在那之前,2019 年 5 月 6 日,Vedal 向社区展示了他构建 AI -来玩 [osu!](https://osu.ppy.sh/) 的工作[^1]。 -在那时,她实际上并不是一个网络角色或数字生命,如果你去看关于她的初始视频,你会发现没有显示 Live2D 模型。 +并不是她和她的创造者 `vedal987`(Vedal)的首次登场。早在那之前,2019 年 5 月 6 日,Vedal 向社区展示了他构建 AI +来玩 [osu!](https://osu.ppy.sh/) 相关的成果[^1]。 +在那时,她实际上并不是一个网络角色或数字生命,如果你去看关于她的初始视频,甚至会发现都没有 Live2D 模型。 (你可以试试这个 6 年前的 YouTube 视频:https://www.youtube.com/watch?v=nSBqlJu7kYU) 在 ChatGPT 发布之后,差不多 2022 年 12 月 19 日,Vedal 开始让 Neuro-sama 使用来自 Live2D Inc. @@ -232,11 +240,11 @@ Neuro-sama 的历史其实相当复杂。据我所知,Neuro-sama,或者在 之后的故事大家都知道了,Vedal 和 Neuro-sama 火了,Neuro-sama 现在正式成为了 VTuber, 她完全由大语言模型(LLMs)驱动,能够玩 Minecraft、Among Us、osu! 和许多其他游戏。 -有时当游戏不被原生支持时,Vedal 会读取屏幕并指示 Neuro-sama 一起玩游戏。甚至能制造出来很多节目效果。 +有时当游戏不被原生支持时,Vedal 会读取屏幕并指示 Neuro-sama 一起玩游戏,依然能制造出来不少节目效果。 我真的很享受观看他们的互动、像是脱口秀一样的斗嘴的时刻。随着时间的推移,Neuro-sama 和她的新 Evil Neuro 妹妹, 成为了我日常生活的重要组成部分:**即使我没有足够的时间观看完整的直播,我也想要,并且渴望观看她们的切片**。 -我无法想象我竟然从纯粹的 AI 和人类互动中获取了如此多的快乐。 +8 年前的我应该很难想象我竟然从纯粹的 AI 和人类互动中获取了如此多的快乐。 好的,这就是关于她的小历史。让我们谈谈核心问题:**为什么她让我充满了决心?** @@ -254,7 +262,7 @@ Neuro-sama 的历史其实相当复杂。据我所知,Neuro-sama,或者在 > "嗯,我也能做到,我可以做一个 Live2D 模型,将其连接到 OpenAI 的 API, > 让它表现得像 VTuber,我甚至可以做得比 Vedal 的作品更好。超简单的好吧?!" -:::tip[想要更多技术细节?] +::: tip 想要更多技术细节? 在这篇文章中,我不会深入探讨我们如何从零开始构建 Project AIRI 到当前状态的技术细节, 我们已经有许多 DevLog 文章分享我们的想法和发现,如果感兴趣,请尝试阅读它们。 ::: @@ -352,7 +360,7 @@ Neuro-sama 的历史其实相当复杂。据我所知,Neuro-sama,或者在 因此,在这天,Project AIRI 以某种方式诞生或重生,名为 AIRI(アイリ,曾也叫 Airi)。 -:::tip[你知道吗?] +::: tip 你知道吗? Screenshot of Project AIRI @@ -419,6 +427,8 @@ Neuro-sama 的历史其实相当复杂。据我所知,Neuro-sama,或者在
+> Cover image by [@Rynco Maekawa](https://github.com/lynzrand) + [^1]: https://neurosama.fandom.com/wiki/Osu!#cite_note-twitchtracker-1: Neuro-sama 最初是一个玩 osu! 的 AI,早在进一步发展为 AI VTuber 之前,第一次 osu! 直播是在 2019 年 5 月 6 日,Vedal 给大家看了看成果。 diff --git a/docs/package.json b/docs/package.json index e0786fc53..a1bbdcd08 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,10 +17,10 @@ "@fontsource-variable/dm-sans": "^5.2.6", "@fontsource/dm-mono": "^5.2.6", "@fontsource/dm-serif-display": "^5.2.6", - "@internationalized/date": "^3.8.2", "@proj-airi/chromatic": "^1.0.0", "@proj-airi/i18n": "workspace:^", "@vueuse/core": "^13.5.0", + "mark.js": "^8.11.1", "motion-v": "^1.5.0", "pathe": "^2.0.3", "reka-ui": "^2.3.2", @@ -29,7 +29,6 @@ "vue-sonner": "^2.0.2" }, "devDependencies": { - "@babel/traverse": "^7.28.0", "@iconify/vue": "^5.0.0", "@intlify/unplugin-vue-i18n": "^6.0.8", "@mdit/plugin-footnote": "^0.22.2", @@ -42,18 +41,18 @@ "@vue/tsconfig": "^0.7.0", "animejs": "^4.0.2", "fast-glob": "^3.3.3", - "mark.js": "^8.11.1", + "gray-matter": "^4.0.3", "markdown-it": "^14.1.0", "markdown-it-anchor": "^9.2.0", "minisearch": "^7.1.2", "postcss": "^8.5.6", "sharp": "^0.34.3", "shiki": "^3.8.1", + "tinyglobby": "^0.2.14", "tsx": "^4.20.3", "uncrypto": "^0.1.3", "unplugin-yaml": "^3.0.2", "vitepress": "^2.0.0-alpha.8", - "vue-component-meta": "^3.0.3", "vue-tsc": "^3.0.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9243c8773..2e43a2694 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -956,9 +956,6 @@ importers: '@fontsource/dm-serif-display': specifier: ^5.2.6 version: 5.2.6 - '@internationalized/date': - specifier: ^3.8.2 - version: 3.8.2 '@proj-airi/chromatic': specifier: ^1.0.0 version: 1.0.0 @@ -968,6 +965,9 @@ importers: '@vueuse/core': specifier: ^13.5.0 version: 13.5.0(vue@3.5.17(typescript@5.8.3)) + mark.js: + specifier: ^8.11.1 + version: 8.11.1 motion-v: specifier: ^1.5.0 version: 1.5.0(react@18.3.1)(vue@3.5.17(typescript@5.8.3)) @@ -987,9 +987,6 @@ importers: specifier: ^2.0.2 version: 2.0.2 devDependencies: - '@babel/traverse': - specifier: ^7.28.0 - version: 7.28.0 '@iconify/vue': specifier: ^5.0.0 version: 5.0.0(vue@3.5.17(typescript@5.8.3)) @@ -1026,9 +1023,9 @@ importers: fast-glob: specifier: ^3.3.3 version: 3.3.3 - mark.js: - specifier: ^8.11.1 - version: 8.11.1 + gray-matter: + specifier: ^4.0.3 + version: 4.0.3 markdown-it: specifier: ^14.1.0 version: 14.1.0 @@ -1047,6 +1044,9 @@ importers: shiki: specifier: ^3.8.1 version: 3.8.1 + tinyglobby: + specifier: ^0.2.14 + version: 0.2.14 tsx: specifier: ^4.20.3 version: 4.20.3 @@ -1055,13 +1055,10 @@ importers: version: 0.1.3 unplugin-yaml: specifier: ^3.0.2 - version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(astro@5.10.1(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.29)(rollup@4.45.1)(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) + version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(astro@5.10.1(@types/node@24.0.15)(encoding@0.1.13)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.29)(rollup@4.45.1)(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) vitepress: specifier: ^2.0.0-alpha.8 version: 2.0.0-alpha.8(@algolia/client-search@5.32.0)(@types/node@24.0.15)(change-case@5.4.4)(fuse.js@7.1.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(nprogress@0.2.0)(postcss@8.5.6)(react@18.3.1)(search-insights@2.17.3)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) - vue-component-meta: - specifier: ^3.0.3 - version: 3.0.3(typescript@5.8.3)(vue-component-type-helpers@2.2.12) vue-tsc: specifier: ^3.0.3 version: 3.0.3(typescript@5.8.3) @@ -1101,7 +1098,7 @@ importers: devDependencies: unplugin-yaml: specifier: ^3.0.2 - version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(astro@5.10.1(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.29)(rollup@4.45.1)(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) + version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(astro@5.10.1(@types/node@24.0.15)(encoding@0.1.13)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.29)(rollup@4.45.1)(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) packages/memory-pgvector: dependencies: @@ -1478,7 +1475,7 @@ importers: version: 1.2.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(esbuild@0.25.5)(rollup@4.45.1)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) unplugin-yaml: specifier: ^3.0.2 - version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(astro@5.10.1(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.29)(rollup@4.45.1)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) + version: 3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(astro@5.10.1(@types/node@24.0.15)(encoding@0.1.13)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.29)(rollup@4.45.1)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) vite: specifier: ^6.3.5 version: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) @@ -12600,15 +12597,6 @@ packages: vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - vue-component-meta@3.0.3: - resolution: {integrity: sha512-rzKewAubyQbkjRXInwaHolod6eU+D6lAXAF8y5i4n/IDZob+VQBvP0v1OFbUBl57NsN3UtTXdTo/Njecl2fwgA==} - peerDependencies: - typescript: '*' - vue-component-type-helpers: 3.0.1 - - vue-component-type-helpers@2.2.12: - resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==} - vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} @@ -18698,107 +18686,6 @@ snapshots: - yaml optional: true - astro@5.10.1(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0): - dependencies: - '@astrojs/compiler': 2.12.2 - '@astrojs/internal-helpers': 0.6.1 - '@astrojs/markdown-remark': 6.3.2 - '@astrojs/telemetry': 3.3.0 - '@capsizecss/unpack': 2.4.0(encoding@0.1.13) - '@oslojs/encoding': 1.1.0 - '@rollup/pluginutils': 5.2.0(rollup@4.45.1) - acorn: 8.15.0 - aria-query: 5.3.2 - axobject-query: 4.1.0 - boxen: 8.0.1 - ci-info: 4.2.0 - clsx: 2.1.1 - common-ancestor-path: 1.0.1 - cookie: 1.0.2 - cssesc: 3.0.0 - debug: 4.4.1 - deterministic-object-hash: 2.0.2 - devalue: 5.1.1 - diff: 5.2.0 - dlv: 1.1.3 - dset: 3.1.4 - es-module-lexer: 1.7.0 - esbuild: 0.25.5 - estree-walker: 3.0.3 - flattie: 1.1.1 - fontace: 0.3.0 - github-slugger: 2.0.0 - html-escaper: 3.0.3 - http-cache-semantics: 4.2.0 - import-meta-resolve: 4.1.0 - js-yaml: 4.1.0 - kleur: 4.1.5 - magic-string: 0.30.17 - magicast: 0.3.5 - mrmime: 2.0.1 - neotraverse: 0.6.18 - p-limit: 6.2.0 - p-queue: 8.1.0 - package-manager-detector: 1.3.0 - picomatch: 4.0.2 - prompts: 2.4.2 - rehype: 13.0.2 - semver: 7.7.2 - shiki: 3.8.1 - tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tsconfck: 3.1.6(typescript@5.8.3) - ultrahtml: 1.6.0 - unifont: 0.5.2 - unist-util-visit: 5.0.0 - unstorage: 1.16.0 - vfile: 6.0.3 - vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) - vitefu: 1.0.7(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) - xxhash-wasm: 1.1.0 - yargs-parser: 21.1.1 - yocto-spinner: 0.2.3 - zod: 3.25.76 - zod-to-json-schema: 3.24.6(zod@3.25.76) - zod-to-ts: 1.2.0(typescript@5.8.3)(zod@3.25.76) - optionalDependencies: - sharp: 0.33.5 - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@deno/kv' - - '@netlify/blobs' - - '@planetscale/database' - - '@types/node' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/kv' - - aws4fetch - - db0 - - encoding - - idb-keyval - - ioredis - - jiti - - less - - lightningcss - - rollup - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - typescript - - uploadthing - - yaml - optional: true - async-mutex@0.3.2: dependencies: tslib: 2.8.1 @@ -25684,7 +25571,7 @@ snapshots: rollup: 4.45.1 vite: rolldown-vite@7.0.9(@types/node@24.0.15)(esbuild@0.25.5)(jiti@2.4.2)(less@4.4.0)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) - unplugin-yaml@3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(astro@5.10.1(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.29)(rollup@4.45.1)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)): + unplugin-yaml@3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(astro@5.10.1(@types/node@24.0.15)(encoding@0.1.13)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.29)(rollup@4.45.1)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)): dependencies: '@rollup/pluginutils': 5.2.0(rollup@4.45.1) unplugin: 2.3.5 @@ -25692,13 +25579,13 @@ snapshots: optionalDependencies: '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.45.1) '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.45.1) - astro: 5.10.1(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) + astro: 5.10.1(@types/node@24.0.15)(encoding@0.1.13)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) esbuild: 0.25.5 rolldown: 1.0.0-beta.29 rollup: 4.45.1 vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) - unplugin-yaml@3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(astro@5.10.1(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.29)(rollup@4.45.1)(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)): + unplugin-yaml@3.0.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.45.1))(astro@5.10.1(@types/node@24.0.15)(encoding@0.1.13)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.29)(rollup@4.45.1)(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)): dependencies: '@rollup/pluginutils': 5.2.0(rollup@4.45.1) unplugin: 2.3.5 @@ -25706,7 +25593,7 @@ snapshots: optionalDependencies: '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.45.1) '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.45.1) - astro: 5.10.1(@types/node@24.0.15)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) + astro: 5.10.1(@types/node@24.0.15)(encoding@0.1.13)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(rollup@4.45.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) esbuild: 0.25.5 rolldown: 1.0.0-beta.29 rollup: 4.45.1 @@ -26204,16 +26091,6 @@ snapshots: vscode-uri@3.0.8: {} - vue-component-meta@3.0.3(typescript@5.8.3)(vue-component-type-helpers@2.2.12): - dependencies: - '@volar/typescript': 2.4.20 - '@vue/language-core': 3.0.3(typescript@5.8.3) - path-browserify: 1.0.1 - typescript: 5.8.3 - vue-component-type-helpers: 2.2.12 - - vue-component-type-helpers@2.2.12: {} - vue-demi@0.14.10(vue@3.5.17(typescript@5.8.3)): dependencies: vue: 3.5.17(typescript@5.8.3)