diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e70c4d5a..c7701b34b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: strategy: matrix: include: - - command: pnpm -F @proj-airi/stage-web run build && pnpm -F @proj-airi/docs run build:base && mv ./docs/dist ./apps/stage-web/dist/docs && pnpm -F @proj-airi/stage-ui run story:build && mv ./packages/stage-ui/.histoire/dist ./apps/stage-web/dist/ui + - command: pnpm -F @proj-airi/stage-web run build && pnpm -F @proj-airi/docs run build:base && mv ./docs/.vitepress/dist ./apps/stage-web/dist/docs && pnpm -F @proj-airi/stage-ui run story:build && mv ./packages/stage-ui/.histoire/dist ./apps/stage-web/dist/ui app_name: stage-web - command: pnpm -F @proj-airi/stage-tamagotchi run build app_name: stage-tamagotchi diff --git a/.gitignore b/.gitignore index 2b4b71010..a1a2a97ac 100644 --- a/.gitignore +++ b/.gitignore @@ -31,8 +31,8 @@ coverage/ **/public/assets/live2d/models/* **/public/assets/vrm/models/* +# Serving fonts locally **/public/assets/fonts/ -**/public/assets/static-fonts/ *.pcm *.wav diff --git a/apps/playground-prompt-engineering/package.json b/apps/playground-prompt-engineering/package.json index 91f345fc3..5483551f6 100644 --- a/apps/playground-prompt-engineering/package.json +++ b/apps/playground-prompt-engineering/package.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@iconify/vue": "^5.0.0", - "@types/node": "^24.0.10", + "@types/node": "^24.0.13", "@unocss/reset": "^66.3.3", "@vitejs/plugin-vue": "^6.0.0", "typescript": "~5.8.3", diff --git a/apps/stage-tamagotchi/package.json b/apps/stage-tamagotchi/package.json index d6ee06def..313c593a2 100644 --- a/apps/stage-tamagotchi/package.json +++ b/apps/stage-tamagotchi/package.json @@ -93,10 +93,10 @@ "@fontsource/sniglet": "^5.2.6", "@iconify-json/carbon": "^1.2.10", "@iconify-json/eos-icons": "^1.2.2", - "@iconify-json/lucide": "^1.2.55", + "@iconify-json/lucide": "^1.2.57", "@iconify-json/mingcute": "^1.2.3", "@iconify-json/ph": "^1.2.2", - "@iconify-json/simple-icons": "^1.2.41", + "@iconify-json/simple-icons": "^1.2.43", "@iconify-json/solar": "^1.2.2", "@iconify-json/svg-spinners": "^1.2.2", "@iconify-json/vscode-icons": "^1.2.23", @@ -110,7 +110,7 @@ "@tauri-apps/cli": "^2.6.2", "@types/culori": "^4.0.0", "@types/nprogress": "^0.2.3", - "@types/three": "^0.178.0", + "@types/three": "^0.178.1", "@types/yauzl": "^2.10.3", "@unocss/reset": "^66.3.3", "@vitejs/plugin-vue": "^6.0.0", diff --git a/apps/stage-web/index.html b/apps/stage-web/index.html index 664af94b5..255acecda 100644 --- a/apps/stage-web/index.html +++ b/apps/stage-web/index.html @@ -6,7 +6,6 @@ - diff --git a/apps/stage-web/netlify.toml b/apps/stage-web/netlify.toml index b72491b7c..10395feab 100755 --- a/apps/stage-web/netlify.toml +++ b/apps/stage-web/netlify.toml @@ -1,6 +1,6 @@ [build] base = "/" -command = "pnpm -F @proj-airi/stage-web run build && pnpm -F @proj-airi/docs run build:base && mv ./docs/dist ./apps/stage-web/dist/docs && pnpm -F @proj-airi/stage-ui run story:build && mv ./packages/stage-ui/.histoire/dist ./apps/stage-web/dist/ui" +command = "pnpm -F @proj-airi/stage-web run build && pnpm -F @proj-airi/docs run build:base && mv ./docs/.vitepress/dist ./apps/stage-web/dist/docs && pnpm -F @proj-airi/stage-ui run story:build && mv ./packages/stage-ui/.histoire/dist ./apps/stage-web/dist/ui" publish = "/apps/stage-web/dist" [build.environment] diff --git a/apps/stage-web/package.json b/apps/stage-web/package.json index 97cb4d67d..532bf1dd6 100644 --- a/apps/stage-web/package.json +++ b/apps/stage-web/package.json @@ -86,9 +86,9 @@ "devDependencies": { "@iconify-json/carbon": "^1.2.10", "@iconify-json/eos-icons": "^1.2.2", - "@iconify-json/lucide": "^1.2.55", + "@iconify-json/lucide": "^1.2.57", "@iconify-json/mingcute": "^1.2.3", - "@iconify-json/simple-icons": "^1.2.41", + "@iconify-json/simple-icons": "^1.2.43", "@iconify-json/solar": "^1.2.2", "@iconify-json/svg-spinners": "^1.2.2", "@iconify-json/vscode-icons": "^1.2.23", @@ -100,7 +100,7 @@ "@types/audioworklet": "^0.0.77", "@types/culori": "^4.0.0", "@types/nprogress": "^0.2.3", - "@types/three": "^0.178.0", + "@types/three": "^0.178.1", "@types/yauzl": "^2.10.3", "@unocss/reset": "^66.3.3", "@vitejs/plugin-vue": "^6.0.0", diff --git a/cspell.config.yaml b/cspell.config.yaml index 70e7988de..0f3a55f17 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -35,6 +35,7 @@ words: - clippy - collectblock - colorjs + - Comfortaa - composables - cooldown - coreml @@ -78,6 +79,7 @@ words: - Factorio - feaxios - formkit + - frontmatter - giteeai - gltf - gpuu @@ -124,6 +126,7 @@ words: - mineflayer - mingcute - minimizable + - minisearch - mkdist - modelcontextprotocol - modnet @@ -168,6 +171,7 @@ words: - prereleased - pretrained - prismarine + - prng - pthread - quanlai - qwen @@ -210,6 +214,7 @@ words: - tsdown - turborepo - unbundle + - uncrypto - unhead - Unlisten - unocss @@ -223,6 +228,7 @@ words: - vllm - Volcengine - vrma + - vtuber - vueuse - waapi - wavefile diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 6240da8b1..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# build output -dist/ -# generated types -.astro/ - -# dependencies -node_modules/ - -# logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - - -# environment variables -.env -.env.production - -# macOS-specific files -.DS_Store diff --git a/docs/.vitepress/components/Banner.vue b/docs/.vitepress/components/Banner.vue new file mode 100644 index 000000000..9e2d79b3b --- /dev/null +++ b/docs/.vitepress/components/Banner.vue @@ -0,0 +1,37 @@ + + + diff --git a/docs/.vitepress/components/BlogPosts.vue b/docs/.vitepress/components/BlogPosts.vue new file mode 100644 index 000000000..1a78581fe --- /dev/null +++ b/docs/.vitepress/components/BlogPosts.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/docs/.vitepress/components/CardLink.vue b/docs/.vitepress/components/CardLink.vue new file mode 100644 index 000000000..4c314cc8f --- /dev/null +++ b/docs/.vitepress/components/CardLink.vue @@ -0,0 +1,36 @@ + + + diff --git a/docs/.vitepress/components/Chip.vue b/docs/.vitepress/components/Chip.vue new file mode 100644 index 000000000..bf48939c8 --- /dev/null +++ b/docs/.vitepress/components/Chip.vue @@ -0,0 +1,14 @@ + + + diff --git a/docs/.vitepress/components/Contributors.vue b/docs/.vitepress/components/Contributors.vue new file mode 100644 index 000000000..0fc6c6065 --- /dev/null +++ b/docs/.vitepress/components/Contributors.vue @@ -0,0 +1,57 @@ + + + diff --git a/docs/.vitepress/components/DocCarbonAds.vue b/docs/.vitepress/components/DocCarbonAds.vue new file mode 100644 index 000000000..9661bcc98 --- /dev/null +++ b/docs/.vitepress/components/DocCarbonAds.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/docs/.vitepress/components/DocCommunity.vue b/docs/.vitepress/components/DocCommunity.vue new file mode 100644 index 000000000..b62f2b443 --- /dev/null +++ b/docs/.vitepress/components/DocCommunity.vue @@ -0,0 +1,47 @@ + + + diff --git a/docs/.vitepress/components/DocFooter.vue b/docs/.vitepress/components/DocFooter.vue new file mode 100644 index 000000000..95cc5c6c9 --- /dev/null +++ b/docs/.vitepress/components/DocFooter.vue @@ -0,0 +1,114 @@ + + + diff --git a/docs/.vitepress/components/DocFooterLastUpdated.vue b/docs/.vitepress/components/DocFooterLastUpdated.vue new file mode 100644 index 000000000..1294b80aa --- /dev/null +++ b/docs/.vitepress/components/DocFooterLastUpdated.vue @@ -0,0 +1,33 @@ + + + diff --git a/docs/.vitepress/components/DocOutline.vue b/docs/.vitepress/components/DocOutline.vue new file mode 100644 index 000000000..87e009b11 --- /dev/null +++ b/docs/.vitepress/components/DocOutline.vue @@ -0,0 +1,52 @@ + + + diff --git a/docs/.vitepress/components/DocOutlineItem.vue b/docs/.vitepress/components/DocOutlineItem.vue new file mode 100644 index 000000000..44a4010fd --- /dev/null +++ b/docs/.vitepress/components/DocOutlineItem.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/docs/.vitepress/components/DocSidebar.vue b/docs/.vitepress/components/DocSidebar.vue new file mode 100644 index 000000000..cfd837dfc --- /dev/null +++ b/docs/.vitepress/components/DocSidebar.vue @@ -0,0 +1,47 @@ + + + diff --git a/docs/.vitepress/components/DocSidebarItem.vue b/docs/.vitepress/components/DocSidebarItem.vue new file mode 100644 index 000000000..cc6e138ba --- /dev/null +++ b/docs/.vitepress/components/DocSidebarItem.vue @@ -0,0 +1,36 @@ + + + diff --git a/docs/.vitepress/components/DocTopbar.vue b/docs/.vitepress/components/DocTopbar.vue new file mode 100644 index 000000000..283f450f0 --- /dev/null +++ b/docs/.vitepress/components/DocTopbar.vue @@ -0,0 +1,164 @@ + + + diff --git a/docs/.vitepress/components/DropdownMenu.vue b/docs/.vitepress/components/DropdownMenu.vue new file mode 100644 index 000000000..877d80d30 --- /dev/null +++ b/docs/.vitepress/components/DropdownMenu.vue @@ -0,0 +1,67 @@ + + + diff --git a/docs/.vitepress/components/Home.vue b/docs/.vitepress/components/Home.vue new file mode 100644 index 000000000..d799cda57 --- /dev/null +++ b/docs/.vitepress/components/Home.vue @@ -0,0 +1,37 @@ + + + diff --git a/docs/.vitepress/components/HomePage.vue b/docs/.vitepress/components/HomePage.vue new file mode 100644 index 000000000..b0b7eb4f1 --- /dev/null +++ b/docs/.vitepress/components/HomePage.vue @@ -0,0 +1,43 @@ + + + diff --git a/docs/pages/en/index.md b/docs/.vitepress/components/Image.vue similarity index 100% rename from docs/pages/en/index.md rename to docs/.vitepress/components/Image.vue diff --git a/docs/.vitepress/components/Navbar.vue b/docs/.vitepress/components/Navbar.vue new file mode 100644 index 000000000..77fc5aafb --- /dev/null +++ b/docs/.vitepress/components/Navbar.vue @@ -0,0 +1,181 @@ + + + diff --git a/docs/.vitepress/components/SearchCommandBox.vue b/docs/.vitepress/components/SearchCommandBox.vue new file mode 100644 index 000000000..fe2e6a974 --- /dev/null +++ b/docs/.vitepress/components/SearchCommandBox.vue @@ -0,0 +1,226 @@ + + + diff --git a/docs/.vitepress/components/SearchTrigger.vue b/docs/.vitepress/components/SearchTrigger.vue new file mode 100644 index 000000000..6cfc4c65f --- /dev/null +++ b/docs/.vitepress/components/SearchTrigger.vue @@ -0,0 +1,64 @@ + + + diff --git a/docs/.vitepress/components/Spinner.vue b/docs/.vitepress/components/Spinner.vue new file mode 100644 index 000000000..33e5dc3eb --- /dev/null +++ b/docs/.vitepress/components/Spinner.vue @@ -0,0 +1,10 @@ + + + diff --git a/docs/.vitepress/components/ThemeToggle.vue b/docs/.vitepress/components/ThemeToggle.vue new file mode 100644 index 000000000..f418a9334 --- /dev/null +++ b/docs/.vitepress/components/ThemeToggle.vue @@ -0,0 +1,38 @@ + + + diff --git a/docs/.vitepress/composables/edit-link.ts b/docs/.vitepress/composables/edit-link.ts new file mode 100644 index 000000000..d4b8692cc --- /dev/null +++ b/docs/.vitepress/composables/edit-link.ts @@ -0,0 +1,19 @@ +import { useData } from 'vitepress' +import { computed } from 'vue' + +export function useEditLink() { + const { theme, page } = useData() + + return computed(() => { + const { text = 'Edit this page', pattern = '' } = theme.value.editLink || {} + let url: string + if (typeof pattern === 'function') { + url = pattern(page.value) + } + else { + url = pattern.replace(/:path/g, page.value.filePath) + } + + return { url, text } + }) +} diff --git a/docs/.vitepress/composables/outline.ts b/docs/.vitepress/composables/outline.ts new file mode 100644 index 000000000..0f6e951c2 --- /dev/null +++ b/docs/.vitepress/composables/outline.ts @@ -0,0 +1,272 @@ +import type { DefaultTheme } from 'vitepress/theme' +import type { Ref } from 'vue' + +// 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' + +export interface Header { + /** + * The level of the header + * + * `1` to `6` for `

` to `

` + */ + level: number + /** + * The title of the header + */ + title: string + /** + * The slug of the header + * + * Typically the `id` attr of the header anchor + */ + slug: string + /** + * Link of the header + * + * Typically using `#${slug}` as the anchor hash + */ + link: string + /** + * The children of the header + */ + children: Header[] +} + +// cached list of anchor elements from resolveHeaders +const resolvedHeaders: { element: HTMLHeadElement, link: string }[] = [] + +export type MenuItem = Omit & { + element: HTMLHeadElement + children?: MenuItem[] +} + +export function resolveTitle(theme: DefaultTheme.Config) { + return ( + (typeof theme.outline === 'object' + && !Array.isArray(theme.outline) + && theme.outline.label) + || theme.outlineTitle + || 'On this page' + ) +} + +export function getHeaders(range: DefaultTheme.Config['outline']) { + const headers = [ + // @ts-expect-error copied script + ...document.querySelectorAll('article :where(h1,h2,h3,h4,h5,h6)'), + ] + .filter(el => el.id && el.hasChildNodes()) + .map((el) => { + const level = Number(el.tagName[1]) + return { + element: el as HTMLHeadElement, + title: serializeHeader(el), + link: `#${el.id}`, + level, + } + }) + + return resolveHeaders(headers, range) +} + +function serializeHeader(h: Element): string { + let ret = '' + // @ts-expect-error copied script + for (const node of h.childNodes) { + if (node.nodeType === 1) { + if ( + (node as Element).classList.contains('VPBadge') + || (node as Element).classList.contains('header-anchor') + || (node as Element).classList.contains('ignore-header') + ) { + continue + } + ret += node.textContent + } + else if (node.nodeType === 3) { + ret += node.textContent + } + } + return ret.trim() +} + +export function resolveHeaders( + headers: MenuItem[], + range?: DefaultTheme.Config['outline'], +): MenuItem[] { + if (range === false) { + return [] + } + + const levelsRange + = (typeof range === 'object' && !Array.isArray(range) + ? range.level + : range) || 2 + + const [high, low]: [number, number] + = typeof levelsRange === 'number' + ? [levelsRange, levelsRange] + : levelsRange === 'deep' + ? [2, 6] + : levelsRange + + headers = headers.filter(h => h.level >= high && h.level <= low) + // clear previous caches + resolvedHeaders.length = 0 + // update global header list for active link rendering + for (const { element, link } of headers) { + resolvedHeaders.push({ element, link }) + } + + const ret: MenuItem[] = [] + // eslint-disable-next-line no-labels + outer: for (let i = 0; i < headers.length; i++) { + const cur = headers[i] + if (i === 0) { + ret.push(cur) + } + else { + for (let j = i - 1; j >= 0; j--) { + const prev = headers[j] + if (prev.level < cur.level) { + ;(prev.children || (prev.children = [])).push(cur) + // eslint-disable-next-line no-labels + continue outer + } + } + ret.push(cur) + } + } + + return ret +} + +export function useActiveAnchor( + container: Ref, + marker: Ref, +) { + const onScroll = throttleAndDebounce(setActiveLink, 100) + + let prevActiveLink: HTMLAnchorElement | null = null + + onMounted(() => { + requestAnimationFrame(setActiveLink) + window.addEventListener('scroll', onScroll) + }) + + onUpdated(() => { + // sidebar update means a route change + activateLink(location.hash) + }) + + onUnmounted(() => { + window.removeEventListener('scroll', onScroll) + }) + + function setActiveLink() { + const scrollY = window.scrollY + const innerHeight = window.innerHeight + const offsetHeight = document.body.offsetHeight + const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1 + + // resolvedHeaders may be repositioned, hidden or fix positioned + const headers = resolvedHeaders + .map(({ element, link }) => ({ + link, + top: getAbsoluteTop(element), + })) + .filter(({ top }) => !Number.isNaN(top)) + .sort((a, b) => a.top - b.top) + + // no headers available for active link + if (!headers.length) { + activateLink(null) + return + } + + // page top + if (scrollY < 1) { + activateLink(null) + return + } + + // page bottom - highlight last link + if (isBottom) { + activateLink(headers[headers.length - 1].link) + return + } + + // find the last header above the top of viewport + let activeLink: string | null = null + for (const { link, top } of headers) { + if (top > scrollY + getScrollOffset() + 4) { + break + } + activeLink = link + } + activateLink(activeLink) + } + + function activateLink(hash: string | null) { + if (prevActiveLink) { + prevActiveLink.classList.remove('active') + } + + if (hash == null) { + prevActiveLink = null + } + else { + prevActiveLink = container.value.querySelector( + `a[href="${decodeURIComponent(hash)}"]`, + ) + } + + const activeLink = prevActiveLink + + if (activeLink) { + activeLink.classList.add('active') + marker.value.style.top = `${activeLink.offsetTop + 39}px` + marker.value.style.opacity = '1' + } + else { + marker.value.style.top = '33px' + marker.value.style.opacity = '0' + } + } +} + +function getAbsoluteTop(element: HTMLElement): number { + let offsetTop = 0 + while (element !== document.body) { + if (element === null) { + // child element is: + // - not attached to the DOM (display: none) + // - set to fixed position (not scrollable) + // - body or html element (null offsetParent) + return Number.NaN + } + offsetTop += element.offsetTop + element = element.offsetParent as HTMLElement + } + return offsetTop +} + +function throttleAndDebounce(fn: () => void, delay: number): () => void { + let timeoutId: NodeJS.Timeout + let called = false + + return () => { + if (timeoutId) + clearTimeout(timeoutId) + + if (!called) { + fn() + ;(called = true) && setTimeout(() => (called = false), delay) + } + else { + timeoutId = setTimeout(fn, delay) + } + } +} diff --git a/docs/.vitepress/composables/prev-next.ts b/docs/.vitepress/composables/prev-next.ts new file mode 100644 index 000000000..88f8497d8 --- /dev/null +++ b/docs/.vitepress/composables/prev-next.ts @@ -0,0 +1,74 @@ +import { useData } from 'vitepress' +import { computed } from 'vue' + +import { getFlatSideBarLinks, getSidebar, isActive } from './sidebar' + +export function usePrevNext() { + const { page, theme, frontmatter } = useData() + + return computed(() => { + const sidebar = getSidebar(theme.value.sidebar, page.value.relativePath) + const links = getFlatSideBarLinks(sidebar) + + // ignore inner-page links with hashes + const candidates = uniqBy(links, link => link.link.replace(/[?#].*$/, '')) + + const index = candidates.findIndex((link) => { + return isActive(page.value.relativePath, link.link) + }) + + const hidePrev + = (theme.value.docFooter?.prev === false && !frontmatter.value.prev) + || frontmatter.value.prev === false + + const hideNext + = (theme.value.docFooter?.next === false && !frontmatter.value.next) + || frontmatter.value.next === false + + return { + prev: hidePrev + ? undefined + : { + text: + (typeof frontmatter.value.prev === 'string' + ? frontmatter.value.prev + : typeof frontmatter.value.prev === 'object' + ? frontmatter.value.prev.text + : undefined) + ?? candidates[index - 1]?.docFooterText + ?? candidates[index - 1]?.text, + link: + (typeof frontmatter.value.prev === 'object' + ? frontmatter.value.prev.link + : undefined) ?? candidates[index - 1]?.link, + }, + next: hideNext + ? undefined + : { + text: + (typeof frontmatter.value.next === 'string' + ? frontmatter.value.next + : typeof frontmatter.value.next === 'object' + ? frontmatter.value.next.text + : undefined) + ?? candidates[index + 1]?.docFooterText + ?? candidates[index + 1]?.text, + link: + (typeof frontmatter.value.next === 'object' + ? frontmatter.value.next.link + : undefined) ?? candidates[index + 1]?.link, + }, + } as { + prev?: { text?: string, link?: string } + next?: { text?: string, link?: string } + } + }) +} + +function uniqBy(array: T[], keyFn: (item: T) => any): T[] { + const seen = new Set() + return array.filter((item) => { + const k = keyFn(item) + return seen.has(k) ? false : seen.add(k) + }) +} diff --git a/docs/.vitepress/composables/sidebar.ts b/docs/.vitepress/composables/sidebar.ts new file mode 100644 index 000000000..d0cc2a4ed --- /dev/null +++ b/docs/.vitepress/composables/sidebar.ts @@ -0,0 +1,349 @@ +import type { DefaultTheme } from 'vitepress/theme' +import type { ComputedRef, Ref } from 'vue' + +import { useMediaQuery } from '@vueuse/core' +import { useData } from 'vitepress' +import { + computed, + + onMounted, + onUnmounted, + + ref, + watch, + watchEffect, + watchPostEffect, +} from 'vue' + +export interface SidebarControl { + collapsed: Ref + collapsible: ComputedRef + isLink: ComputedRef + isActiveLink: Ref + hasActiveLink: ComputedRef + hasChildren: ComputedRef + toggle: () => void +} + +export interface SidebarItem extends DefaultTheme.SidebarItem {} + +export function useSidebar() { + const { frontmatter, theme } = useData() + const is960 = useMediaQuery('(min-width: 960px)') + + const isOpen = ref(false) + + const hasSidebar = computed(() => { + return ( + frontmatter.value.sidebar !== false + && frontmatter.value.layout !== 'home' + ) + }) + + const hasAside = computed(() => { + if (frontmatter.value.layout === 'home') + return false + if (frontmatter.value.aside != null) + return !!frontmatter.value.aside + return theme.value.aside !== false + }) + + const isSidebarEnabled = computed(() => hasSidebar.value && is960.value) + + function open() { + isOpen.value = true + } + + function close() { + isOpen.value = false + } + + function toggle() { + isOpen.value ? close() : open() + } + + return { + isOpen, + + hasSidebar, + hasAside, + isSidebarEnabled, + open, + close, + toggle, + } +} + +/** + * a11y: cache the element that opened the Sidebar (the menu button) then + * focus that button again when Menu is closed with Escape key. + */ +export function useCloseSidebarOnEscape( + isOpen: Ref, + close: () => void, +) { + let triggerElement: HTMLButtonElement | undefined + + watchEffect(() => { + triggerElement = isOpen.value + ? (document.activeElement as HTMLButtonElement) + : undefined + }) + + onMounted(() => { + window.addEventListener('keyup', onEscape) + }) + + onUnmounted(() => { + window.removeEventListener('keyup', onEscape) + }) + + function onEscape(e: KeyboardEvent) { + if (e.key === 'Escape' && isOpen.value) { + close() + triggerElement?.focus() + } + } +} + +export function useSidebarControl( + item: ComputedRef, +): SidebarControl { + const { page, hash } = useData() + + const collapsed = ref(false) + + const collapsible = computed(() => { + return item.value.collapsed != null + }) + + const isLink = computed(() => { + return !!item.value.link + }) + + const isActiveLink = ref(false) + const updateIsActiveLink = () => { + isActiveLink.value = isActive(page.value.relativePath, item.value.link) + } + + watch([page, item, hash], updateIsActiveLink) + onMounted(updateIsActiveLink) + + const hasActiveLink = computed(() => { + if (isActiveLink.value) { + return true + } + + return item.value.items + ? containsActiveLink(page.value.relativePath, item.value.items) + : false + }) + + const hasChildren = computed(() => { + return !!(item.value.items && item.value.items.length) + }) + + watchEffect(() => { + collapsed.value = !!(collapsible.value && item.value.collapsed) + }) + + watchPostEffect(() => { + ;(isActiveLink.value || hasActiveLink.value) && (collapsed.value = false) + }) + + function toggle() { + if (collapsible.value) { + collapsed.value = !collapsed.value + } + } + + return { + collapsed, + collapsible, + isLink, + isActiveLink, + hasActiveLink, + hasChildren, + toggle, + } +} + +// From https://github.com/vuejs/vitepress/blob/97f9469b6d4eb7ba9de9a1111986581d1f704ec3/src/shared/shared.ts +const inBrowser = typeof document !== 'undefined' + +const HASH_RE = /#.*$/ +const HASH_OR_QUERY_RE = /[?#].*$/ +const INDEX_OR_EXT_RE = /(?:(^|\/)index)?\.(?:md|html)$/ + +export function isActive( + currentPath: string, + matchPath?: string, + asRegex: boolean = false, +): boolean { + if (matchPath === undefined) { + return false + } + + currentPath = normalize(`/${currentPath}`) + + if (asRegex) { + return new RegExp(matchPath).test(currentPath) + } + + if (normalize(matchPath) !== currentPath) { + return false + } + + const hashMatch = matchPath.match(HASH_RE) + + if (hashMatch) { + return (inBrowser ? location.hash : '') === hashMatch[0] + } + + return true +} + +function normalize(path: string): string { + return decodeURI(path) + .replace(HASH_OR_QUERY_RE, '') + .replace(INDEX_OR_EXT_RE, '$1') +} + +// From https://github.com/vuejs/vitepress/blob/97f9469b6d4eb7ba9de9a1111986581d1f704ec3/src/client/theme-default/support/sidebar.ts +function containsActiveLink( + path: string, + items: any | any[], +): boolean { + if (Array.isArray(items)) { + return items.some(item => containsActiveLink(path, item)) + } + + return isActive(path, items.link) + ? true + : items.items + ? containsActiveLink(path, items.items) + : false +} + +// From https://github.com/vuejs/vitepress/blob/fa81e89643523170047ca2c9a690f4d7adf4ffdc/src/client/theme-default/support/sidebar.ts +export interface SidebarLink { + text: string + link: string + docFooterText?: string +} + +function ensureStartingSlash(path: string): string { + return /^\//.test(path) ? path : `/${path}` +} + +/** + * Get the `Sidebar` from sidebar option. This method will ensure to get correct + * sidebar config from `MultiSideBarConfig` with various path combinations such + * as matching `guide/` and `/guide/`. If no matching config was found, it will + * return empty array. + */ +export function getSidebar( + _sidebar: DefaultTheme.Sidebar | undefined, + path: string, +): SidebarItem[] { + if (Array.isArray(_sidebar)) + return addBase(_sidebar) + if (_sidebar == null) + return [] + + path = ensureStartingSlash(path) + + const dir = Object.keys(_sidebar) + .sort((a, b) => { + return b.split('/').length - a.split('/').length + }) + .find((dir) => { + // make sure the multi sidebar key starts with slash too + return path.startsWith(ensureStartingSlash(dir)) + }) + + const sidebar = dir ? _sidebar[dir] : [] + return Array.isArray(sidebar) + ? addBase(sidebar) + : addBase(sidebar.items, sidebar.base) +} + +/** + * Get or generate sidebar group from the given sidebar items. + */ +export function getSidebarGroups(sidebar: SidebarItem[]): SidebarItem[] { + const groups: SidebarItem[] = [] + + let lastGroupIndex: number = 0 + + for (const index in sidebar) { + const item = sidebar[index] + + if (item.items) { + lastGroupIndex = groups.push(item) + continue + } + + if (!groups[lastGroupIndex]) { + groups.push({ items: [] }) + } + + groups[lastGroupIndex]!.items!.push(item) + } + + return groups +} + +export function getFlatSideBarLinks(sidebar: SidebarItem[]): SidebarLink[] { + const links: SidebarLink[] = [] + + function recursivelyExtractLinks(items: SidebarItem[]) { + for (const item of items) { + if (item.text && item.link) { + links.push({ + text: item.text, + link: item.link, + docFooterText: item.docFooterText, + }) + } + + if (item.items) { + recursivelyExtractLinks(item.items) + } + } + } + + recursivelyExtractLinks(sidebar) + + return links +} + +/** + * Check if the given sidebar item contains any active link. + */ +export function hasActiveLink( + path: string, + items: SidebarItem | SidebarItem[], +): boolean { + if (Array.isArray(items)) { + return items.some(item => hasActiveLink(path, item)) + } + + return isActive(path, items.link) + ? true + : items.items + ? hasActiveLink(path, items.items) + : false +} + +function addBase(items: SidebarItem[], _base?: string): SidebarItem[] { + return [...items].map((_item) => { + const item = { ..._item } + const base = item.base || _base + if (base && item.link) + item.link = base + item.link + if (item.items) + item.items = addBase(item.items, base) + return item + }) +} diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts new file mode 100644 index 000000000..a3db5e7b8 --- /dev/null +++ b/docs/.vitepress/config.ts @@ -0,0 +1,179 @@ +import type { DefaultTheme } from 'vitepress' + +import anchor from 'markdown-it-anchor' +import unocss from 'unocss/vite' + +import { footnote } from '@mdit/plugin-footnote' +import { tasklist } from '@mdit/plugin-tasklist' +import { defineConfig, postcssIsolateStyles } from 'vitepress' + +import { version } from '../../package.json' +import { teamMembers } from './contributors' +import { + discord, + github, + ogImage, + ogUrl, + rekaDescription, + rekaName, + rekaShortName, + releases, +} from './meta' + +// function BadgeHTML(text: string, translucent = false) { +// return `
+// ${text} +//
+// ` +// } + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + cleanUrls: true, + ignoreDeadLinks: true, + title: rekaName, + description: rekaDescription, + titleTemplate: rekaShortName, + head: [ + ['meta', { name: 'theme-color', content: '#00C38A' }], + ['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg', sizes: 'any' }], + ['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }], + ['meta', { name: 'apple-mobile-web-app-title', content: rekaName }], + ['meta', { name: 'author', content: `${teamMembers.map(c => c.name).join(', ')} and ${rekaName} contributors` }], + ['meta', { name: 'keywords', content: '' }], + ['meta', { property: 'og:title', content: rekaName }], + ['meta', { property: 'og:description', content: rekaDescription }], + ['meta', { property: 'og:url', content: ogUrl }], + ['meta', { property: 'og:image', content: ogImage }], + ['meta', { name: 'twitter:title', content: rekaName }], + ['meta', { name: 'twitter:description', content: rekaDescription }], + ['meta', { name: 'twitter:image', content: ogImage }], + ['meta', { name: 'twitter:card', content: 'summary_large_image' }], + ['link', { rel: 'mask-icon', href: '/logo.svg', color: '#ffffff' }], + ], + lastUpdated: true, + sitemap: { hostname: ogUrl }, + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Docs', link: '/docs/overview/' }, + { text: 'Blog', link: '/blog/' }, + { + text: `v${version}`, + items: [ + { text: 'Release Notes ', link: releases }, + ], + }, + ], + outline: { + level: 'deep', + }, + logo: '/favicon.svg', + + sidebar: [ + { + text: 'Overview', + icon: 'lucide:rocket', + items: [ + { text: 'Introduction', link: '/docs/overview/' }, + { text: 'About AI Vtuber', link: '/docs/overview/about-ai-vtuber' }, + { text: 'About Neuro-sama', link: '/docs/overview/about-neuro-sama' }, + ], + }, + { + text: 'Guides', + icon: 'lucide:book-open', + items: [ + { text: 'Contributing', link: '/docs/guides/contributing/' }, + { + text: 'Design Guidelines', + link: '/docs/guides/design-guidelines/', + items: [ + { text: 'Resources', link: '/docs/guides/contributing/design-guidelines/resources' }, + { text: 'Tools', link: '/docs/guides/contributing/design-guidelines/tools' }, + ], + }, + ], + }, + { + text: 'Characters', + icon: 'lucide:scan-face', + link: '/characters/', + }, + ] as (DefaultTheme.SidebarItem & { icon?: string })[], + + socialLinks: [ + { icon: 'discord', link: discord }, + { icon: 'github', link: github }, + ], + + search: { + provider: 'local', + }, + editLink: { + pattern: 'https://github.com/moeru-ai/airi/edit/main/docs/content/:path', + }, + }, + srcDir: 'content', + appearance: 'dark', + markdown: { + theme: { + light: 'catppuccin-latte', + dark: 'catppuccin-mocha', + }, + headers: { + level: [2, 3, 4, 5, 6], + }, + config(md) { + md.use(tasklist) + md.use(footnote) + }, + anchor: { + callback(token) { + // set tw `group` modifier to heading element + token.attrSet( + 'class', + 'group relative border-none mb-4 lg:-ml-2 lg:pl-2 lg:pr-2', + ) + }, + permalink: anchor.permalink.linkInsideHeader({ + class: + 'header-anchor [&_span]:focus:opacity-100 [&_span_>_span]:focus:outline', + symbol: ``, + renderAttrs: (slug, state) => { + // From: https://github.com/vuejs/vitepress/blob/256d742b733bfb62d54c78168b0e867b8eb829c9/src/node/markdown/markdown.ts#L263 + // Find `heading_open` with the id identical to slug + const idx = state.tokens.findIndex((token) => { + const attrs = token.attrs + const id = attrs?.find(attr => attr[0] === 'id') + return id && slug === id[1] + }) + // Get the actual heading content + const title = state.tokens[idx + 1].content + return { + 'aria-label': `Permalink to "${title}"`, + } + }, + }), + }, + }, + transformPageData(pageData) { + if (pageData.frontmatter.sidebar != null) + return + + // hide sidebar on showcase page + pageData.frontmatter.sidebar = pageData.frontmatter.layout !== 'showcase' + }, + vite: { + plugins: [ + unocss(), + ], + css: { + postcss: { + plugins: [ + postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] }), + ], + }, + }, + }, +}) diff --git a/docs/.vitepress/contributor-names.json b/docs/.vitepress/contributor-names.json new file mode 100644 index 000000000..f0ecc8805 --- /dev/null +++ b/docs/.vitepress/contributor-names.json @@ -0,0 +1,27 @@ +[ + "nekomeowww", + "luoling8192", + "LemonNekoGH", + "kwaa", + "sumimakito", + "junkwarrior87", + "Menci", + "LJ5O", + "lorisj", + "LittleSound", + "SliverKeigo", + "qyl27", + "exoticknight", + "typed-sigterm", + "liliumneko", + "antfu", + "AprilNEA", + "AsterisMono", + "DWCarrot", + "Ding-Fan", + "leohearts", + "Mirza-Samad-Ahmed-Baig", + "RUI-LONG", + "jokester", + "Me1td0wn76" +] diff --git a/docs/.vitepress/contributors.ts b/docs/.vitepress/contributors.ts new file mode 100644 index 000000000..d53e0a7c5 --- /dev/null +++ b/docs/.vitepress/contributors.ts @@ -0,0 +1,52 @@ +import type { DefaultTheme } from 'vitepress' + +import contributorNames from './contributor-names.json' + +export interface Contributor { + name: string + avatar: string +} + +export interface CoreTeam extends DefaultTheme.TeamMember { + // required to download avatars from GitHub + github: string + twitter?: string + mastodon?: string + discord?: string + youtube?: string +} + +const contributorsAvatars: Record = {} + +function getAvatarUrl(name: string) { + return `https://github.com/${name}.png` +} + +export const contributors = (contributorNames as string[]).reduce((acc, name) => { + contributorsAvatars[name] = getAvatarUrl(name) + acc.push({ name, avatar: contributorsAvatars[name] }) + return acc +}, [] as Contributor[]) +function createLinks(tm: CoreTeam): CoreTeam { + tm.links = [{ icon: 'github', link: `https://github.com/${tm.github}` }] + if (tm.mastodon) + tm.links.push({ icon: 'mastodon', link: tm.mastodon }) + + if (tm.discord) + tm.links.push({ icon: 'discord', link: tm.discord }) + + if (tm.youtube) + tm.links.push({ icon: 'youtube', link: `https://www.youtube.com/@${tm.youtube}` }) + + if (tm.twitter) + tm.links.push({ icon: 'twitter', link: `https://twitter.com/${tm.twitter}` }) + + return tm +} + +// TODO +const plainTeamMembers: CoreTeam[] = [] + +const teamMembers = plainTeamMembers.map(tm => createLinks(tm)) + +export { teamMembers } diff --git a/docs/.vitepress/custom/Docs.vue b/docs/.vitepress/custom/Docs.vue new file mode 100644 index 000000000..aad728ec2 --- /dev/null +++ b/docs/.vitepress/custom/Docs.vue @@ -0,0 +1,115 @@ + + + diff --git a/docs/.vitepress/custom/Layout.vue b/docs/.vitepress/custom/Layout.vue new file mode 100644 index 000000000..59522d216 --- /dev/null +++ b/docs/.vitepress/custom/Layout.vue @@ -0,0 +1,67 @@ + + + diff --git a/docs/.vitepress/custom/Showcase.vue b/docs/.vitepress/custom/Showcase.vue new file mode 100644 index 000000000..f588807f2 --- /dev/null +++ b/docs/.vitepress/custom/Showcase.vue @@ -0,0 +1,71 @@ + + + diff --git a/docs/.vitepress/functions/cache.ts b/docs/.vitepress/functions/cache.ts new file mode 100644 index 000000000..266104c7e --- /dev/null +++ b/docs/.vitepress/functions/cache.ts @@ -0,0 +1,39 @@ +// adapted from https://stackoverflow.com/a/46432113/11613622 +// eslint-disable-next-line ts/ban-ts-comment +// @ts-nocheck +export class LRUCache { + max + cache + constructor(max = 10) { + this.max = max + this.cache = new Map() + } + + get(key) { + const item = this.cache.get(key) + if (item !== undefined) { + // refresh key + this.cache.delete(key) + this.cache.set(key, item) + } + return item + } + + set(key, val) { + // refresh key + if (this.cache.has(key)) + this.cache.delete(key) + // evict oldest + else if (this.cache.size === this.max) + this.cache.delete(this.first()) + this.cache.set(key, val) + } + + first() { + return this.cache.keys().next().value + } + + clear() { + this.cache.clear() + } +} diff --git a/docs/.vitepress/functions/flatten.ts b/docs/.vitepress/functions/flatten.ts new file mode 100644 index 000000000..5271b9a47 --- /dev/null +++ b/docs/.vitepress/functions/flatten.ts @@ -0,0 +1,8 @@ +export function flatten, U extends keyof T>(items: T[], key: U): T[] { + return items.reduce((acc: any[], item: T) => { + acc.push(item) + if (item[key]) + acc.push(...flatten(item[key], key as any)) + return acc + }, []) +} diff --git a/docs/.vitepress/meta.ts b/docs/.vitepress/meta.ts new file mode 100644 index 000000000..fb2fd6267 --- /dev/null +++ b/docs/.vitepress/meta.ts @@ -0,0 +1,20 @@ +// noinspection ES6PreferShortImport: IntelliJ IDE hint to avoid warning to use `~/contributors`, will fail on build if changed + +/* Texts */ +export const rekaName = 'Project AIRI' +export const rekaShortName = 'AIRI' +export const rekaDescription + = 'An open-source attempt to re-create Neuro-sama, the most famous AI VTuber on this earth, but also forming a cyber companion for everyone.' + +/* vitepress head */ +export const ogUrl = 'https://airi.moeru.ai/' +export const ogImage = `${ogUrl}og.jpg` + +/* GitHub and social links */ +export const github = 'https://github.com/moeru-ai/airi' +export const releases = 'https://github.com/moeru-ai/airi/releases' +export const contributing = 'https://github.com/moeru-ai/airi/blob/main/.github/CONTRIBUTING.md' +export const discord = 'https://discord.gg/TgQ3Cu2F7A' + +/* Avatar/Image/Sponsors servers */ +export const sponsor = 'https://github.com/sponsors/nekomeowww' diff --git a/docs/.vitepress/theme/blog.data.ts b/docs/.vitepress/theme/blog.data.ts new file mode 100644 index 000000000..24c8165f2 --- /dev/null +++ b/docs/.vitepress/theme/blog.data.ts @@ -0,0 +1,46 @@ +import { createContentLoader } from 'vitepress' + +interface Post { + title: string + url: string + date: { + time: number + string: string + } + excerpt: string | undefined + frontmatter?: Record +} + +declare const data: Post[] +export { data } + +export default createContentLoader('**/blog/**/*.md', { + includeSrc: true, + render: true, + excerpt: true, + transform(raw): Post[] { + return raw + .map(({ url, frontmatter, excerpt }) => ({ + title: frontmatter.title, + url, + excerpt, + date: formatDate(frontmatter.date), + frontmatter, + })) + .sort((a, b) => b.date.time - a.date.time) + }, +}) + +function formatDate(raw: string): Post['date'] { + const date = new Date(raw) + date.setUTCHours(12) + + return { + time: +date, + string: date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + }), + } +} diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 000000000..851a1ea2e --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,13 @@ +import type { Theme } from 'vitepress' + +import Layout from '../custom/Layout.vue' + +import '@unocss/reset/tailwind.css' +import 'uno.css' +import './style.css' + +export default { + Layout, + enhanceApp(_ctx) { + }, +} satisfies Theme diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 000000000..3a203f6c1 --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -0,0 +1,457 @@ +:root { + --background: 0 0% 100%; + --foreground: 207 10% 3.9%; + + --card: 207 8% 97%; + --card-foreground: 0 10% 3.9%; + + --popover: 0 0% 100%; + --popover-foreground: 207 10% 3.9%; + + --primary: 207 62% 59%; + --primary-foreground: 355.7 100% 97.3%; + + --secondary: 207 4.8% 95.9%; + --secondary-foreground: 207 5.9% 10%; + + --muted: 207 0% 91%; + --muted-foreground: 207 3.8% 46.1%; + + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + + --code: 24 9.8% 10%; /* 0 0% 4%; */ + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 142.1 76.2% 36.3%; + --radius: 0.5rem; +} + +.dark { + --background: 207 15% 5%; + --foreground: 0 0% 95%; + + --card: 207 9.8% 10%; + --card-foreground: 0 0% 95%; + + --popover: 0 0% 9%; + --popover-foreground: 207 0% 95%; + + --primary: 207 62% 59%; + --primary-foreground: 144.9 80.4% 10%; + + --secondary: 207 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + + --muted: 207 0% 15%; + --muted-foreground: 207 2.6% 62.9%; + + --accent: 12 6.5% 15.1%; + --accent-foreground: 0 0% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 85.7% 97.3%; + + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 142.4 71.8% 29.2%; +} + +html { + -webkit-text-size-adjust: 100%; + font-variation-settings: normal; +} + +html.dark { + color-scheme: dark; +} + +body { + @apply bg-background text-foreground min-h-screen antialiased transition-colors duration-200 ease-in-out; + /* font-feature-settings: "rlig" 1, "calt" 1; */ + font-synthesis-weight: none; + text-rendering: optimizeLegibility; + transition: background-color 0.2s ease-in-out; +} + +/* Mobile tap highlight */ +/* https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-tap-highlight-color */ +html { + -webkit-tap-highlight-color: transparent; +} + +*:focus-visible { + @apply outline-primary; +} + +::selection { + background: hsl(var(--primary) / 0.3); +} + +.step { + counter-increment: step; + } + + .step:before { + @apply absolute w-9 h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 border-background; + @apply -ml-[50px] -mt-1; + content: counter(step); + } + + /* Hide scrollbar for Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + /* Hide scrollbar for IE, Edge and Firefox */ + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } + +@media (max-width: 640px) { + .container { + @apply px-4; + } +} + +.custom .vp-code { + @apply border-none rounded-none; +} +.vp-code { + @apply px-6 py-4 my-0 bg-[hsl(24_9.8%_10%)] border border-[hsl(0_0%_15%)] rounded-lg; + + .diff { + @apply inline-block w-[calc(100%_+_48px)] px-6 -mx-6 + } + .diff:before { + @apply absolute left-2.5 + } + + .diff.remove { + @apply bg-red-500/20 opacity-70 + } + .diff.remove:before { + content: "-"; + @apply text-red-500/80 + } + + .diff.add { + @apply bg-green-600/15 + } + .diff.add:before { + content: "+"; + @apply text-green-600/80 + } + + .highlighted { + @apply inline-block w-[calc(100%_+_48px)] px-6 -mx-6 bg-[hsl(0_0%_15%)] + } + + code { + @apply w-fit block min-w-full + } +} + +.custom-block { + @apply rounded-xl px-4 py-3 border border-muted relative overflow-hidden; + + &::before { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 4px; + height: 100%; + border-radius: 999px; + } + + & .custom-block-title { + margin: 0 0 8px 0; + } + + & > p:nth-child(2) { + margin: 0; + display: block; + } +} + +.custom-block.info { + @apply bg-slate-200/20 dark:bg-slate-900/80 border-none; + + &::before { + @apply bg-slate-500/20 dark:bg-slate-400/20; + } + + & .custom-block-title { + @apply text-slate-800 dark:text-slate-300 font-semibold; + } + + &> p:nth-child(2) { + @apply dark:text-slate-50; + } +} + +.custom-block.tip { + @apply bg-violet-50 dark:bg-violet-900/20 border-none; + + &::before { + @apply bg-violet-500/20 dark:bg-violet-400/20; + } + + & .custom-block-title { + @apply text-violet-800 dark:text-violet-300 font-semibold; + } + + & > p:nth-child(2) { + @apply dark:text-violet-50; + } +} + +.custom-block.warning { + @apply bg-yellow-50 dark:bg-yellow-900/20 border-none; + + &::before { + @apply bg-yellow-500/20 dark:bg-yellow-400/20; + } + + & .custom-block-title { + @apply text-yellow-800 dark:text-yellow-300 font-semibold; + } + + &> p:nth-child(2) { + @apply dark:text-yellow-50; + } +} + +.custom-block.danger { + @apply bg-neutral-100 dark:bg-neutral-900/20 border-none; + + &::before { + @apply bg-neutral-500/20 dark:bg-neutral-400/20; + } + + & .custom-block-title { + @apply text-neutral-800 dark:text-neutral-300 font-semibold; + } + + & > p:nth-child(2) { + @apply dark:text-neutral-50; + } +} + +input { + @apply bg-foreground; +} + +.list-box-tips kbd { + padding: 4px 8px; + font-size: 0.8rem; + cursor: pointer; + user-select: none; + position: relative; + bottom: 2px; + font-family: 'DM Sans', -apple-system, BlinkMacSystemFont ,"Segoe UI Adjusted", "Segoe UI", "Liberation Sans", sans-serif; + font-weight: 600; + border: none; +} + +.list-box-tips kbd::after { + display: inline; + content: ''; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 100%; + background: hsl(0 0% 94% / 1); + box-shadow: 0px 2px 0 2px hsl(0 0% 88% / 1); + border-radius: 6px; + font-size: 0.8rem; + cursor: pointer; + user-select: none; + z-index: -1; +} + +.dark .list-box-tips kbd::after { + background: #3c3c3c; + box-shadow: 0 2px 0 2px #292929; +} + +/** +https://apple.stackexchange.com/a/123577 +*/ +.list-box-tips kbd[data-macos-keyboard-key="command"]::before { + content: "⌘"; + vertical-align: top; + margin-right: 4px; +} + +.list-box-tips kbd[data-macos-keyboard-key="option"]::before { + content: "⌥"; + vertical-align: top; + margin-right: 4px; +} + +.list-box-tips kbd[data-windows-keyboard-key="windows"]::before { + content: "⊞"; + vertical-align: top; + margin-right: 4px; + font-size: 1.4em; +} + +.list-box-tips kbd[data-keyboard-key="esc"]::before { + content: "⎋"; + vertical-align: top; + margin-right: 4px; +} + +.list-box-tips kbd[data-keyboard-key="shift"]::before { + content: "⇧"; + vertical-align: top; + margin-right: 4px; +} + +.list-box-tips kbd[data-keyboard-key="return"]::before { + content: "⏎"; + vertical-align: top; + margin-right: 4px; +} + +.list-box-tips kbd[data-keyboard-key="control"]::before { + content: "⌃"; + vertical-align: top; + margin-right: 4px; +} + +.list-box-tips kbd[data-keyboard-key="enter"]::before { + content: "⏎"; + vertical-align: top; + margin-right: 4px; +} + +.list-box-tips kbd[data-keyboard-key="space"]::before { + content: "␣"; + vertical-align: top; + margin-right: 4px; + font-weight: bold; +} + +.list-box-tips kbd[data-keyboard-key="up-arrow"]::before { + content: "↑"; + vertical-align: top; +} + +.list-box-tips kbd[data-keyboard-key="down-arrow"]::before { + content: "↓"; + vertical-align: top; +} + +.list-box-tips kbd[data-keyboard-key="left-arrow"]::before { + content: "←"; + vertical-align: top; +} + +.list-box-tips kbd[data-keyboard-key="right-arrow"]::before { + content: "→"; + vertical-align: top; +} + +mark { + @apply bg-transparent text-foreground font-semibold; +} + +[class*="language"] { + @apply relative mb-4; + + & .lang { + @apply hidden; + } + + /* Better impl for custom copy button */ + & .copy { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/svg%3E"); + background-size: 16px; + background-position: 50% center; + background-repeat: no-repeat; + transition: border-color 0.25s ease 0s, background-color 0.25s ease 0s, opacity 0.25s ease 0s; + @apply absolute top-2 right-2 w-9 h-9 rounded-lg; + } + + & .copied { + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik0yMCA2TDkgMTdsLTUtNSIvPjwvc3ZnPg=="); + } +} + +.step { + counter-increment: step; +} + +.step:before { + @apply absolute w-9 h-9 bg-muted rounded-lg font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 border-background; + @apply -ml-[50px] -mt-1; + content: counter(step); +} + +.image-bg { + @apply scale-75 !transform !-translate-x-1/2 !-translate-y-1/2; +} + +.custom-justify-center:before, +.custom-justify-center:after { + content: ""; + flex: 1; +} + +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.15s ease-in-out; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +main .prose :where(details):not(:where(.not-prose,.not-prose *)) { + background-color:rgba(131, 186, 231, 0.1); + + padding: 1.2rem 1.2rem; + border-radius: 12px; +} + +.dark main .prose :where(details):not(:where(.not-prose,.not-prose *)) { + background-color:rgba(88, 112, 132, 0.06); +} + +pre.shiki { + border-radius: 12px; + background-color: var(--shiki-light-bg); +} + +.dark pre.shiki { + background-color: var(--shiki-dark-bg); +} + +pre.shiki code span { + color: var(--shiki-light); +} + +.dark pre.shiki code span { + color: var(--shiki-dark); +} + +html.dark img.light { + display: none; +} + +html:not(.dark) img.dark { + display: none; +} + +video { + border-radius: 8px; +} diff --git a/docs/astro.config.ts b/docs/astro.config.ts deleted file mode 100644 index e73f88ab3..000000000 --- a/docs/astro.config.ts +++ /dev/null @@ -1,91 +0,0 @@ -import starlight from '@astrojs/starlight' -import UnoCSS from 'unocss/astro' - -import { defineConfig } from 'astro/config' - -// https://astro.build/config -export default defineConfig({ - site: 'https://airi.build', - vite: { - // This kind of error occurs when building astro site: - // The requested module 'nanoid/non-secure' does not provide an export named 'default' - // 12:51:31 AM: Location: - // 12:51:31 AM: /opt/build/repo/node_modules/.pnpm/astro@5.10.1_@types+node@24.0.4_encoding@0.1.13_jiti@2.4.2_less@4.3.0_lightningcss@1.30_07650979d98d2e2072de2da1f022c9b9/node_modules/astro/dist/core/build/pipeline.js:221:15 - // - // Discovered these issues: - // - // [Build Error] The requested module 'x' does not provide an export named 'default' · Issue #8660 · withastro/astro - // https://github.com/withastro/astro/issues/8660 - // - // Related to: - // - // - Issue when importing default from module on SSR https://github.com/vitejs/vite/issues/10712 - // - CJS named imports in an ESM library https://github.com/vitejs/vite/issues/14332 - // - // This is because somehow postcss related script is imported, - // and import require$$0$2 from 'nanoid/non-secure' will be - // generated, while nanoid/non-secure does not have a default export. - ssr: { - noExternal: ['nanoid'], - }, - }, - integrations: [ - UnoCSS(), - starlight({ - title: 'Project AIRI', - logo: { - light: './src/assets/logo.svg', - dark: './src/assets/logo-dark.svg', - }, - social: [ - { - icon: 'seti:github', - label: 'GitHub', - href: 'https://github.com/moeru-ai/airi', - }, - ], - components: { - Head: './src/components/starlight/Head.astro', - }, - sidebar: [ - { - label: 'Guides', - autogenerate: { directory: 'guides' }, - }, - { - label: 'References', - autogenerate: { directory: 'references' }, - }, - { - label: 'Blog', - autogenerate: { directory: 'blog' }, - }, - ], - head: [ - { tag: 'link', attrs: { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg', sizes: 'any' } }, - { tag: 'link', attrs: { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' } }, - { tag: 'meta', attrs: { name: 'apple-mobile-web-app-title', content: 'AIRI' } }, - { tag: 'link', attrs: { ref: 'manifest', href: '/site.webmanifest' } }, - { tag: 'script', attrs: { 'defer': true, 'data-domain': 'airi.moeru.ai', 'data-api': '/api/v1/page-external-data/submit', 'src': '/remote-assets/page-external-data/js/script.js' } }, - ], - tableOfContents: { minHeadingLevel: 2, maxHeadingLevel: 6 }, - customCss: [ - './src/styles/custom.css', - '@fontsource/quicksand/300.css', - '@fontsource/fusion-pixel-12px-proportional-sc/400.css', - ], - // Set English as the default language for this site. - defaultLocale: 'root', - locales: { - 'root': { - label: 'English', - lang: 'en', - }, - 'zh-cn': { - label: '简体中文', - lang: 'zh-CN', - }, - }, - }), - ], -}) diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/airi-build-dark.png b/docs/content/blog/DevLog-2025.03.05/assets/airi-build-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/airi-build-dark.png rename to docs/content/blog/DevLog-2025.03.05/assets/airi-build-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/airi-build-light.png b/docs/content/blog/DevLog-2025.03.05/assets/airi-build-light.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/airi-build-light.png rename to docs/content/blog/DevLog-2025.03.05/assets/airi-build-light.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/airi-logo-v2-dark.png b/docs/content/blog/DevLog-2025.03.05/assets/airi-logo-v2-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/airi-logo-v2-dark.png rename to docs/content/blog/DevLog-2025.03.05/assets/airi-logo-v2-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/airi-logo-v2.png b/docs/content/blog/DevLog-2025.03.05/assets/airi-logo-v2.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/airi-logo-v2.png rename to docs/content/blog/DevLog-2025.03.05/assets/airi-logo-v2.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-final.png b/docs/content/blog/DevLog-2025.03.05/assets/airi-logos-final.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-final.png rename to docs/content/blog/DevLog-2025.03.05/assets/airi-logos-final.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-v1.png b/docs/content/blog/DevLog-2025.03.05/assets/airi-logos-v1.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-v1.png rename to docs/content/blog/DevLog-2025.03.05/assets/airi-logos-v1.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-v2.png b/docs/content/blog/DevLog-2025.03.05/assets/airi-logos-v2.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-v2.png rename to docs/content/blog/DevLog-2025.03.05/assets/airi-logos-v2.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-v3.png b/docs/content/blog/DevLog-2025.03.05/assets/airi-logos-v3.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-v3.png rename to docs/content/blog/DevLog-2025.03.05/assets/airi-logos-v3.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-v4.png b/docs/content/blog/DevLog-2025.03.05/assets/airi-logos-v4.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-v4.png rename to docs/content/blog/DevLog-2025.03.05/assets/airi-logos-v4.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-v5.png b/docs/content/blog/DevLog-2025.03.05/assets/airi-logos-v5.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/airi-logos-v5.png rename to docs/content/blog/DevLog-2025.03.05/assets/airi-logos-v5.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.05/animation-transitions.gif b/docs/content/blog/DevLog-2025.03.05/assets/animation-transitions.gif similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.05/animation-transitions.gif rename to docs/content/blog/DevLog-2025.03.05/assets/animation-transitions.gif diff --git a/docs/public/static/blog/DevLog-2025.03.10/customizable-theme-colors.mp4 b/docs/content/blog/DevLog-2025.03.05/assets/customizable-theme-colors.mp4 similarity index 100% rename from docs/public/static/blog/DevLog-2025.03.10/customizable-theme-colors.mp4 rename to docs/content/blog/DevLog-2025.03.05/assets/customizable-theme-colors.mp4 diff --git a/docs/src/content/docs/blog/DevLog-2025.03.05.md b/docs/content/blog/DevLog-2025.03.05/index.md similarity index 78% rename from docs/src/content/docs/blog/DevLog-2025.03.05.md rename to docs/content/blog/DevLog-2025.03.05/index.md index e70bd4444..9ba6ea6fc 100644 --- a/docs/src/content/docs/blog/DevLog-2025.03.05.md +++ b/docs/content/blog/DevLog-2025.03.05/index.md @@ -1,5 +1,7 @@ --- title: DevLog @ 2025.03.05 +category: DevLog +date: 2025-03-05 --- ## Dejavu @@ -28,42 +30,42 @@ Last week, our corporate designer/artist has submitted our commission for the basic/first version of Project AIRI logo. The feeling of the logo looks like this: -![](../../../assets/images/blog/DevLog-2025.03.05/airi-logos-v1.png) +![](./assets/airi-logos-v1.png) ## Daytime From the perspective of designing, these were so complex and user unfriendly when scaled down to Home Screen app sizes. Therefore I added this version: -![](../../../assets/images/blog/DevLog-2025.03.05/airi-logo-v2.png) +![](./assets/airi-logo-v2.png) And edited with other variants: -![](../../../assets/images/blog/DevLog-2025.03.05/airi-logos-v2.png) +![](./assets/airi-logos-v2.png) All of those were only suitable for dark theme, "we need a dark theme version logo as well!" I thought, so I did this: -![](../../../assets/images/blog/DevLog-2025.03.05/airi-logo-v2-dark.png) +![](./assets/airi-logo-v2-dark.png) [@kwaa](https://github.com/kwaa) said that we can try to swap the color schemes for both themes: -![](../../../assets/images/blog/DevLog-2025.03.05/airi-logos-v3.png) +![](./assets/airi-logos-v3.png) This indeed looks better. We updated the Typography too: -![](../../../assets/images/blog/DevLog-2025.03.05/airi-logos-v4.png) +![](./assets/airi-logos-v4.png) And optimized the background colors: -![](../../../assets/images/blog/DevLog-2025.03.05/airi-logos-v5.png) +![](./assets/airi-logos-v5.png) So this is what we get finally: -![](../../../assets/images/blog/DevLog-2025.03.05/airi-logos-final.png) +![](./assets/airi-logos-final.png) Later on today, I was working on publishing the [documentation site](https://airi.build) of Project AIRI online, for me, @@ -72,8 +74,8 @@ and other developers, artists to use it as a reference and guidelines. I did it! And the newly designed logo has been put into the [documentation site](https://airi.build) along with the color palette: -![](../../../assets/images/blog/DevLog-2025.03.05/airi-build-light.png) -![](../../../assets/images/blog/DevLog-2025.03.05/airi-build-dark.png) +![](./assets/airi-build-light.png) +![](./assets/airi-build-dark.png) [Basic guide](../guides/), [contribution guidelines](../references/contributing/guide/), @@ -102,7 +104,7 @@ I ported many of the animation transitions from [yui540](https://github.com/yui5 It worked really well: -![](../../../assets/images/blog/DevLog-2025.03.05/animation-transitions.gif) +![](./assets/animation-transitions.gif) That's all for today's DevLog, thank you to everyone that joined the DevStream and stayed with me until the end. I'll see you all tomorrow. diff --git a/docs/content/blog/DevLog-2025.03.06/assets/animation-transitions.gif b/docs/content/blog/DevLog-2025.03.06/assets/animation-transitions.gif new file mode 100644 index 000000000..ee2bf5fd3 Binary files /dev/null and b/docs/content/blog/DevLog-2025.03.06/assets/animation-transitions.gif differ diff --git a/docs/src/assets/images/blog/DevLog-2025.03.06/live-stream-layout-update.jpeg b/docs/content/blog/DevLog-2025.03.06/assets/live-stream-layout-update.jpeg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.06/live-stream-layout-update.jpeg rename to docs/content/blog/DevLog-2025.03.06/assets/live-stream-layout-update.jpeg diff --git a/docs/src/content/docs/blog/DevLog-2025.03.06.md b/docs/content/blog/DevLog-2025.03.06/index.md similarity index 96% rename from docs/src/content/docs/blog/DevLog-2025.03.06.md rename to docs/content/blog/DevLog-2025.03.06/index.md index a041dd13c..8146f4a1c 100644 --- a/docs/src/content/docs/blog/DevLog-2025.03.06.md +++ b/docs/content/blog/DevLog-2025.03.06/index.md @@ -1,5 +1,7 @@ --- title: DevLog @ 2025.03.06 +category: DevLog +date: 2025-03-06 --- ## Dejavu @@ -17,7 +19,7 @@ into a reusable Vue component for any of the Vue project to be able to use it. The result is quite good, already deployed to [https://proj-airi-packages-ui-transitions.netlify.app/#/](https://proj-airi-packages-ui-transitions.netlify.app/#/). -![](../../../assets/images/blog/DevLog-2025.03.05/animation-transitions.gif) +![](./assets/animation-transitions.gif) > And also, from now on, all of the playgrounds of each packages will use > "proj-airi" + "${subDirectory}" + "${packageName}" pattern for the Netlify @@ -74,7 +76,7 @@ by myself almost a year ago, but it still looks great and feel calm when looking at it. Please leave comments in the chat for any suggestions, really appreciated. -![](../../../assets/images/blog/DevLog-2025.03.06/live-stream-layout-update.jpeg) +![](./assets/live-stream-layout-update.jpeg) During the DevStream of today, I was trying to integrate the stage transition animation component into AIRI's website main stage, it wasn't that smooth, diff --git a/docs/src/assets/images/blog/DevLog-2025.03.10/customizable-theme-colors.mp4 b/docs/content/blog/DevLog-2025.03.10/assets/customizable-theme-colors.mp4 similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.10/customizable-theme-colors.mp4 rename to docs/content/blog/DevLog-2025.03.10/assets/customizable-theme-colors.mp4 diff --git a/docs/src/assets/images/blog/DevLog-2025.03.10/new-ui-v1-dark.png b/docs/content/blog/DevLog-2025.03.10/assets/new-ui-v1-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.10/new-ui-v1-dark.png rename to docs/content/blog/DevLog-2025.03.10/assets/new-ui-v1-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.10/new-ui-v1.png b/docs/content/blog/DevLog-2025.03.10/assets/new-ui-v1.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.10/new-ui-v1.png rename to docs/content/blog/DevLog-2025.03.10/assets/new-ui-v1.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.10/new-ui-v2.png b/docs/content/blog/DevLog-2025.03.10/assets/new-ui-v2.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.10/new-ui-v2.png rename to docs/content/blog/DevLog-2025.03.10/assets/new-ui-v2.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.10/new-ui-v3-dark.png b/docs/content/blog/DevLog-2025.03.10/assets/new-ui-v3-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.10/new-ui-v3-dark.png rename to docs/content/blog/DevLog-2025.03.10/assets/new-ui-v3-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.10/new-ui-v3.png b/docs/content/blog/DevLog-2025.03.10/assets/new-ui-v3.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.10/new-ui-v3.png rename to docs/content/blog/DevLog-2025.03.10/assets/new-ui-v3.png diff --git a/docs/src/content/docs/blog/DevLog-2025.03.10.md b/docs/content/blog/DevLog-2025.03.10/index.md similarity index 90% rename from docs/src/content/docs/blog/DevLog-2025.03.10.md rename to docs/content/blog/DevLog-2025.03.10/index.md index e319e6f3c..721b41468 100644 --- a/docs/src/content/docs/blog/DevLog-2025.03.10.md +++ b/docs/content/blog/DevLog-2025.03.10/index.md @@ -1,5 +1,7 @@ --- title: DevLog @ 2025.03.10 +category: DevLog +date: 2025-03-10 --- ## Dejavu @@ -22,14 +24,14 @@ Including [@LemonNekoGH](https://github.com/LemonNekoGH), It was me that first finished the basic version of the settings' design, it feels like this: -![](../../../assets/images/blog/DevLog-2025.03.10/new-ui-v1.png) +![](./assets/new-ui-v1.png) -![](../../../assets/images/blog/DevLog-2025.03.10/new-ui-v1-dark.png) +![](./assets/new-ui-v1-dark.png) And later on [@sumimakito](https://github.com/sumimakito) went up online helped me implementing this dotted effect for the buttons: -![](../../../assets/images/blog/DevLog-2025.03.10/new-ui-v2.png) +![](./assets/new-ui-v2.png) > We can now feels more beats from the menu, isn't it?! @@ -79,7 +81,7 @@ At the end of March 8th, [@LemonNekoGH](https://github.com/LemonNekoGH) and colors for the entire stage! (I never thought this could be done within hours...) - https://github.com/moeru-ai/airi/pull/53 @@ -95,9 +97,9 @@ They even made it possible for the logo to follow the customizable colors 🤯. This was the final result we got, give it a try! -![](../../../assets/images/blog/DevLog-2025.03.10/new-ui-v3.png) +![](./assets/new-ui-v3.png) -![](../../../assets/images/blog/DevLog-2025.03.10/new-ui-v3-dark.png) +![](./assets/new-ui-v3-dark.png) And as always, feel free to come and contribute to us! We are absolutely here open and friendly to everyone, even for those not familiar with programming and coding! diff --git a/docs/public/static/blog/DevLog-2025.03.20/airi-demo.mp4 b/docs/content/blog/DevLog-2025.03.20/assets/airi-demo.mp4 similarity index 100% rename from docs/public/static/blog/DevLog-2025.03.20/airi-demo.mp4 rename to docs/content/blog/DevLog-2025.03.20/assets/airi-demo.mp4 diff --git a/docs/public/static/blog/DevLog-2025.03.20/ashley-pitch-test.mp3 b/docs/content/blog/DevLog-2025.03.20/assets/ashley-pitch-test.mp3 similarity index 100% rename from docs/public/static/blog/DevLog-2025.03.20/ashley-pitch-test.mp3 rename to docs/content/blog/DevLog-2025.03.20/assets/ashley-pitch-test.mp3 diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/elevenlabs-api-key.jpeg b/docs/content/blog/DevLog-2025.03.20/assets/elevenlabs-api-key.jpeg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/elevenlabs-api-key.jpeg rename to docs/content/blog/DevLog-2025.03.20/assets/elevenlabs-api-key.jpeg diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/elevenlabs.jpeg b/docs/content/blog/DevLog-2025.03.20/assets/elevenlabs.jpeg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/elevenlabs.jpeg rename to docs/content/blog/DevLog-2025.03.20/assets/elevenlabs.jpeg diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/histoire-color-slider-dark.png b/docs/content/blog/DevLog-2025.03.20/assets/histoire-color-slider-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/histoire-color-slider-dark.png rename to docs/content/blog/DevLog-2025.03.20/assets/histoire-color-slider-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/histoire-color-slider.png b/docs/content/blog/DevLog-2025.03.20/assets/histoire-color-slider.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/histoire-color-slider.png rename to docs/content/blog/DevLog-2025.03.20/assets/histoire-color-slider.png diff --git a/docs/public/static/blog/DevLog-2025.03.20/histoire-first-look.mp4 b/docs/content/blog/DevLog-2025.03.20/assets/histoire-first-look.mp4 similarity index 100% rename from docs/public/static/blog/DevLog-2025.03.20/histoire-first-look.mp4 rename to docs/content/blog/DevLog-2025.03.20/assets/histoire-first-look.mp4 diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/histoire-logo-dark.png b/docs/content/blog/DevLog-2025.03.20/assets/histoire-logo-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/histoire-logo-dark.png rename to docs/content/blog/DevLog-2025.03.20/assets/histoire-logo-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/histoire-logo.png b/docs/content/blog/DevLog-2025.03.20/assets/histoire-logo.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/histoire-logo.png rename to docs/content/blog/DevLog-2025.03.20/assets/histoire-logo.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/modules-i18n.jpeg b/docs/content/blog/DevLog-2025.03.20/assets/modules-i18n.jpeg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/modules-i18n.jpeg rename to docs/content/blog/DevLog-2025.03.20/assets/modules-i18n.jpeg diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/new-ui-v4-speech-dark.png b/docs/content/blog/DevLog-2025.03.20/assets/new-ui-v4-speech-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/new-ui-v4-speech-dark.png rename to docs/content/blog/DevLog-2025.03.20/assets/new-ui-v4-speech-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/new-ui-v4-speech.png b/docs/content/blog/DevLog-2025.03.20/assets/new-ui-v4-speech.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/new-ui-v4-speech.png rename to docs/content/blog/DevLog-2025.03.20/assets/new-ui-v4-speech.png diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/settings-i18n.jpeg b/docs/content/blog/DevLog-2025.03.20/assets/settings-i18n.jpeg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/settings-i18n.jpeg rename to docs/content/blog/DevLog-2025.03.20/assets/settings-i18n.jpeg diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/steins-gate-gelnana-from-elpsycongrooblog.webp b/docs/content/blog/DevLog-2025.03.20/assets/steins-gate-gelnana-from-elpsycongrooblog.webp similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/steins-gate-gelnana-from-elpsycongrooblog.webp rename to docs/content/blog/DevLog-2025.03.20/assets/steins-gate-gelnana-from-elpsycongrooblog.webp diff --git a/docs/src/assets/images/blog/DevLog-2025.03.20/steins-gate-mayori.webp b/docs/content/blog/DevLog-2025.03.20/assets/steins-gate-mayori.webp similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.03.20/steins-gate-mayori.webp rename to docs/content/blog/DevLog-2025.03.20/assets/steins-gate-mayori.webp diff --git a/docs/src/content/docs/blog/DevLog-2025.03.20.mdx b/docs/content/blog/DevLog-2025.03.20/index.md similarity index 73% rename from docs/src/content/docs/blog/DevLog-2025.03.20.mdx rename to docs/content/blog/DevLog-2025.03.20/index.md index 29171e57b..c003a6632 100644 --- a/docs/src/content/docs/blog/DevLog-2025.03.20.mdx +++ b/docs/content/blog/DevLog-2025.03.20/index.md @@ -1,18 +1,21 @@ --- title: DevLog @ 2025.03.20 +category: DevLog +date: 2025-03-20 --- -import { Image } from 'astro:assets'; -import Gelbana from '../../../assets/images/blog/DevLog-2025.03.20/steins-gate-gelnana-from-elpsycongrooblog.webp' -import NewUIV3 from '../../../assets/images/blog/DevLog-2025.03.10/new-ui-v3.png' -import NewUIV3Dark from '../../../assets/images/blog/DevLog-2025.03.10/new-ui-v3-dark.png' -import HistoireColorSlider from '../../../assets/images/blog/DevLog-2025.03.20/histoire-color-slider.png' -import HistoireColorSliderDark from '../../../assets/images/blog/DevLog-2025.03.20/histoire-color-slider-dark.png' -import HistoireLogo from '../../../assets/images/blog/DevLog-2025.03.20/histoire-logo.png' -import HistoireLogoDark from '../../../assets/images/blog/DevLog-2025.03.20/histoire-logo-dark.png' -import NewUIV4Speech from '../../../assets/images/blog/DevLog-2025.03.20/new-ui-v4-speech.png' -import NewUIV4SpeechDark from '../../../assets/images/blog/DevLog-2025.03.20/new-ui-v4-speech-dark.png' -import SteinsGateMayori from '../../../assets/images/blog/DevLog-2025.03.20/steins-gate-mayori.webp' + Hello again! It has been 10 days since the [last post of DevLog](../references/contributing/guide/). @@ -26,7 +29,7 @@ There is so much more that we can't wait to tell you about. Let's rewind the time a little bit! -Gelbana +Gelbana > Ahh, don't worry, our beloved [AIRI](https://github.com/moeru-ai/airi) will > not turn into GEL-NANA like this. BUT, if you haven't watched the @@ -40,8 +43,8 @@ this project, haha, join us if you will. 🥺 (PLEASED FACE)). And this was the final result we got at that time: -new ui -new ui +new ui +new ui

0 @@ -70,7 +73,7 @@ Here's the first look that [@sumimakito](https://github.com/sumimakito) recorded once done: The entire OKLCH color palette can spread on to the canvas all at once @@ -79,16 +82,16 @@ and have the same scheme of feelings of Project AIRI's theme, was it? So I first re-implemented the color slider, which feels much more suitable: -color slider -color slider +color slider +color slider This does make the slider a bit more professional. The logo and the default greenish color can be replaced to align the theme of AIRI, that's why I designed another dedicated logo for the UI page: -project airi logo for histoire -project airi logo for histoire +project airi logo for histoire +project airi logo for histoire Oh, right, the entire UI component has been deployed to Netlify as usual under the path `/ui/`, feel free to take a look at it if you ever wondered @@ -123,8 +126,8 @@ We did some other experiment over the momentum and 3D too. Check this out: -brand new speech design -brand new speech design +brand new speech design +brand new speech design We finally supported speech models configurations 🎉! (Previously was only capable to configure for ElevenLabs) Since the @@ -143,13 +146,13 @@ service was powered by Microsoft, with the voice named `Ashley`, along with a yourself: Isn't it the same, this is insane! That's means, we can finally approach to what Neuro-sama can do with the new **Speech** ability! -character from anime Steins;Gate +character from anime Steins;Gate

1 @@ -165,7 +168,7 @@ what Neuro-sama can do with the new **Speech** ability! With all of those, we can get this result: Nearly the same. But our story doesn't end here, currently, we haven't @@ -188,4 +191,3 @@ all the way down here. I'll see you all tomorrow. > El Psy Congroo. - diff --git a/docs/src/assets/images/blog/DevLog-2025.04.06/awesome-ai-vtuber-logo-light.png b/docs/content/blog/DevLog-2025.04.06/assets/awesome-ai-vtuber-logo-light.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.06/awesome-ai-vtuber-logo-light.png rename to docs/content/blog/DevLog-2025.04.06/assets/awesome-ai-vtuber-logo-light.png diff --git a/docs/src/assets/images/blog/DevLog-2025.04.06/character-card-detail.png b/docs/content/blog/DevLog-2025.04.06/assets/character-card-detail.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.06/character-card-detail.png rename to docs/content/blog/DevLog-2025.04.06/assets/character-card-detail.png diff --git a/docs/src/assets/images/blog/DevLog-2025.04.06/character-card.png b/docs/content/blog/DevLog-2025.04.06/assets/character-card.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.06/character-card.png rename to docs/content/blog/DevLog-2025.04.06/assets/character-card.png diff --git a/docs/src/assets/images/blog/DevLog-2025.04.06/memory-decay.png b/docs/content/blog/DevLog-2025.04.06/assets/memory-decay.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.06/memory-decay.png rename to docs/content/blog/DevLog-2025.04.06/assets/memory-decay.png diff --git a/docs/src/assets/images/blog/DevLog-2025.04.06/memory-retrieval.png b/docs/content/blog/DevLog-2025.04.06/assets/memory-retrieval.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.06/memory-retrieval.png rename to docs/content/blog/DevLog-2025.04.06/assets/memory-retrieval.png diff --git a/docs/src/assets/images/blog/DevLog-2025.04.06/more-theme-colors.png b/docs/content/blog/DevLog-2025.04.06/assets/more-theme-colors.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.06/more-theme-colors.png rename to docs/content/blog/DevLog-2025.04.06/assets/more-theme-colors.png diff --git a/docs/src/assets/images/blog/DevLog-2025.04.06/relu-sticker-wow.png b/docs/content/blog/DevLog-2025.04.06/assets/relu-sticker-wow.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.06/relu-sticker-wow.png rename to docs/content/blog/DevLog-2025.04.06/assets/relu-sticker-wow.png diff --git a/docs/src/content/docs/blog/DevLog-2025.04.06.mdx b/docs/content/blog/DevLog-2025.04.06/index.md similarity index 96% rename from docs/src/content/docs/blog/DevLog-2025.04.06.mdx rename to docs/content/blog/DevLog-2025.04.06/index.md index bc434165f..ef96e0b8f 100644 --- a/docs/src/content/docs/blog/DevLog-2025.04.06.mdx +++ b/docs/content/blog/DevLog-2025.04.06/index.md @@ -1,18 +1,18 @@ --- title: DevLog @ 2025.04.06 -layout: "../../../layouts/98.astro" +category: DevLog +date: 2025-04-06 --- -import { Image } from 'astro:assets'; -import MemoryDecay from '../../../assets/images/blog/DevLog-2025.04.06/memory-decay.png' -import MemoryRetrieval from '../../../assets/images/blog/DevLog-2025.04.06/memory-retrieval.png' -import CharacterCard from '../../../assets/images/blog/DevLog-2025.04.06/character-card.png' -import CharacterCardDetail from '../../../assets/images/blog/DevLog-2025.04.06/character-card-detail.png' -import MoreThemeColors from '../../../assets/images/blog/DevLog-2025.04.06/more-theme-colors.png' -import AwesomeAIVTuber from '../../../assets/images/blog/DevLog-2025.04.06/awesome-ai-vtuber-logo-light.png' -import ReLUStickerWow from '../../../assets/images/blog/DevLog-2025.04.06/relu-sticker-wow.png' - -# DevLog @ 2025.04.06 + ## Before all the others 在其他东西之前 @@ -340,7 +340,7 @@ First we will need to define the schema of our table, the code of the Drizzle sc 首先,我们需要定义表结构,Drizzle 的代码可以参考如下内容: ```typescript -import { index, pgTable, serial, text, vector } from 'drizzle-orm/pg-core'; +import { index, pgTable, serial, text, vector } from 'drizzle-orm/pg-core' export const demoTable = pgTable( 'demo', @@ -351,10 +351,10 @@ export const demoTable = pgTable( url: text('url').notNull().default(''), embedding: vector('embedding', { dimensions: 1536 }), }, - (table) => [ + table => [ index('embeddingIndex').using('hnsw', table.embedding.op('vector_cosine_ops')), ] -); +) ``` The corresponding SQL to create the table looks like this: @@ -363,11 +363,11 @@ The corresponding SQL to create the table looks like this: ```sql CREATE TABLE "chat_messages" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "title" text DEFAULT '' NOT NULL, "description" text DEFAULT '' NOT NULL, "url" text DEFAULT '' NOT NULL, - "embedding" vector(1536) + "embedding" vector(1536) ); CREATE INDEX "embeddingIndex" ON "demo" USING hnsw ("embedding" vector_cosine_ops); @@ -530,7 +530,7 @@ So we did a lot of other things. Like actually implementing a forgetting curve o
- memory decay & retention simulation + memory decay & retention simulation
@@ -560,7 +560,7 @@ we made a little interactive playground for it.
- memory sudden retrieval & emotion biased simulation + memory sudden retrieval & emotion biased simulation
@@ -578,7 +578,6 @@ It is fully interactive too!!! Can be played on [drizzle-orm-duckdb-wasm.netlify - ReLU sticker Vol.2 animated finished! - 89 tasks finished for [Roadmap v0.4](https://github.com/moeru-ai/airi/issues/42) - - 300 🌟 达成 - 3 位新的 Issue 贡献者 - 10 位新的 Discord 成员 @@ -668,7 +667,7 @@ We finally supported Character cards natively!
- character card + character card
@@ -690,7 +689,7 @@ Big shout out to [@luoling8192](https://github.com/luoling8192)
- character card detail + character card detail
@@ -709,7 +708,7 @@ we got color presets included!
- more theme colors + more theme colors
@@ -729,7 +728,7 @@ we got color presets included!
- Awesome AI VTuber Logo + Awesome AI VTuber Logo
Awesome AI VTuber A curated list of AI VTubers and their related projects @@ -788,7 +787,7 @@ Let's use the quote from ReLU for the ending:
- ReLU sticker for expression wow + ReLU sticker for expression wow
Some days I feel like I'm really a symbolic being,
diff --git a/docs/src/assets/images/blog/DevLog-2025.04.14/memory-controler.jpg b/docs/content/blog/DevLog-2025.04.14/assets/memory-controler.jpg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.14/memory-controler.jpg rename to docs/content/blog/DevLog-2025.04.14/assets/memory-controler.jpg diff --git a/docs/src/assets/images/blog/DevLog-2025.04.14/memory-decay.jpg b/docs/content/blog/DevLog-2025.04.14/assets/memory-decay.jpg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.14/memory-decay.jpg rename to docs/content/blog/DevLog-2025.04.14/assets/memory-decay.jpg diff --git a/docs/src/assets/images/blog/DevLog-2025.04.14/memory-driver.jpg b/docs/content/blog/DevLog-2025.04.14/assets/memory-driver.jpg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.14/memory-driver.jpg rename to docs/content/blog/DevLog-2025.04.14/assets/memory-driver.jpg diff --git a/docs/src/assets/images/blog/DevLog-2025.04.14/memory-emotional-model.jpg b/docs/content/blog/DevLog-2025.04.14/assets/memory-emotional-model.jpg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.14/memory-emotional-model.jpg rename to docs/content/blog/DevLog-2025.04.14/assets/memory-emotional-model.jpg diff --git a/docs/src/assets/images/blog/DevLog-2025.04.14/memory-emotional-score.jpg b/docs/content/blog/DevLog-2025.04.14/assets/memory-emotional-score.jpg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.14/memory-emotional-score.jpg rename to docs/content/blog/DevLog-2025.04.14/assets/memory-emotional-score.jpg diff --git a/docs/src/assets/images/blog/DevLog-2025.04.14/memory-emotional-simulator.jpg b/docs/content/blog/DevLog-2025.04.14/assets/memory-emotional-simulator.jpg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.14/memory-emotional-simulator.jpg rename to docs/content/blog/DevLog-2025.04.14/assets/memory-emotional-simulator.jpg diff --git a/docs/src/assets/images/blog/DevLog-2025.04.14/memory-retrieval.jpg b/docs/content/blog/DevLog-2025.04.14/assets/memory-retrieval.jpg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.14/memory-retrieval.jpg rename to docs/content/blog/DevLog-2025.04.14/assets/memory-retrieval.jpg diff --git a/docs/src/assets/images/blog/DevLog-2025.04.14/review-1.jpg b/docs/content/blog/DevLog-2025.04.14/assets/review-1.jpg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.14/review-1.jpg rename to docs/content/blog/DevLog-2025.04.14/assets/review-1.jpg diff --git a/docs/src/assets/images/blog/DevLog-2025.04.14/review-2.jpg b/docs/content/blog/DevLog-2025.04.14/assets/review-2.jpg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.14/review-2.jpg rename to docs/content/blog/DevLog-2025.04.14/assets/review-2.jpg diff --git a/docs/src/content/docs/blog/DevLog-2025.04.14.md b/docs/content/blog/DevLog-2025.04.14/index.md similarity index 92% rename from docs/src/content/docs/blog/DevLog-2025.04.14.md rename to docs/content/blog/DevLog-2025.04.14/index.md index a6b8b99bc..39e26ed2b 100644 --- a/docs/src/content/docs/blog/DevLog-2025.04.14.md +++ b/docs/content/blog/DevLog-2025.04.14/index.md @@ -1,10 +1,12 @@ --- title: DevLog @ 2025.04.14 +category: DevLog +date: 2025-04-14 --- ## 引子 -[上次](../devlog-20250406#memory-system-记忆系统)我们聊到了 AIRI 的记忆系统,这次我们再深入聊聊,如何实现一个这么复杂的记忆系统,以及对未来的展望。 +[上次](../Devlog-2025.04.06/#memory-system-记忆系统)我们聊到了 AIRI 的记忆系统,这次我们再深入聊聊,如何实现一个这么复杂的记忆系统,以及对未来的展望。 ## 先从 搜索引擎 开始 @@ -100,21 +102,21 @@ RAG 现在会流行一个新的流程,就是 reranking model,相当于是** [Project AIRI Memory Driver @duckdb/duckdb-wasm Playground](https://drizzle-orm-duckdb-wasm.netlify.app/#/memory-decay) -![](../../../assets/images/blog/DevLog-2025.04.14/memory-driver.jpg) +![](./assets/memory-driver.jpg) 左边这个高亮的 half life 就是记忆的半衰期。 默认情况下时间流逝速度是 1s 1 天,所以 7s 后,记忆的分数就会减半。 什么是记忆分数?记忆分数基本上是由这个控制的: -![](../../../assets/images/blog/DevLog-2025.04.14/memory-controler.jpg) +![](./assets/memory-controler.jpg) 得出的分数就是 current score 什么是 original 呢,就是初始化的时候的分数。 例子:原始分数为 523,它的当前分数实际上是在慢慢变少的: -![](../../../assets/images/blog/DevLog-2025.04.14/memory-decay.jpg) +![](./assets/memory-decay.jpg) 在继续介绍之前,解释一下,这个遗忘曲线的 SQL 是无状态的。 @@ -158,17 +160,17 @@ RAG 现在会流行一个新的流程,就是 reranking model,相当于是** 我们可以再来回顾一下 DevLog 中提到的排序表达式,应该会能帮助理解。 -![](../../../assets/images/blog/DevLog-2025.04.14/review-1.jpg) +![](./assets/review-1.jpg) 余弦距离就是「相关度」,是最基本的粗排: -![](../../../assets/images/blog/DevLog-2025.04.14/review-2.jpg) +![](./assets/review-2.jpg) 现在需要时间参与进去,那我们多加一个字段拿来存储时间距离就好了,然后再弄一个单独的字段存合并分数 `(1.2 * similarity) + (0.2 * time_relevance)` ,其中 语义相关度 占 1.2 倍权重(倍率因子,不要求小于 1),时间距离相关度占 0.2 倍权重。 这样我们就很巧妙地把无状态的多字段相关度排序 SQL 实现出来了,还让它可以调节参数(1.2 和 0.2)。 在记忆详情卡上,可以点击 simulate retrieval,这可以主动触发一次记忆召回。 -![](../../../assets/images/blog/DevLog-2025.04.14/memory-retrieval.jpg) +![](./assets/memory-retrieval.jpg) 现在的 demo 里面是直接给原本的表里的 retrieval count(召回次数)字段用 UPDATE 语句写了 +1 来实现的。 @@ -183,7 +185,7 @@ RAG 现在会流行一个新的流程,就是 reranking model,相当于是** https://drizzle-orm-duckdb-wasm.netlify.app/#/memory-simulator 这个新的 simulator 里面就有情绪相关的模拟: -![](../../../assets/images/blog/DevLog-2025.04.14/memory-emotional-simulator.jpg) +![](./assets/memory-emotional-simulator.jpg) ### 情绪和记忆相关吗? @@ -194,7 +196,7 @@ https://drizzle-orm-duckdb-wasm.netlify.app/#/memory-simulator 如果对「某段过去的记忆开心,并且希望再次体验它」,但是由于「暂时没办法实现这个记忆里面的场景」,所以觉得「得不到就难过」。 可以在记忆数据库中存储「欢欣」和「厌恶」的分数: -![](../../../assets/images/blog/DevLog-2025.04.14/memory-emotional-score.jpg) +![](./assets/memory-emotional-score.jpg) ### PTSD? @@ -204,7 +206,7 @@ PTSD 通常会涉及到两个词「trigger」和「闪回」,很明显 PTSD 可以参考一下 https://yutsuki.moe/2019/09/a0d0fa1b/ 里面的情绪模型。 -![](../../../assets/images/blog/DevLog-2025.04.14/memory-emotional-model.jpg) +![](./assets/memory-emotional-model.jpg) ## 还有很多事情要做…… diff --git a/docs/public/static/blog/DevLog-2025.04.22/cursor-open-settings.mp4 b/docs/content/blog/DevLog-2025.04.22/assets/cursor-open-settings.mp4 similarity index 100% rename from docs/public/static/blog/DevLog-2025.04.22/cursor-open-settings.mp4 rename to docs/content/blog/DevLog-2025.04.22/assets/cursor-open-settings.mp4 diff --git a/docs/src/assets/images/blog/DevLog-2025.04.22/develop-with-cursor.jpeg b/docs/content/blog/DevLog-2025.04.22/assets/develop-with-cursor.jpeg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.22/develop-with-cursor.jpeg rename to docs/content/blog/DevLog-2025.04.22/assets/develop-with-cursor.jpeg diff --git a/docs/src/content/docs/blog/DevLog-2025.04.22.md b/docs/content/blog/DevLog-2025.04.22/index.md similarity index 96% rename from docs/src/content/docs/blog/DevLog-2025.04.22.md rename to docs/content/blog/DevLog-2025.04.22/index.md index 14f5e09b4..ca2031a26 100644 --- a/docs/src/content/docs/blog/DevLog-2025.04.22.md +++ b/docs/content/blog/DevLog-2025.04.22/index.md @@ -1,5 +1,7 @@ --- title: DevLog @ 2025.04.22 +category: DevLog +date: 2025-04-22 --- ## Day time 日常 @@ -11,7 +13,7 @@ title: DevLog @ 2025.04.22 在上个周末(2025.04.20),我花了点时间,做了一个能与 ADB 交互的 MCP 服务器 Demo [airi-android](https://github.com/LemonNekoGH/airi-android),给 AIRI 提供了最基础的与手机交互的能力(事实上大部分 LLM 都可以通过它与手机交互),这是演示视频: 我也把它打包成了 Docker 镜像,提交到了 [MCP 服务器列表](https://mcp.so/server/airi-android/lemonnekogh),有兴趣的可以试试。 @@ -58,7 +60,7 @@ def get_devices(): 这是我第一次和 AI 写代码的时候感受到像人类一起写代码一样,不知道是不是因为我的目的就是让 AI 来使用我的工具,所以它变成了我的客户,我需要不停地根据它给的反馈来调整我的代码,它也变成了我的同事,我需要和它一起思考,一起解决问题。看这个截屏,是不是确实很像? -![](../../../assets/images/blog/DevLog-2025.04.22/develop-with-cursor.jpeg) +![](./assets/develop-with-cursor.jpeg) 在开发过程中还学了一些小技巧,比如我们可以使用命令行来启动 Android 模拟器,这样就不用打开 Android Studio 了,内存压力也小了很多。 diff --git a/docs/src/assets/images/blog/DevLog-2025.04.28/airi-mcp-arch.jpg b/docs/content/blog/DevLog-2025.04.28/assets/airi-mcp-arch.jpg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.04.28/airi-mcp-arch.jpg rename to docs/content/blog/DevLog-2025.04.28/assets/airi-mcp-arch.jpg diff --git a/docs/public/static/blog/DevLog-2025.04.28/airi-mcp-input-text.mp4 b/docs/content/blog/DevLog-2025.04.28/assets/airi-mcp-input-text.mp4 similarity index 100% rename from docs/public/static/blog/DevLog-2025.04.28/airi-mcp-input-text.mp4 rename to docs/content/blog/DevLog-2025.04.28/assets/airi-mcp-input-text.mp4 diff --git a/docs/public/static/blog/DevLog-2025.04.28/airi-mcp-settings.mp4 b/docs/content/blog/DevLog-2025.04.28/assets/airi-mcp-settings.mp4 similarity index 100% rename from docs/public/static/blog/DevLog-2025.04.28/airi-mcp-settings.mp4 rename to docs/content/blog/DevLog-2025.04.28/assets/airi-mcp-settings.mp4 diff --git a/docs/src/content/docs/blog/DevLog-2025.04.28.mdx b/docs/content/blog/DevLog-2025.04.28/index.md similarity index 91% rename from docs/src/content/docs/blog/DevLog-2025.04.28.mdx rename to docs/content/blog/DevLog-2025.04.28/index.md index e7cf36809..9436b6064 100644 --- a/docs/src/content/docs/blog/DevLog-2025.04.28.mdx +++ b/docs/content/blog/DevLog-2025.04.28/index.md @@ -1,9 +1,12 @@ --- title: DevLog @ 2025.04.28 +category: DevLog +date: 2025-04-28 --- -import { Image } from 'astro:assets'; -import airiMcpArch from '../../../assets/images/blog/DevLog-2025.04.28/airi-mcp-arch.jpg'; + 大家好,这里是 [@LemonNeko](https://github.com/LemonNekoGH),今天由我来和大家一起分享开发故事。 @@ -17,21 +20,21 @@ import airiMcpArch from '../../../assets/images/blog/DevLog-2025.04.28/airi-mcp-
AIRI 的 MCP 服务器设置 -
AIRI 在手机上输入 `Hello World`
开发时,为了理清思路,我画了一张图,从 LLM 调用安卓手机: -AIRI 操作手机 +AIRI 操作手机 接下来和大家分享一下我的开发过程。 @@ -51,12 +54,12 @@ fn list_tools() -> Vec { ```javascript import { invoke } from '@Tauri-apps/api/core' -export mcp = [ +export const mcp = [ { - name: "list_tools", - description: "List all tools", + name: 'list_tools', + description: 'List all tools', execute: async () => { - return await invoke("list_tools") + return await invoke('list_tools') } } ] @@ -158,7 +161,7 @@ async fn call_tool(state: State<'_, Mutex>>, name: String, arg ```javascript import { invoke } from '@Tauri-apps/api/core' -invoke("call_tool", { name: "input_swipe", args: { x1: 100, y1: 100, x2: 200, y2: 200, duration: 500 } }) +invoke('call_tool', { name: 'input_swipe', args: { x1: 100, y1: 100, x2: 200, y2: 200, duration: 500 } }) ``` 超方便! diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/character-card-menu-dark.png b/docs/content/blog/DevLog-2025.05.16/assets/character-card-menu-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/character-card-menu-dark.png rename to docs/content/blog/DevLog-2025.05.16/assets/character-card-menu-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/character-card-menu-light.png b/docs/content/blog/DevLog-2025.05.16/assets/character-card-menu-light.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/character-card-menu-light.png rename to docs/content/blog/DevLog-2025.05.16/assets/character-card-menu-light.png diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/character-card-settings-dark.png b/docs/content/blog/DevLog-2025.05.16/assets/character-card-settings-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/character-card-settings-dark.png rename to docs/content/blog/DevLog-2025.05.16/assets/character-card-settings-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/character-card-settings-light.png b/docs/content/blog/DevLog-2025.05.16/assets/character-card-settings-light.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/character-card-settings-light.png rename to docs/content/blog/DevLog-2025.05.16/assets/character-card-settings-light.png diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/character-card-showcase-dark.png b/docs/content/blog/DevLog-2025.05.16/assets/character-card-showcase-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/character-card-showcase-dark.png rename to docs/content/blog/DevLog-2025.05.16/assets/character-card-showcase-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/character-card-showcase-light.png b/docs/content/blog/DevLog-2025.05.16/assets/character-card-showcase-light.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/character-card-showcase-light.png rename to docs/content/blog/DevLog-2025.05.16/assets/character-card-showcase-light.png diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/demo-day-hangzhou-1.jpeg b/docs/content/blog/DevLog-2025.05.16/assets/demo-day-hangzhou-1.jpeg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/demo-day-hangzhou-1.jpeg rename to docs/content/blog/DevLog-2025.05.16/assets/demo-day-hangzhou-1.jpeg diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/demo-day-hangzhou-2.jpeg b/docs/content/blog/DevLog-2025.05.16/assets/demo-day-hangzhou-2.jpeg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/demo-day-hangzhou-2.jpeg rename to docs/content/blog/DevLog-2025.05.16/assets/demo-day-hangzhou-2.jpeg diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/demo-day-hangzhou-3.jpeg b/docs/content/blog/DevLog-2025.05.16/assets/demo-day-hangzhou-3.jpeg similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/demo-day-hangzhou-3.jpeg rename to docs/content/blog/DevLog-2025.05.16/assets/demo-day-hangzhou-3.jpeg diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/velin-dark.png b/docs/content/blog/DevLog-2025.05.16/assets/velin-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/velin-dark.png rename to docs/content/blog/DevLog-2025.05.16/assets/velin-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/velin-light.png b/docs/content/blog/DevLog-2025.05.16/assets/velin-light.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/velin-light.png rename to docs/content/blog/DevLog-2025.05.16/assets/velin-light.png diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/velin-playground-dark.png b/docs/content/blog/DevLog-2025.05.16/assets/velin-playground-dark.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/velin-playground-dark.png rename to docs/content/blog/DevLog-2025.05.16/assets/velin-playground-dark.png diff --git a/docs/src/assets/images/blog/DevLog-2025.05.16/velin-playground-light.png b/docs/content/blog/DevLog-2025.05.16/assets/velin-playground-light.png similarity index 100% rename from docs/src/assets/images/blog/DevLog-2025.05.16/velin-playground-light.png rename to docs/content/blog/DevLog-2025.05.16/assets/velin-playground-light.png diff --git a/docs/public/static/blog/DevLog-2025.05.16/webai-examples-demo.MP4 b/docs/content/blog/DevLog-2025.05.16/assets/webai-examples-demo.MP4 similarity index 100% rename from docs/public/static/blog/DevLog-2025.05.16/webai-examples-demo.MP4 rename to docs/content/blog/DevLog-2025.05.16/assets/webai-examples-demo.MP4 diff --git a/docs/src/content/docs/blog/DevLog-2025.05.16.mdx b/docs/content/blog/DevLog-2025.05.16/index.md similarity index 81% rename from docs/src/content/docs/blog/DevLog-2025.05.16.mdx rename to docs/content/blog/DevLog-2025.05.16/index.md index 0e9afae3c..f318276d9 100644 --- a/docs/src/content/docs/blog/DevLog-2025.05.16.mdx +++ b/docs/content/blog/DevLog-2025.05.16/index.md @@ -1,27 +1,29 @@ --- title: DevLog @ 2025.05.16 +category: DevLog +date: 2025-05-16 --- -import { Image } from 'astro:assets'; + Hello again! Here's [Neko](https://github.com/nekomeowww), the girl who started the [Project AIRI](https://github.com/moeru-ai/airi)! @@ -54,7 +56,7 @@ Most profoundly appreciated to [@LemonNekoGH](https://github.com/LemonNekoGH) an - Character Card support -:::note[What is Character Card?] +::: tip What is Character Card? Well, local-first chat applications like [SillyTavern](https://github.com/SillyTavern/SillyTavern) , [RisuAI](https://risuai.net/) or online services like [JanitorAI](https://janitorai.com/) uses、 a file contains character's background, personality, and other role-playing essential context for @@ -79,19 +81,19 @@ documentation to share, for now, try read [Void's Lorebook Types](https://rentry > corner of the app, or hovering Gear icon in desktop app), > Find and click the "Airi Card" button. -screenshot of a menu offers Airi Card menu button -screenshot of a menu offers Airi Card menu button +screenshot of a menu offers Airi Card menu button +screenshot of a menu offers Airi Card menu button > This will bring you to the "Airi Card editor screen", where you can upload and > edit your character card for persona customization. -screenshot of a menu offers Airi Card menu button -screenshot of a menu offers Airi Card menu button +screenshot of a menu offers Airi Card menu button +screenshot of a menu offers Airi Card menu button For Character Card showcases, we tried some approaches too... -a card like user interface design for a blue hair character called ReLU -a card like user interface design for a blue hair character called ReLU +a card like user interface design for a blue hair character called ReLU +a card like user interface design for a blue hair character called ReLU It's live in our UI component library, you can play around with it: https://airi.moeru.ai/ui/#/story/src-components-menu-charactercard-story-vue . @@ -138,8 +140,8 @@ Eventually, I got this: which I managed to proof the work can be done on Web browser within one single 300 ~ 500 lines of TypeScript code to achieve ChatGPT voice chat system. -