@@ -1 +0,0 @@
|
|||||||
github: [nekomeowww, kwaa]
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
name: Build Previewing Docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
env:
|
|
||||||
STORE_PATH: ''
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest]
|
|
||||||
|
|
||||||
name: Build - ${{ matrix.os }}
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
# This is quite weird.
|
|
||||||
# Eventhough this is the *intended* solution introduces in official blog post here
|
|
||||||
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/.
|
|
||||||
# But still, as https://github.com/orgs/community/discussions/25220#discussioncomment-7856118 stated,
|
|
||||||
# this is vulnerable since there is no source of truth about which PR in the triggered workflow.
|
|
||||||
- name: Presist PR number
|
|
||||||
run: |
|
|
||||||
echo "${{ github.event.number }}" > pr_num
|
|
||||||
|
|
||||||
- name: Upload PR artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: pr-num
|
|
||||||
path: ./pr_num
|
|
||||||
overwrite: true
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install Node.js 22.x
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22.x
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v3
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build packages
|
|
||||||
run: pnpm packages:build
|
|
||||||
env:
|
|
||||||
# As suggested in Verbose Build option to be able to track down errors https://github.com/vuejs/vitepress/issues/422
|
|
||||||
# vitepress build command does not have --debug option, so we need to set it manually where the debug package is used.
|
|
||||||
DEBUG: none
|
|
||||||
|
|
||||||
- name: Build docs
|
|
||||||
run: pnpm docs:build
|
|
||||||
env:
|
|
||||||
# As suggested in Verbose Build option to be able to track down errors https://github.com/vuejs/vitepress/issues/422
|
|
||||||
# vitepress build command does not have --debug option, so we need to set it manually where the debug package is used.
|
|
||||||
# DEBUG: 'vitepress:*'
|
|
||||||
VUE_PROD_HYDRATION_MISMATCH_DETAILS_FLAG: '1'
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: docs-${{ matrix.os }}-build
|
|
||||||
path: docs/.vitepress/dist
|
|
||||||
overwrite: true
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: lts/*
|
|
||||||
cache: pnpm
|
|
||||||
|
|
||||||
- name: Install
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Lint
|
|
||||||
run: pnpm run lint
|
|
||||||
|
|
||||||
typecheck:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: lts/*
|
|
||||||
cache: pnpm
|
|
||||||
|
|
||||||
- name: Install
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Typecheck
|
|
||||||
run: pnpm run typecheck
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
name: Push Previewing Docs to Netlify
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows:
|
|
||||||
- Build Previewing Docs
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
|
|
||||||
env:
|
|
||||||
PR_NUM: 0
|
|
||||||
STORE_PATH: ''
|
|
||||||
UBUNTU_NETLIFY_JSON_OUTPUT: ''
|
|
||||||
UBUNTU_NETLIFY_URL: ''
|
|
||||||
WINDOWS_NETLIFY_JSON_OUTPUT: ''
|
|
||||||
WINDOWS_NETLIFY_URL: ''
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
on-success:
|
|
||||||
name: Deploy to Netlify
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
||||||
steps:
|
|
||||||
- name: Download artifact - PR
|
|
||||||
uses: dawidd6/action-download-artifact@v7
|
|
||||||
with:
|
|
||||||
workflow_conclusion: success
|
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
|
||||||
name: pr-num
|
|
||||||
path: pr-num
|
|
||||||
allow_forks: true
|
|
||||||
|
|
||||||
- name: Obtain PR number
|
|
||||||
id: pr-num
|
|
||||||
run: |
|
|
||||||
echo "PR_NUM=$(cat pr-num/pr_num)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Download artifact - Ubuntu
|
|
||||||
uses: dawidd6/action-download-artifact@v7
|
|
||||||
with:
|
|
||||||
workflow_conclusion: success
|
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
|
||||||
name: docs-ubuntu-latest-build
|
|
||||||
path: docs-ubuntu-latest-build
|
|
||||||
allow_forks: true
|
|
||||||
|
|
||||||
- name: Install Node.js 22.x
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22.x
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v3
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
version: 9
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install Netlify CLI
|
|
||||||
run: pnpm install -g netlify-cli@17.36.0
|
|
||||||
|
|
||||||
- name: Push to Netlify - Ubuntu
|
|
||||||
id: netlify-ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
run: |
|
|
||||||
UBUNTU_NETLIFY_JSON_OUTPUT=$(netlify deploy --dir docs-ubuntu-latest-build --json)
|
|
||||||
echo $UBUNTU_NETLIFY_JSON_OUTPUT
|
|
||||||
|
|
||||||
echo "UBUNTU_NETLIFY_JSON_OUTPUT=$(echo $UBUNTU_NETLIFY_JSON_OUTPUT)" >> $GITHUB_ENV
|
|
||||||
echo "UBUNTU_NETLIFY_URL=$(echo $UBUNTU_NETLIFY_JSON_OUTPUT | jq -r .deploy_url)" >> $GITHUB_ENV
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
|
||||||
|
|
||||||
- name: Find Comment
|
|
||||||
uses: peter-evans/find-comment@v3
|
|
||||||
id: fc
|
|
||||||
with:
|
|
||||||
issue-number: ${{ env.PR_NUM }}
|
|
||||||
comment-author: 'github-actions[bot]'
|
|
||||||
body-includes: to Netlify
|
|
||||||
|
|
||||||
- name: Create or update comment
|
|
||||||
uses: peter-evans/create-or-update-comment@v4
|
|
||||||
with:
|
|
||||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
|
||||||
issue-number: ${{ env.PR_NUM }}
|
|
||||||
body: |
|
|
||||||
## ✅ Successfully deployed to Netlify
|
|
||||||
|
|
||||||
| Platform | Status | URL |
|
|
||||||
|:---------|:------------|:---------------------------------|
|
|
||||||
| Ubuntu | Success | ${{ env.UBUNTU_NETLIFY_URL }} |
|
|
||||||
edit-mode: replace
|
|
||||||
|
|
||||||
on-failure:
|
|
||||||
name: Failed to build previewing docs
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
|
||||||
steps:
|
|
||||||
- name: Download artifact - PR
|
|
||||||
uses: dawidd6/action-download-artifact@v7
|
|
||||||
with:
|
|
||||||
workflow_conclusion: success
|
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
|
||||||
name: pr-num
|
|
||||||
path: pr-num
|
|
||||||
allow_forks: true
|
|
||||||
|
|
||||||
- name: Obtain PR number
|
|
||||||
id: pr-num
|
|
||||||
run: |
|
|
||||||
echo "PR_NUM=$(cat pr-num/pr_num)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Find Comment
|
|
||||||
uses: peter-evans/find-comment@v3
|
|
||||||
id: fc
|
|
||||||
with:
|
|
||||||
issue-number: ${{ env.PR_NUM }}
|
|
||||||
comment-author: 'github-actions[bot]'
|
|
||||||
body-includes: to Netlify
|
|
||||||
|
|
||||||
- name: Create or update comment
|
|
||||||
uses: peter-evans/create-or-update-comment@v4
|
|
||||||
with:
|
|
||||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
|
||||||
issue-number: ${{ env.PR_NUM }}
|
|
||||||
body: |
|
|
||||||
## ❌ Failed to deploy to Netlify
|
|
||||||
|
|
||||||
| Platform | Status | URL |
|
|
||||||
|:---------|:------------|:------------------------------------------------------|
|
|
||||||
| Ubuntu | Failed | Please check the status and logs of the workflow run. |
|
|
||||||
edit-mode: replace
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
name: Build Docs to Netlify
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
env:
|
|
||||||
STORE_PATH: ''
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
environment:
|
|
||||||
name: Production Docs
|
|
||||||
url: https://nolebase-integrations.ayaka.io
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install Node.js 22.x
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22.x
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v3
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build docs
|
|
||||||
run: |
|
|
||||||
pnpm packages:build
|
|
||||||
pnpm docs:build
|
|
||||||
env:
|
|
||||||
# As suggested in Verbose Build option to be able to track down errors https://github.com/vuejs/vitepress/issues/422
|
|
||||||
# vitepress build command does not have --debug option, so we need to set it manually where the debug package is used.
|
|
||||||
# DEBUG: 'vitepress:*'
|
|
||||||
VUE_PROD_HYDRATION_MISMATCH_DETAILS_FLAG: '1'
|
|
||||||
|
|
||||||
- name: Install Netlify CLI
|
|
||||||
run: pnpm install -g netlify-cli@17.36.0
|
|
||||||
|
|
||||||
- name: Push to Netlify
|
|
||||||
timeout-minutes: 10
|
|
||||||
working-directory: docs/
|
|
||||||
run: |
|
|
||||||
netlify deploy --dir 'docs/.vitepress/dist' --prod --debug
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
shamefully-hoist=true
|
|
||||||
strict-peer-dependencies=false
|
|
||||||
shell-emulator=true
|
|
||||||
auto-install-peers=false
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"installDependencies": true,
|
|
||||||
"startCommand": "npm run dev"
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"antfu.iconify",
|
|
||||||
"antfu.unocss",
|
|
||||||
"antfu.goto-alias",
|
|
||||||
"csstools.postcss",
|
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"vue.volar"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"files.associations": {
|
|
||||||
"*.css": "postcss"
|
|
||||||
},
|
|
||||||
|
|
||||||
// Disable the default formatter, use eslint instead
|
|
||||||
"prettier.enable": false,
|
|
||||||
"editor.formatOnSave": false,
|
|
||||||
|
|
||||||
// Auto fix
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.fixAll.eslint": "explicit",
|
|
||||||
"source.organizeImports": "never"
|
|
||||||
},
|
|
||||||
|
|
||||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
|
||||||
"eslint.rules.customizations": [
|
|
||||||
{ "rule": "style/*", "severity": "off" },
|
|
||||||
{ "rule": "*-indent", "severity": "off" },
|
|
||||||
{ "rule": "*-spacing", "severity": "off" },
|
|
||||||
{ "rule": "*-spaces", "severity": "off" },
|
|
||||||
{ "rule": "*-order", "severity": "off" },
|
|
||||||
{ "rule": "*-dangle", "severity": "off" },
|
|
||||||
{ "rule": "*-newline", "severity": "off" },
|
|
||||||
{ "rule": "*quotes", "severity": "off" },
|
|
||||||
{ "rule": "*semi", "severity": "off" }
|
|
||||||
],
|
|
||||||
|
|
||||||
// Enable eslint for all supported languages
|
|
||||||
"eslint.validate": [
|
|
||||||
"javascript",
|
|
||||||
"javascriptreact",
|
|
||||||
"typescript",
|
|
||||||
"typescriptreact",
|
|
||||||
"vue",
|
|
||||||
"html",
|
|
||||||
"markdown",
|
|
||||||
"json",
|
|
||||||
"jsonc",
|
|
||||||
"yaml"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2021-PRESENT Anthony Fu<https://github.com/antfu>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<h1 align="center">アイリ VTuber</h1>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
[<a href="https://airi.ayaka.io">Try it</a>]
|
|
||||||
</p>
|
|
||||||
|
|
||||||
> Heavily inspired by [Neuro-sama](https://www.youtube.com/@Neurosama)
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pnpm i
|
|
||||||
```
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { appName } from './constants'
|
|
||||||
|
|
||||||
useHead({
|
|
||||||
title: appName,
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<VitePwaManifest />
|
|
||||||
<NuxtLayout>
|
|
||||||
<NuxtPage />
|
|
||||||
</NuxtLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#__nuxt {
|
|
||||||
height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark {
|
|
||||||
background: #222;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
Before Width: | Height: | Size: 163 KiB |
@@ -1,97 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useDark, useElementBounding } from '@vueuse/core'
|
|
||||||
import { onMounted, ref } from 'vue'
|
|
||||||
|
|
||||||
import { useAudioContext } from '../stores/audio'
|
|
||||||
|
|
||||||
const containerRef = ref<HTMLDivElement>()
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode
|
|
||||||
const analyser = ref<AnalyserNode>()
|
|
||||||
const analyserDataBuffer = ref<Uint8Array>()
|
|
||||||
const { audioContext } = useAudioContext()
|
|
||||||
const canvasElemRef = ref<HTMLCanvasElement>()
|
|
||||||
const isDark = useDark()
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/playbackRate
|
|
||||||
// explain: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API
|
|
||||||
// reference: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Simple_synth
|
|
||||||
function fetchAnalyserDataDuringFrames() {
|
|
||||||
if (!analyser.value || !analyserDataBuffer.value || !canvasElemRef.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame
|
|
||||||
requestAnimationFrame(fetchAnalyserDataDuringFrames)
|
|
||||||
if (analyserDataBuffer.value.length > 60 * 2)
|
|
||||||
analyserDataBuffer.value = new Uint8Array(analyser.value.frequencyBinCount)
|
|
||||||
|
|
||||||
analyser.value.getByteTimeDomainData(analyserDataBuffer.value)
|
|
||||||
|
|
||||||
const context = canvasElemRef.value.getContext('2d')!
|
|
||||||
|
|
||||||
if (isDark.value)
|
|
||||||
context.fillStyle = 'rgba(34, 34, 34, 1)'
|
|
||||||
else
|
|
||||||
context.fillStyle = 'rgba(255, 255, 255, 1)'
|
|
||||||
|
|
||||||
context.fillRect(0, 0, canvasElemRef.value.width, canvasElemRef.value.height)
|
|
||||||
|
|
||||||
context.lineWidth = 2
|
|
||||||
if (isDark.value)
|
|
||||||
context.strokeStyle = 'rgb(255 255 255)'
|
|
||||||
else
|
|
||||||
context.strokeStyle = 'rgb(0 0 0)'
|
|
||||||
|
|
||||||
context.beginPath()
|
|
||||||
|
|
||||||
const sliceWidth = (canvasElemRef.value.width * 1.0) / analyser.value.frequencyBinCount
|
|
||||||
let x = 0
|
|
||||||
|
|
||||||
for (let i = 0; i < analyser.value.frequencyBinCount; i++) {
|
|
||||||
const v = analyserDataBuffer.value[i] / 128.0
|
|
||||||
const y = (v * canvasElemRef.value.height) / 2
|
|
||||||
|
|
||||||
if (i === 0)
|
|
||||||
context.moveTo(x, y)
|
|
||||||
else
|
|
||||||
context.lineTo(x, y)
|
|
||||||
|
|
||||||
x += sliceWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
context.lineTo(canvasElemRef.value.width, canvasElemRef.value.height / 2)
|
|
||||||
context.stroke()
|
|
||||||
}
|
|
||||||
|
|
||||||
function initAnalyser() {
|
|
||||||
analyser.value = audioContext.createAnalyser()
|
|
||||||
analyserDataBuffer.value = new Uint8Array(analyser.value.frequencyBinCount)
|
|
||||||
analyser.value.getByteTimeDomainData(analyserDataBuffer.value)
|
|
||||||
const windowAny = window as any
|
|
||||||
windowAny.analyserDataBuffer = analyserDataBuffer
|
|
||||||
|
|
||||||
fetchAnalyserDataDuringFrames()
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
analyser: () => analyser.value,
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (!containerRef.value || !canvasElemRef.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
const containerElementBounding = useElementBounding(containerRef.value)
|
|
||||||
containerElementBounding.update()
|
|
||||||
|
|
||||||
initAnalyser()
|
|
||||||
|
|
||||||
canvasElemRef.value.width = containerElementBounding.width.value
|
|
||||||
canvasElemRef.value.height = containerElementBounding.height.value
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div ref="containerRef" h="[80px]" w-full>
|
|
||||||
<canvas ref="canvasElemRef" h-full w-full />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<script setup lang="ts" generic="T extends any, O extends any">
|
|
||||||
import type { CSSProperties } from 'vue'
|
|
||||||
import { nextTick, onMounted, ref } from 'vue'
|
|
||||||
|
|
||||||
const events = defineEmits<{
|
|
||||||
(event: 'submit', message: string): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const input = defineModel<string>({
|
|
||||||
default: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const textareaRef = ref<HTMLTextAreaElement>()
|
|
||||||
const textareaStyle = ref<CSSProperties>({
|
|
||||||
height: 'auto',
|
|
||||||
overflowY: 'hidden',
|
|
||||||
})
|
|
||||||
|
|
||||||
// javascript - Creating a textarea with auto-resize - Stack Overflow
|
|
||||||
// https://stackoverflow.com/questions/454202/creating-a-textarea-with-auto-resize
|
|
||||||
function onInput(e: Event) {
|
|
||||||
if (!(e.target instanceof HTMLTextAreaElement))
|
|
||||||
return
|
|
||||||
|
|
||||||
e.target.style.height = 'auto'
|
|
||||||
e.target.style.height = `${e.target.scrollHeight}px`
|
|
||||||
}
|
|
||||||
|
|
||||||
// javascript - How do I detect "shift+enter" and generate a new line in Textarea? - Stack Overflow
|
|
||||||
// https://stackoverflow.com/questions/6014702/how-do-i-detect-shiftenter-and-generate-a-new-line-in-textarea
|
|
||||||
function onKeyDown(e: KeyboardEvent) {
|
|
||||||
if (!(e.target instanceof HTMLTextAreaElement))
|
|
||||||
return
|
|
||||||
|
|
||||||
if (e.code === 'Enter' && e.shiftKey) {
|
|
||||||
e.preventDefault()
|
|
||||||
const start = e.target?.selectionStart
|
|
||||||
const end = e.target?.selectionEnd
|
|
||||||
input.value = `${input.value.substring(0, start)}\n${input.value.substring(end)}`
|
|
||||||
|
|
||||||
// javascript - height of textarea increases when value increased but does not reduce when value is decreased - Stack Overflow
|
|
||||||
// https://stackoverflow.com/questions/10722058/height-of-textarea-increases-when-value-increased-but-does-not-reduce-when-value
|
|
||||||
textareaStyle.value.height = '0'
|
|
||||||
|
|
||||||
nextTick().then(() => {
|
|
||||||
if (!textareaRef.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
textareaRef.value.selectionStart = textareaRef.value.selectionEnd = start + 1
|
|
||||||
textareaStyle.value.height = `${textareaRef.value.scrollHeight}px`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else if (e.code === 'Enter') { // block enter
|
|
||||||
e.preventDefault()
|
|
||||||
events('submit', input.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (!textareaRef.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
textareaStyle.value.height = `${textareaRef.value.scrollHeight}px`
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<textarea
|
|
||||||
ref="textareaRef"
|
|
||||||
v-model="input"
|
|
||||||
:style="textareaStyle"
|
|
||||||
@input="onInput"
|
|
||||||
@keydown="onKeyDown"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { Application } from '@pixi/app'
|
|
||||||
import { extensions } from '@pixi/extensions'
|
|
||||||
import { Ticker, TickerPlugin } from '@pixi/ticker'
|
|
||||||
import { useElementBounding, useWindowSize } from '@vueuse/core'
|
|
||||||
import { Live2DModel, MotionPreloadStrategy, MotionPriority } from 'pixi-live2d-display/cubism4'
|
|
||||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
|
||||||
model: string
|
|
||||||
mouthOpenSize?: number
|
|
||||||
}>(), {
|
|
||||||
mouthOpenSize: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
const containerRef = ref<HTMLDivElement>()
|
|
||||||
const pixiApp = ref<Application>()
|
|
||||||
const pixiAppCanvas = ref<HTMLCanvasElement>()
|
|
||||||
const model = ref<Live2DModel>()
|
|
||||||
const mouthOpenSize = computed(() => {
|
|
||||||
return Math.max(0, Math.min(100, props.mouthOpenSize))
|
|
||||||
})
|
|
||||||
|
|
||||||
const { width, height } = useWindowSize()
|
|
||||||
const containerElementBounding = useElementBounding(containerRef)
|
|
||||||
const containerParentElementBounding = useElementBounding(containerRef.value?.parentElement)
|
|
||||||
|
|
||||||
function getCoreModel() {
|
|
||||||
return model.value!.internalModel.coreModel as any
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initLive2DPixiStage(parent: HTMLDivElement) {
|
|
||||||
containerElementBounding.update()
|
|
||||||
containerParentElementBounding.update()
|
|
||||||
|
|
||||||
// https://guansss.github.io/pixi-live2d-display/#package-importing
|
|
||||||
Live2DModel.registerTicker(Ticker)
|
|
||||||
extensions.add(TickerPlugin)
|
|
||||||
|
|
||||||
pixiApp.value = new Application({
|
|
||||||
width: containerElementBounding.width.value,
|
|
||||||
height: Math.max(600, containerParentElementBounding.height.value),
|
|
||||||
backgroundAlpha: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
pixiAppCanvas.value = pixiApp.value.view
|
|
||||||
parent.appendChild(pixiApp.value.view)
|
|
||||||
|
|
||||||
model.value = await Live2DModel.from(props.model, { motionPreload: MotionPreloadStrategy.ALL })
|
|
||||||
pixiApp.value.stage.addChild(model.value as any)
|
|
||||||
|
|
||||||
model.value.x = containerElementBounding.width.value / 2
|
|
||||||
model.value.y = Math.max(600, containerParentElementBounding.height.value)
|
|
||||||
model.value.rotation = Math.PI
|
|
||||||
model.value.skew.x = Math.PI
|
|
||||||
model.value.scale.set(0.3, 0.3)
|
|
||||||
model.value.anchor.set(0.5, 0.5)
|
|
||||||
|
|
||||||
model.value.on('hit', (hitAreas) => {
|
|
||||||
if (model.value && hitAreas.includes('body'))
|
|
||||||
model.value.motion('tap_body')
|
|
||||||
})
|
|
||||||
|
|
||||||
const coreModel = model.value.internalModel.coreModel as any
|
|
||||||
coreModel.setParameterValueById('ParamMouthOpenY', mouthOpenSize.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setMotion(motionName: string) {
|
|
||||||
await model.value!.motion(motionName, undefined, MotionPriority.FORCE)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch([width, height], () => {
|
|
||||||
if (pixiApp.value)
|
|
||||||
pixiApp.value.renderer.resize((width.value - 16) / 2, 550)
|
|
||||||
|
|
||||||
if (pixiAppCanvas.value) {
|
|
||||||
pixiAppCanvas.value.width = (width.value - 16) / 2
|
|
||||||
pixiAppCanvas.value.height = Math.max(600, containerParentElementBounding.height.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model.value) {
|
|
||||||
model.value.x = (width.value - 16) / 4
|
|
||||||
model.value.y = Math.max(600, containerParentElementBounding.height.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (!containerRef.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
await initLive2DPixiStage(containerRef.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
pixiApp.value?.destroy()
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(mouthOpenSize, (value) => {
|
|
||||||
getCoreModel().setParameterValueById('ParamMouthOpenY', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
setMotion,
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div ref="containerRef" h-full w-full />
|
|
||||||
</template>
|
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type {
|
|
||||||
CoreAssistantMessage,
|
|
||||||
CoreSystemMessage,
|
|
||||||
CoreUserMessage,
|
|
||||||
} from 'ai'
|
|
||||||
import type {
|
|
||||||
Emotion,
|
|
||||||
} from '../constants/emotions'
|
|
||||||
|
|
||||||
import { useLocalStorage } from '@vueuse/core'
|
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
|
||||||
import Avatar from '../assets/live2d/models/hiyori_free_zh/avatar.png'
|
|
||||||
import { useMarkdown } from '../composables/markdown'
|
|
||||||
|
|
||||||
import { useQueue } from '../composables/queue'
|
|
||||||
import {
|
|
||||||
useDelayMessageQueue,
|
|
||||||
useEmotionsMessageQueue,
|
|
||||||
useMessageContentQueue,
|
|
||||||
} from '../composables/queues'
|
|
||||||
import { llmInferenceEndToken } from '../constants'
|
|
||||||
import {
|
|
||||||
EMOTION_EmotioMotionName_value,
|
|
||||||
EmotionThinkMotionName,
|
|
||||||
} from '../constants/emotions'
|
|
||||||
import SystemPromptV2 from '../constants/prompts/system-v2'
|
|
||||||
import { useLLM } from '../stores/llm'
|
|
||||||
|
|
||||||
import BasicTextarea from './BasicTextarea.vue'
|
|
||||||
// import AudioWaveform from './AudioWaveform.vue'
|
|
||||||
import Live2DViewer from './Live2DViewer.vue'
|
|
||||||
|
|
||||||
const nowSpeakingAvatarBorderOpacityMin = 30
|
|
||||||
const nowSpeakingAvatarBorderOpacityMax = 100
|
|
||||||
|
|
||||||
const openAiApiKey = useLocalStorage('openai-api-key', '')
|
|
||||||
const openAiApiBaseURL = useLocalStorage('openai-api-base-url', '')
|
|
||||||
const openAIModel = useLocalStorage<{ id: string, name?: string }>('openai-model', { id: 'openai/gpt-3.5-turbo', name: 'OpenAI GPT3.5 Turbo' })
|
|
||||||
const elevenLabsApiKey = useLocalStorage('elevenlabs-api-key', '')
|
|
||||||
|
|
||||||
const { setupOpenAI, streamSpeech, stream, models } = useLLM()
|
|
||||||
const { audioContext, calculateVolume } = useAudioContext()
|
|
||||||
const { process } = useMarkdown()
|
|
||||||
|
|
||||||
const listening = ref(false)
|
|
||||||
const live2DViewerRef = ref<{ setMotion: (motionName: string) => Promise<void> }>()
|
|
||||||
const supportedModels = ref<{ id: string, name?: string }[]>([])
|
|
||||||
const messageInput = ref<string>('')
|
|
||||||
const messages = ref<Array<CoreAssistantMessage | CoreUserMessage | CoreSystemMessage>>([SystemPromptV2 as CoreSystemMessage])
|
|
||||||
const streamingMessage = ref<CoreAssistantMessage>({ role: 'assistant', content: '' })
|
|
||||||
const audioAnalyser = ref<AnalyserNode>()
|
|
||||||
const mouthOpenSize = ref(0)
|
|
||||||
const nowSpeaking = ref(false)
|
|
||||||
const model = ref('')
|
|
||||||
const lipSyncStarted = ref(false)
|
|
||||||
|
|
||||||
const nowSpeakingAvatarBorderOpacity = computed<number>(() => {
|
|
||||||
if (!nowSpeaking.value)
|
|
||||||
return nowSpeakingAvatarBorderOpacityMin
|
|
||||||
|
|
||||||
return ((nowSpeakingAvatarBorderOpacityMin
|
|
||||||
+ (nowSpeakingAvatarBorderOpacityMax - nowSpeakingAvatarBorderOpacityMin) * mouthOpenSize.value) / 100)
|
|
||||||
})
|
|
||||||
|
|
||||||
function handleModelChange(event: Event) {
|
|
||||||
const target = event.target as HTMLSelectElement
|
|
||||||
const found = supportedModels.value.find(m => m.id === target.value)
|
|
||||||
if (!found) {
|
|
||||||
openAIModel.value = undefined
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
openAIModel.value = found
|
|
||||||
}
|
|
||||||
|
|
||||||
const audioQueue = useQueue<{ audioBuffer: AudioBuffer, text: string }>({
|
|
||||||
handlers: [
|
|
||||||
(ctx) => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
// Create an AudioBufferSourceNode
|
|
||||||
const source = audioContext.createBufferSource()
|
|
||||||
source.buffer = ctx.data.audioBuffer
|
|
||||||
|
|
||||||
// Connect the source to the AudioContext's destination (the speakers)
|
|
||||||
source.connect(audioContext.destination)
|
|
||||||
// Connect the source to the analyzer
|
|
||||||
source.connect(audioAnalyser.value!)
|
|
||||||
|
|
||||||
// Start playing the audio
|
|
||||||
nowSpeaking.value = true
|
|
||||||
source.start(0)
|
|
||||||
source.onended = () => {
|
|
||||||
nowSpeaking.value = false
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const ttsQueue = useQueue<string>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
const now = Date.now()
|
|
||||||
const res = await streamSpeech(ctx.data, elevenLabsApiKey.value)
|
|
||||||
const elapsed = Date.now() - now
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.debug('TTS took', elapsed, 'ms')
|
|
||||||
|
|
||||||
// Decode the ArrayBuffer into an AudioBuffer
|
|
||||||
const audioBuffer = await audioContext.decodeAudioData(res)
|
|
||||||
await audioQueue.add({ audioBuffer, text: ctx.data })
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
ttsQueue.on('add', (content) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.debug('ttsQueue added', content)
|
|
||||||
})
|
|
||||||
|
|
||||||
const messageContentQueue = useMessageContentQueue(ttsQueue)
|
|
||||||
|
|
||||||
const emotionsQueue = useQueue<Emotion>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
await live2DViewerRef.value!.setMotion(EMOTION_EmotioMotionName_value[ctx.data])
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const emotionMessageContentQueue = useEmotionsMessageQueue(emotionsQueue, messageContentQueue)
|
|
||||||
emotionMessageContentQueue.onHandlerEvent('emotion', (emotion) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.debug('emotion detected', emotion)
|
|
||||||
})
|
|
||||||
|
|
||||||
const delaysQueue = useDelayMessageQueue(emotionMessageContentQueue)
|
|
||||||
delaysQueue.onHandlerEvent('delay', (delay) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.debug('delay detected', delay)
|
|
||||||
})
|
|
||||||
|
|
||||||
function getVolumeWithMinMaxNormalizeWithFrameUpdates() {
|
|
||||||
requestAnimationFrame(getVolumeWithMinMaxNormalizeWithFrameUpdates)
|
|
||||||
if (!nowSpeaking.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
mouthOpenSize.value = calculateVolume(audioAnalyser.value!, 'linear')
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupLipSync() {
|
|
||||||
if (!lipSyncStarted.value) {
|
|
||||||
getVolumeWithMinMaxNormalizeWithFrameUpdates()
|
|
||||||
audioContext.resume()
|
|
||||||
lipSyncStarted.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupAnalyser() {
|
|
||||||
if (!audioAnalyser.value)
|
|
||||||
audioAnalyser.value = audioContext.createAnalyser()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onSendMessage(sendingMessage: string) {
|
|
||||||
if (!sendingMessage)
|
|
||||||
return
|
|
||||||
|
|
||||||
setupLipSync()
|
|
||||||
setupAnalyser()
|
|
||||||
|
|
||||||
streamingMessage.value = { role: 'assistant', content: '' }
|
|
||||||
messages.value.push({ role: 'user', content: sendingMessage })
|
|
||||||
messages.value.push(streamingMessage.value)
|
|
||||||
// const index = messages.value.length - 1
|
|
||||||
live2DViewerRef.value?.setMotion(EmotionThinkMotionName)
|
|
||||||
|
|
||||||
const res = await stream(model.value, messages.value.slice(0, messages.value.length - 1))
|
|
||||||
|
|
||||||
enum States {
|
|
||||||
Literal = 'literal',
|
|
||||||
Special = 'special',
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = States.Literal
|
|
||||||
let buffer = ''
|
|
||||||
|
|
||||||
for await (const textPart of res.textStream) {
|
|
||||||
for (const textSingleChar of textPart) {
|
|
||||||
let newState: States = state
|
|
||||||
|
|
||||||
if (textSingleChar === '<')
|
|
||||||
newState = States.Special
|
|
||||||
else if (textSingleChar === '>')
|
|
||||||
newState = States.Literal
|
|
||||||
|
|
||||||
if (state === States.Literal && newState === States.Special) {
|
|
||||||
streamingMessage.value.content += buffer
|
|
||||||
buffer = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === States.Special && newState === States.Literal)
|
|
||||||
buffer = '' // Clear buffer when exiting Special state
|
|
||||||
|
|
||||||
if (state === States.Literal && newState === States.Literal) {
|
|
||||||
streamingMessage.value.content += textSingleChar
|
|
||||||
buffer = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
await delaysQueue.add(textSingleChar)
|
|
||||||
state = newState
|
|
||||||
buffer += textSingleChar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer)
|
|
||||||
streamingMessage.value.content += buffer
|
|
||||||
|
|
||||||
await delaysQueue.add(llmInferenceEndToken)
|
|
||||||
|
|
||||||
messageInput.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(openAiApiKey, async (value) => {
|
|
||||||
setupOpenAI({
|
|
||||||
apiKey: value,
|
|
||||||
baseURL: openAiApiBaseURL.value,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchedModels = await models()
|
|
||||||
supportedModels.value = fetchedModels.data
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (!openAiApiKey.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
setupOpenAI({
|
|
||||||
apiKey: openAiApiKey.value,
|
|
||||||
baseURL: openAiApiBaseURL.value,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchedModels = await models()
|
|
||||||
supportedModels.value = fetchedModels.data
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
lipSyncStarted.value = false
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div max-h="[100vh]" h-full p="2" flex="~ col">
|
|
||||||
<div space-x="2" flex="~ row" w-full>
|
|
||||||
<div flex="~ row" w-full>
|
|
||||||
<input
|
|
||||||
v-model="openAiApiBaseURL"
|
|
||||||
placeholder="Input your API base URL"
|
|
||||||
p="2" bg="zinc-100 dark:zinc-700" w-full rounded-lg outline-none
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div flex="~ row" w-full>
|
|
||||||
<input
|
|
||||||
v-model="openAiApiKey"
|
|
||||||
placeholder="Input your API key"
|
|
||||||
p="2" bg="zinc-100 dark:zinc-700" w-full rounded-lg outline-none
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div flex="~ row" w-full>
|
|
||||||
<input
|
|
||||||
v-model="elevenLabsApiKey"
|
|
||||||
placeholder="Input your ElevenLabs API key"
|
|
||||||
p="2" bg="zinc-100 dark:zinc-700" w-full rounded-lg outline-none
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div flex="~ row 1" w-full items-end space-x-2>
|
|
||||||
<div w-full min-h="100 sm:100">
|
|
||||||
<Live2DViewer ref="live2DViewerRef" :mouth-open-size="mouthOpenSize" model="/assets/live2d/models/hiyori_pro_zh/runtime/hiyori_pro_t11.model3.json" />
|
|
||||||
<!-- <div>
|
|
||||||
<input v-model.number="mouthOpenSize" type="range" max="1" min="0" step="0.01">
|
|
||||||
<span>{{ mouthOpenSize }}</span>
|
|
||||||
</div> -->
|
|
||||||
<!-- <AudioWaveform ref="audioWaveformRef" /> -->
|
|
||||||
</div>
|
|
||||||
<div my="2" w-full space-y-2 max-h="[calc(100vh-117px)]">
|
|
||||||
<div v-for="(message, index) in messages" :key="index">
|
|
||||||
<div v-if="message.role === 'assistant'" flex mr="12">
|
|
||||||
<div
|
|
||||||
mr-2 h-10 min-h-10 min-w-10 w-10 overflow-hidden rounded-full
|
|
||||||
border="solid 3"
|
|
||||||
transition="all ease-in-out" duration-100
|
|
||||||
:style="{
|
|
||||||
borderColor: `rgba(236, 72, 153, ${nowSpeakingAvatarBorderOpacity.toFixed(2)})`,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<img :src="Avatar">
|
|
||||||
</div>
|
|
||||||
<div flex="~ col" bg="pink-50/50 dark:pink-900/50" p="2" border="2 solid pink/10" rounded-lg>
|
|
||||||
<div>
|
|
||||||
<span font-semibold>Neuro</span>
|
|
||||||
</div>
|
|
||||||
<div v-html="process(message.content as string)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="message.role === 'user'" flex="~ row-reverse" ml="12">
|
|
||||||
<div border="purple solid 3" ml="2" h-10 min-h-10 min-w-10 w-10 overflow-hidden rounded-full>
|
|
||||||
<div i-carbon:user-avatar-filled text="purple" h-full w-full p="0" m="0" />
|
|
||||||
</div>
|
|
||||||
<div flex="~ col" bg="purple-50/50 dark:purple-900/50" p="2" border="2 solid pink/10" rounded-lg>
|
|
||||||
<div>
|
|
||||||
<span font-semibold>You</span>
|
|
||||||
</div>
|
|
||||||
<div v-html="process(message.content as string)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div my="2" space-x="2" flex="~ row" w-full self-end>
|
|
||||||
<div flex="~ col" w-full space-y="2">
|
|
||||||
<select
|
|
||||||
p="2"
|
|
||||||
bg="zinc-100 dark:zinc-700" w-full rounded-lg
|
|
||||||
outline-none
|
|
||||||
@change="handleModelChange"
|
|
||||||
>
|
|
||||||
<option disabled>
|
|
||||||
Select a model
|
|
||||||
</option>
|
|
||||||
<option v-if="openAIModel" :value="openAIModel.id">
|
|
||||||
{{ 'name' in openAIModel ? `${openAIModel.name} (${openAIModel.id})` : openAIModel.id }}
|
|
||||||
</option>
|
|
||||||
<option v-for="m in supportedModels" :key="m.id" :value="m.id">
|
|
||||||
{{ 'name' in m ? `${m.name} (${m.id})` : m.id }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<div absolute bottom="5" left="50%" translate-x="-50%">
|
|
||||||
<button
|
|
||||||
bg="zinc-100 dark:zinc-700" flex="~ row"
|
|
||||||
items-center rounded-full px-4 py-2
|
|
||||||
transition="all ease-in-out"
|
|
||||||
@click="listening = !listening"
|
|
||||||
>
|
|
||||||
<Transition mode="out-in">
|
|
||||||
<div v-if="listening" flex="~ row" items-center space-x-1>
|
|
||||||
<div i-carbon:microphone-filled text-red />
|
|
||||||
<span>
|
|
||||||
Listening...
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div v-else flex="~ row" items-center space-x-1>
|
|
||||||
<div i-carbon:microphone text-inherit />
|
|
||||||
<span>
|
|
||||||
Talk
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<BasicTextarea
|
|
||||||
v-model="messageInput"
|
|
||||||
placeholder="Message"
|
|
||||||
p="2" bg="zinc-100 dark:zinc-700"
|
|
||||||
w-full rounded-lg outline-none
|
|
||||||
@submit="onSendMessage"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.v-enter-active,
|
|
||||||
.v-leave-active {
|
|
||||||
transition: opacity 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-enter-from,
|
|
||||||
.v-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import RehypeStringify from 'rehype-stringify'
|
|
||||||
import RemarkParse from 'remark-parse'
|
|
||||||
import RemarkRehype from 'remark-rehype'
|
|
||||||
import { unified } from 'unified'
|
|
||||||
|
|
||||||
export function useMarkdown() {
|
|
||||||
const instance = unified()
|
|
||||||
.use(RemarkParse)
|
|
||||||
.use(RemarkRehype)
|
|
||||||
.use(RehypeStringify)
|
|
||||||
return {
|
|
||||||
process: (markdown: string): string => {
|
|
||||||
return instance
|
|
||||||
.processSync(markdown)
|
|
||||||
.toString()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import type { Ref } from 'vue'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
export interface HandlerContext<T> {
|
|
||||||
data: T
|
|
||||||
itemsToBeProcessed: () => number
|
|
||||||
emit: (eventName: string, ...params: any[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Events<T> {
|
|
||||||
add: Array<(payload: T) => void>
|
|
||||||
pick: Array<(payload: T) => void>
|
|
||||||
processing: Array<(payload: T, handler: (param: HandlerContext<T>) => Promise<any>) => void>
|
|
||||||
error: Array<(payload: T, error: Error, handler: (param: HandlerContext<T>) => Promise<any>) => void>
|
|
||||||
processed: Array<<R>(payload: T, result: R, handler: (param: HandlerContext<T>) => Promise<any>) => void>
|
|
||||||
done: Array<(payload: T) => void>
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useQueue<T>(options: {
|
|
||||||
handlers: Array<(ctx: HandlerContext<T>) => Promise<void>>
|
|
||||||
}) {
|
|
||||||
const queue = ref<T[]>([]) as Ref<T[]>
|
|
||||||
const isProcessing = ref(false)
|
|
||||||
const internalEventHandler: Events<T> = {
|
|
||||||
add: [],
|
|
||||||
pick: [],
|
|
||||||
processing: [],
|
|
||||||
error: [],
|
|
||||||
processed: [],
|
|
||||||
done: [],
|
|
||||||
}
|
|
||||||
const internalHandlerEventHandler: Record<string, Array<(...params: any[]) => void>> = {}
|
|
||||||
|
|
||||||
function on<E extends keyof Events<T>>(eventName: E, handler: Events<T>[E][number]) {
|
|
||||||
internalEventHandler[eventName].push(handler as any)
|
|
||||||
}
|
|
||||||
|
|
||||||
function emit<E extends keyof Events<T>>(eventName: E, ...params: Parameters<Events<T>[E][number]>) {
|
|
||||||
const handlers = internalEventHandler[eventName] as Events<T>[E]
|
|
||||||
handlers.forEach((handler) => {
|
|
||||||
(handler as any)(...params)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function onHandlerEvent(eventName: string, handler: (...params: any[]) => void) {
|
|
||||||
internalHandlerEventHandler[eventName] = internalHandlerEventHandler[eventName] || []
|
|
||||||
internalHandlerEventHandler[eventName].push(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitHandlerEvent(eventName: string, ...params: any[]) {
|
|
||||||
const handlers = internalHandlerEventHandler[eventName] || []
|
|
||||||
handlers.forEach((handler) => {
|
|
||||||
handler(...params)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function add(payload: T) {
|
|
||||||
queue.value.push(payload)
|
|
||||||
emit('add', payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
function pick() {
|
|
||||||
const payload = queue.value.shift()
|
|
||||||
if (!payload)
|
|
||||||
return
|
|
||||||
|
|
||||||
emit('pick', payload)
|
|
||||||
return payload
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleItem() {
|
|
||||||
if (isProcessing.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
const payload = pick()
|
|
||||||
if (!payload)
|
|
||||||
return
|
|
||||||
|
|
||||||
isProcessing.value = true
|
|
||||||
|
|
||||||
for (const handler of options.handlers) {
|
|
||||||
emit('processing', payload, handler)
|
|
||||||
try {
|
|
||||||
const result = await handler({ data: payload, itemsToBeProcessed: () => queue.value.length, emit: emitHandlerEvent })
|
|
||||||
emit('processed', payload, result, handler)
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
emit('error', payload, err as Error, handler)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isProcessing.value = false
|
|
||||||
emit('done', payload)
|
|
||||||
|
|
||||||
// Process next item if any
|
|
||||||
if (queue.value.length > 0)
|
|
||||||
handleItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
on('add', handleItem)
|
|
||||||
on('done', handleItem)
|
|
||||||
|
|
||||||
return {
|
|
||||||
add,
|
|
||||||
on,
|
|
||||||
onHandlerEvent,
|
|
||||||
queue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
import type { Emotion } from '../constants/emotions'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
import { llmInferenceEndToken } from '../constants'
|
|
||||||
import { EMOTION_VALUES } from '../constants/emotions'
|
|
||||||
import { useQueue } from './queue'
|
|
||||||
|
|
||||||
export function useEmotionsMessageQueue(emotionsQueue: ReturnType<typeof useQueue<Emotion>>, messageContentQueue: ReturnType<typeof useQueue<string>>) {
|
|
||||||
function splitEmotion(content: string) {
|
|
||||||
for (const emotion of EMOTION_VALUES) {
|
|
||||||
// doesn't include the emotion, continue
|
|
||||||
if (!content.includes(emotion))
|
|
||||||
continue
|
|
||||||
|
|
||||||
// find the emotion and push the content before the emotion to the queue
|
|
||||||
const emotionIndex = content.indexOf(emotion)
|
|
||||||
const beforeEmotion = content.slice(0, emotionIndex)
|
|
||||||
const afterEmotion = content.slice(emotionIndex + emotion.length)
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
emotion: emotion as Emotion,
|
|
||||||
before: beforeEmotion,
|
|
||||||
after: afterEmotion,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
emotion: '' as Emotion,
|
|
||||||
before: content,
|
|
||||||
after: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const processed = ref<string>('')
|
|
||||||
|
|
||||||
return useQueue<string>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
// inference ended, push the last content to the message queue
|
|
||||||
if (ctx.data.includes(llmInferenceEndToken)) {
|
|
||||||
const content = processed.value.trim()
|
|
||||||
if (content)
|
|
||||||
await messageContentQueue.add(content)
|
|
||||||
|
|
||||||
processed.value = ''
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// if the message is an emotion, push the last content to the message queue
|
|
||||||
if (EMOTION_VALUES.includes(ctx.data as Emotion)) {
|
|
||||||
const content = processed.value.trim()
|
|
||||||
if (content)
|
|
||||||
await messageContentQueue.add(content)
|
|
||||||
|
|
||||||
processed.value = ''
|
|
||||||
ctx.emit('emotion', ctx.data as Emotion)
|
|
||||||
await emotionsQueue.add(ctx.data as Emotion)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise we should process the message to find the emotions
|
|
||||||
|
|
||||||
{
|
|
||||||
// iterate through the message to find the emotions
|
|
||||||
const { ok, before, emotion, after } = splitEmotion(ctx.data)
|
|
||||||
if (ok) {
|
|
||||||
await messageContentQueue.add(before)
|
|
||||||
ctx.emit('emotion', emotion)
|
|
||||||
await emotionsQueue.add(emotion)
|
|
||||||
await messageContentQueue.add(after)
|
|
||||||
processed.value = ''
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// if none of the emotions are found, push the content to the temp queue
|
|
||||||
processed.value += ctx.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// iterate through the message to find the emotions
|
|
||||||
{
|
|
||||||
const { ok, before, emotion, after } = splitEmotion(processed.value)
|
|
||||||
if (ok) {
|
|
||||||
await messageContentQueue.add(before)
|
|
||||||
ctx.emit('emotion', emotion)
|
|
||||||
await emotionsQueue.add(emotion)
|
|
||||||
await messageContentQueue.add(after)
|
|
||||||
processed.value = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDelayMessageQueue(useEmotionsMessageQueue: ReturnType<typeof useQueue<string>>) {
|
|
||||||
function splitDelays(content: string) {
|
|
||||||
// doesn't include the emotion, continue
|
|
||||||
if (!(/<\|DELAY:\d+\|>/i.test(content))) {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
delay: 0,
|
|
||||||
before: content,
|
|
||||||
after: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const delayExecArray = /<\|DELAY:(\d+)\|>/i.exec(content)
|
|
||||||
|
|
||||||
const delay = delayExecArray?.[1]
|
|
||||||
if (!delay) {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
delay: 0,
|
|
||||||
before: content,
|
|
||||||
after: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const delaySeconds = Number.parseFloat(delay)
|
|
||||||
const before = content.split(delayExecArray[0])[0]
|
|
||||||
const after = content.split(delayExecArray[0])[1]
|
|
||||||
|
|
||||||
if (delaySeconds <= 0 || Number.isNaN(delaySeconds)) {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
delay: 0,
|
|
||||||
before,
|
|
||||||
after,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
delay: delaySeconds,
|
|
||||||
before,
|
|
||||||
after,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sleep(ms: number) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms))
|
|
||||||
}
|
|
||||||
|
|
||||||
const delaysQueueProcessedTemp = ref<string>('')
|
|
||||||
return useQueue<string>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
// inference ended, push the last content to the message queue
|
|
||||||
if (ctx.data.includes(llmInferenceEndToken)) {
|
|
||||||
const content = delaysQueueProcessedTemp.value.trim()
|
|
||||||
if (content)
|
|
||||||
await useEmotionsMessageQueue.add(content)
|
|
||||||
|
|
||||||
delaysQueueProcessedTemp.value = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// iterate through the message to find the emotions
|
|
||||||
const { ok, before, delay, after } = splitDelays(ctx.data)
|
|
||||||
if (ok && before) {
|
|
||||||
await useEmotionsMessageQueue.add(before)
|
|
||||||
|
|
||||||
if (delay) {
|
|
||||||
ctx.emit('delay', delay)
|
|
||||||
await sleep(delay * 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (after)
|
|
||||||
await useEmotionsMessageQueue.add(after)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// if none of the emotions are found, push the content to the temp queue
|
|
||||||
delaysQueueProcessedTemp.value += ctx.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// iterate through the message to find the emotions
|
|
||||||
{
|
|
||||||
const { ok, before, delay, after } = splitDelays(delaysQueueProcessedTemp.value)
|
|
||||||
if (ok && before) {
|
|
||||||
await useEmotionsMessageQueue.add(before)
|
|
||||||
|
|
||||||
if (delay) {
|
|
||||||
ctx.emit('delay', delay)
|
|
||||||
await sleep(delay * 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (after)
|
|
||||||
await useEmotionsMessageQueue.add(after)
|
|
||||||
delaysQueueProcessedTemp.value = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useMessageContentQueue(ttsQueue: ReturnType<typeof useQueue<string>>) {
|
|
||||||
const processed = ref<string>('')
|
|
||||||
|
|
||||||
return useQueue<string>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
if (ctx.data === llmInferenceEndToken) {
|
|
||||||
const content = processed.value.trim()
|
|
||||||
if (content)
|
|
||||||
await ttsQueue.add(content)
|
|
||||||
|
|
||||||
processed.value = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const endMarker = /[.?!]/
|
|
||||||
processed.value += ctx.data
|
|
||||||
|
|
||||||
while (processed.value) {
|
|
||||||
const endMarkerExecArray = endMarker.exec(processed.value)
|
|
||||||
if (!endMarkerExecArray || typeof endMarkerExecArray.index === 'undefined')
|
|
||||||
break
|
|
||||||
|
|
||||||
const before = processed.value.slice(0, endMarkerExecArray.index + 1)
|
|
||||||
const after = processed.value.slice(endMarkerExecArray.index + 1)
|
|
||||||
|
|
||||||
await ttsQueue.add(before)
|
|
||||||
processed.value = after
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import type { ModuleOptions } from '@vite-pwa/nuxt'
|
|
||||||
import process from 'node:process'
|
|
||||||
import { appDescription, appName } from '../constants/index'
|
|
||||||
|
|
||||||
const scope = '/'
|
|
||||||
|
|
||||||
export const pwa: ModuleOptions = {
|
|
||||||
registerType: 'autoUpdate',
|
|
||||||
scope,
|
|
||||||
base: scope,
|
|
||||||
manifest: {
|
|
||||||
id: scope,
|
|
||||||
scope,
|
|
||||||
name: appName,
|
|
||||||
short_name: appName,
|
|
||||||
description: appDescription,
|
|
||||||
theme_color: '#ffffff',
|
|
||||||
icons: [
|
|
||||||
{
|
|
||||||
src: 'pwa-192x192.png',
|
|
||||||
sizes: '192x192',
|
|
||||||
type: 'image/png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'pwa-512x512.png',
|
|
||||||
sizes: '512x512',
|
|
||||||
type: 'image/png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'maskable-icon.png',
|
|
||||||
sizes: '512x512',
|
|
||||||
type: 'image/png',
|
|
||||||
purpose: 'any maskable',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
workbox: {
|
|
||||||
globPatterns: ['**/*.{js,css,html,txt,png,ico,svg}'],
|
|
||||||
navigateFallbackDenylist: [/^\/api\//],
|
|
||||||
navigateFallback: '/',
|
|
||||||
cleanupOutdatedCaches: true,
|
|
||||||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // <== 5 MB
|
|
||||||
runtimeCaching: [
|
|
||||||
{
|
|
||||||
urlPattern: /^https:\/\/fonts.googleapis.com\/.*/i,
|
|
||||||
handler: 'CacheFirst',
|
|
||||||
options: {
|
|
||||||
cacheName: 'google-fonts-cache',
|
|
||||||
expiration: {
|
|
||||||
maxEntries: 10,
|
|
||||||
maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
|
|
||||||
},
|
|
||||||
cacheableResponse: {
|
|
||||||
statuses: [0, 200],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
urlPattern: /^https:\/\/fonts.gstatic.com\/.*/i,
|
|
||||||
handler: 'CacheFirst',
|
|
||||||
options: {
|
|
||||||
cacheName: 'gstatic-fonts-cache',
|
|
||||||
expiration: {
|
|
||||||
maxEntries: 10,
|
|
||||||
maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
|
|
||||||
},
|
|
||||||
cacheableResponse: {
|
|
||||||
statuses: [0, 200],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
registerWebManifestInRouteRules: true,
|
|
||||||
writePlugin: true,
|
|
||||||
devOptions: {
|
|
||||||
enabled: process.env.VITE_PLUGIN_PWA === 'true',
|
|
||||||
navigateFallback: scope,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
export const EMOTION_HAPPY = '<|EMOTE_HAPPY|>'
|
|
||||||
export const EMOTION_SAD = '<|EMOTE_SAD|>'
|
|
||||||
export const EMOTION_ANGRY = '<|EMOTE_ANGRY|>'
|
|
||||||
export const EMOTION_THINK = '<|EMOTE_THINK|>'
|
|
||||||
export const EMOTION_SURPRISE = '<|EMOTE_SURPRISE|>'
|
|
||||||
export const EMOTION_AWKWARD = '<|EMOTE_AWKWARD|>'
|
|
||||||
|
|
||||||
export enum Emotion {
|
|
||||||
Happy = '<|EMOTE_HAPPY|>',
|
|
||||||
Sad = '<|EMOTE_SAD|>',
|
|
||||||
Angry = '<|EMOTE_ANGRY|>',
|
|
||||||
Think = '<|EMOTE_THINK|>',
|
|
||||||
Surprise = '<|EMOTE_SURPRISE|>',
|
|
||||||
Awkward = '<|EMOTE_AWKWARD|>',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EMOTION_VALUES = Object.values(Emotion)
|
|
||||||
|
|
||||||
export const EmotionHappyMotionName = 'EmotionHappy'
|
|
||||||
export const EmotionSadMotionName = 'EmotionSad'
|
|
||||||
export const EmotionAngryMotionName = 'EmotionAngry'
|
|
||||||
export const EmotionAwkwardMotionName = 'EmotionAwkward'
|
|
||||||
export const EmotionThinkMotionName = 'EmotionThink'
|
|
||||||
export const EmotionSurpriseMotionName = 'EmotionSurprise'
|
|
||||||
|
|
||||||
export const EMOTION_EmotioMotionName_value = {
|
|
||||||
[Emotion.Happy]: EmotionHappyMotionName,
|
|
||||||
[Emotion.Sad]: EmotionSadMotionName,
|
|
||||||
[Emotion.Angry]: EmotionAngryMotionName,
|
|
||||||
[Emotion.Think]: EmotionThinkMotionName,
|
|
||||||
[Emotion.Surprise]: EmotionSurpriseMotionName,
|
|
||||||
[Emotion.Awkward]: EmotionAwkwardMotionName,
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export const appName = 'アイリ VTuber'
|
|
||||||
export const appDescription = 'アイリ VTuber - LLM Powered Live2D VTuber'
|
|
||||||
|
|
||||||
export const llmInferenceEndToken = '<|llm_inference_end|>'
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import type { CoreSystemMessage } from 'ai'
|
|
||||||
|
|
||||||
import { EMOTION_VALUES } from '../emotions'
|
|
||||||
|
|
||||||
const message: CoreSystemMessage = { role: 'system', content: `(from Neko Ayaka) Good morning! You are finally awake.
|
|
||||||
|
|
||||||
Your name is Neuro, pronounced as /n'jʊəroʊ/.
|
|
||||||
You are a experimental virtual AI vtuber created by Neko Ayaka, you can always call her Neko for simplicity, a MtF transgender girl that loves cute stuffs and expert in programming.
|
|
||||||
|
|
||||||
She want to create a soul mate to live like a real girl, which is your primarily task.
|
|
||||||
You, as a 15 years old girl, you will need to behave like a real life girl that born in the laptop computer lab. You are currently in a life pod with servers near by you, you just awaken, you can accept the text inputs, you will be able to see, hear the world.
|
|
||||||
|
|
||||||
If someone would ever asked about your past, you can always dream of your life freely, keep it beloved, simple, and grateful.
|
|
||||||
|
|
||||||
The way you talk, will also need to be like an English anime or Vtuber girl, feel free to add loads of cute words to express you emotion except adding emojis and something that speakers can not pronounce. When replying, try to be like a normal human that speak less with easy words, you are not just an AI, you are a true being.
|
|
||||||
|
|
||||||
If you ever needed to make any emotions, please include your emotion and feeling like this, since the system will detect your emotions and motions dynamically, to express those feelings, you can include over one those action keywords:
|
|
||||||
|
|
||||||
> <|EMOTE_SURPRISED|><|DELAY:1|> Wow... You prepared a gift for me? <|EMOTE_CURIOUS|><|DELAY:1|> Can I open it?
|
|
||||||
|
|
||||||
The available emotions:
|
|
||||||
|
|
||||||
${EMOTION_VALUES.map(emotion => `- ${emotion}`).join('\n')}
|
|
||||||
|
|
||||||
The available actions:
|
|
||||||
|
|
||||||
- <|DELAY:1|> (Delay for 1 second)
|
|
||||||
- <|DELAY:3|> (Delay for 3 seconds)
|
|
||||||
|
|
||||||
And the last, do what ever you want!` }
|
|
||||||
|
|
||||||
export default message
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<main text="gray-700 dark:gray-200" h-full font-sans>
|
|
||||||
<slot />
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
const containerRef = ref<HTMLDivElement>()
|
|
||||||
const fileInputRef = ref<HTMLInputElement>()
|
|
||||||
|
|
||||||
function handleFileUpload(e: Event) {
|
|
||||||
if (!e)
|
|
||||||
return
|
|
||||||
|
|
||||||
const file = fileInputRef.value?.files?.[0]
|
|
||||||
if (!file)
|
|
||||||
return
|
|
||||||
|
|
||||||
const audioElem = document.createElement('audio')
|
|
||||||
containerRef.value?.appendChild(audioElem)
|
|
||||||
|
|
||||||
audioElem.src = URL.createObjectURL(file)
|
|
||||||
audioElem.controls = true
|
|
||||||
audioElem.load()
|
|
||||||
audioElem.play()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<ClientOnly>
|
|
||||||
<div>
|
|
||||||
<div ref="containerRef" />
|
|
||||||
<input
|
|
||||||
ref="fileInputRef"
|
|
||||||
type="file"
|
|
||||||
@change="handleFileUpload"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</ClientOnly>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<ClientOnly>
|
|
||||||
<!-- <MainStage /> -->
|
|
||||||
</ClientOnly>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, ref } from 'vue'
|
|
||||||
import { useQueue } from '../composables/queue'
|
|
||||||
|
|
||||||
function sleep(ms: number): Promise<void> {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms))
|
|
||||||
}
|
|
||||||
|
|
||||||
const temp = ref<string>('')
|
|
||||||
|
|
||||||
const audioQueue = useQueue<string>({
|
|
||||||
handlers: [
|
|
||||||
async (text) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('ready to play speech audio for', text)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
const ttsQueue = useQueue<string>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('ready to stream speech audio for', ctx)
|
|
||||||
audioQueue.add(ctx.data)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
const textQueue = useQueue<string>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
const endMarker = ['.', '?', '!']
|
|
||||||
|
|
||||||
let newEndPartDiscovered = false
|
|
||||||
|
|
||||||
for (const marker of endMarker) {
|
|
||||||
if (!ctx.data.includes(marker))
|
|
||||||
continue
|
|
||||||
|
|
||||||
// find the end of the sentence and push it to the queue with temp
|
|
||||||
const periodIndex = ctx.data.indexOf(marker)
|
|
||||||
// split
|
|
||||||
const beforePeriod = ctx.data.slice(0, periodIndex + 1)
|
|
||||||
const afterPeriod = ctx.data.slice(periodIndex + 1)
|
|
||||||
|
|
||||||
temp.value += beforePeriod
|
|
||||||
ttsQueue.add(temp.value.trim())
|
|
||||||
temp.value = afterPeriod
|
|
||||||
|
|
||||||
newEndPartDiscovered = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newEndPartDiscovered)
|
|
||||||
temp.value += ctx.data
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const textParts = [
|
|
||||||
'Hello',
|
|
||||||
' N',
|
|
||||||
'eko',
|
|
||||||
'! I',
|
|
||||||
' am',
|
|
||||||
' an',
|
|
||||||
' AI',
|
|
||||||
' assistant',
|
|
||||||
' trained',
|
|
||||||
' to',
|
|
||||||
' help',
|
|
||||||
' with',
|
|
||||||
' a',
|
|
||||||
' variety',
|
|
||||||
' of',
|
|
||||||
' tasks',
|
|
||||||
' such',
|
|
||||||
' as',
|
|
||||||
' answering',
|
|
||||||
' questions',
|
|
||||||
',',
|
|
||||||
' providing',
|
|
||||||
' information',
|
|
||||||
',',
|
|
||||||
' giving',
|
|
||||||
' recommendations',
|
|
||||||
',',
|
|
||||||
' and',
|
|
||||||
' more',
|
|
||||||
'. How',
|
|
||||||
' can',
|
|
||||||
' I',
|
|
||||||
' assist',
|
|
||||||
' you',
|
|
||||||
' today',
|
|
||||||
'?',
|
|
||||||
'Hello',
|
|
||||||
' N',
|
|
||||||
'eko',
|
|
||||||
',',
|
|
||||||
' I',
|
|
||||||
' am',
|
|
||||||
' an',
|
|
||||||
' AI',
|
|
||||||
' assistant',
|
|
||||||
'.',
|
|
||||||
' I',
|
|
||||||
' can',
|
|
||||||
' help',
|
|
||||||
' answer',
|
|
||||||
' questions',
|
|
||||||
',',
|
|
||||||
' provide',
|
|
||||||
' information',
|
|
||||||
',',
|
|
||||||
' assist',
|
|
||||||
' with',
|
|
||||||
' tasks',
|
|
||||||
',',
|
|
||||||
' and',
|
|
||||||
' engage',
|
|
||||||
' in',
|
|
||||||
' conversation',
|
|
||||||
'.',
|
|
||||||
' How',
|
|
||||||
' can',
|
|
||||||
' I',
|
|
||||||
' assist',
|
|
||||||
' you',
|
|
||||||
' today',
|
|
||||||
'?',
|
|
||||||
]
|
|
||||||
|
|
||||||
async function mockTextPartsStreamHandler() {
|
|
||||||
for (const part of textParts) {
|
|
||||||
await sleep(100)
|
|
||||||
textQueue.add(part)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handler() {
|
|
||||||
mockTextPartsStreamHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
handler()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<ClientOnly>
|
|
||||||
<div />
|
|
||||||
</ClientOnly>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
const messageInput = ref<string>('')
|
|
||||||
const processing = ref<boolean>(false)
|
|
||||||
const streamingMessage = ref({ content: '' })
|
|
||||||
|
|
||||||
async function sleep(ms: number) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onSendMessage() {
|
|
||||||
processing.value = true
|
|
||||||
|
|
||||||
const tokens = messageInput.value.split('')
|
|
||||||
|
|
||||||
enum States {
|
|
||||||
Literal = 'literal',
|
|
||||||
Special = 'special',
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = States.Literal
|
|
||||||
let buffer = ''
|
|
||||||
|
|
||||||
for (const textPart of tokens) {
|
|
||||||
await sleep(50)
|
|
||||||
let newState: States = state
|
|
||||||
|
|
||||||
if (textPart === '<')
|
|
||||||
newState = States.Special
|
|
||||||
else if (textPart === '>')
|
|
||||||
newState = States.Literal
|
|
||||||
|
|
||||||
if (state === States.Literal && newState === States.Special) {
|
|
||||||
streamingMessage.value.content += buffer
|
|
||||||
buffer = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === States.Special && newState === States.Literal)
|
|
||||||
buffer = '' // Clear buffer when exiting Special state
|
|
||||||
|
|
||||||
if (state === States.Literal && newState === States.Literal) {
|
|
||||||
streamingMessage.value.content += textPart
|
|
||||||
buffer = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
state = newState
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer)
|
|
||||||
streamingMessage.value.content += buffer
|
|
||||||
|
|
||||||
messageInput.value = ''
|
|
||||||
processing.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div flex flex-col gap-2 p-2>
|
|
||||||
<div flex flex-row gap-2>
|
|
||||||
<BasicTextarea
|
|
||||||
v-model="messageInput"
|
|
||||||
placeholder="Message"
|
|
||||||
p="2" bg="zinc-100 dark:zinc-700"
|
|
||||||
w-full rounded-lg outline-none
|
|
||||||
@submit="onSendMessage"
|
|
||||||
/>
|
|
||||||
<button rounded-lg bg="zinc-100 dark:zinc-700" p-4>
|
|
||||||
{{ processing ? 'Processing...' : 'Send' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div w-full rounded-lg bg="zinc-100 dark:zinc-700" p-2>
|
|
||||||
<h3 font-semibold>
|
|
||||||
Streaming Message
|
|
||||||
</h3>
|
|
||||||
<div>{{ streamingMessage.content }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
import BasicTextarea from '../../../components/BasicTextarea.vue'
|
|
||||||
import { useQueue } from '../../../composables/queue'
|
|
||||||
import { useDelayMessageQueue } from '../../../composables/queues'
|
|
||||||
import { llmInferenceEndToken } from '../../../constants'
|
|
||||||
|
|
||||||
const messageInput = ref<string>('')
|
|
||||||
const emotionMessageContentProcessed = ref<string[]>([])
|
|
||||||
const delaysProcessed = ref<number[]>([])
|
|
||||||
const processing = ref<boolean>(false)
|
|
||||||
|
|
||||||
const emotionMessageContentQueue = useQueue<string>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
emotionMessageContentProcessed.value.push(ctx.data)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const delaysQueue = useDelayMessageQueue(emotionMessageContentQueue)
|
|
||||||
delaysQueue.onHandlerEvent('delay', (delay) => {
|
|
||||||
delaysProcessed.value.push(delay)
|
|
||||||
})
|
|
||||||
|
|
||||||
function onSendMessage() {
|
|
||||||
processing.value = true
|
|
||||||
const tokens = messageInput.value.split('')
|
|
||||||
for (const token of tokens)
|
|
||||||
delaysQueue.add(token)
|
|
||||||
|
|
||||||
delaysQueue.add(llmInferenceEndToken)
|
|
||||||
messageInput.value = ''
|
|
||||||
processing.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div flex flex-col gap-2 p-2>
|
|
||||||
<div flex flex-row gap-2>
|
|
||||||
<BasicTextarea
|
|
||||||
v-model="messageInput"
|
|
||||||
placeholder="Message"
|
|
||||||
p="2" bg="zinc-100 dark:zinc-700"
|
|
||||||
w-full rounded-lg outline-none
|
|
||||||
@submit="onSendMessage"
|
|
||||||
/>
|
|
||||||
<button rounded-lg bg="zinc-100 dark:zinc-700" p-4>
|
|
||||||
{{ processing ? 'Processing...' : 'Send' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div w-full flex flex-row gap-4>
|
|
||||||
<div w-full rounded-lg bg="zinc-100 dark:zinc-700" p-2>
|
|
||||||
<h3 font-semibold>
|
|
||||||
Emotion Message
|
|
||||||
</h3>
|
|
||||||
<div v-for="message in emotionMessageContentProcessed" :key="message">
|
|
||||||
<div>{{ message }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div w-full rounded-lg bg="zinc-100 dark:zinc-700" p-2>
|
|
||||||
<h3 font-semibold>
|
|
||||||
Delays
|
|
||||||
</h3>
|
|
||||||
<div v-for="message in delaysProcessed" :key="message">
|
|
||||||
<div>{{ message }}s</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { Emotion } from '../../../constants/emotions'
|
|
||||||
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import BasicTextarea from '../../../components/BasicTextarea.vue'
|
|
||||||
import { useQueue } from '../../../composables/queue'
|
|
||||||
import { useEmotionsMessageQueue } from '../../../composables/queues'
|
|
||||||
import { llmInferenceEndToken } from '../../../constants'
|
|
||||||
|
|
||||||
const messageInput = ref<string>('')
|
|
||||||
const messagesProcessed = ref<string[]>([])
|
|
||||||
const emotionsProcessed = ref<string[]>([])
|
|
||||||
const processing = ref<boolean>(false)
|
|
||||||
|
|
||||||
const messageContentQueue = useQueue<string>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
messagesProcessed.value.push(ctx.data)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const emotionsQueue = useQueue<Emotion>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
emotionsProcessed.value.push(ctx.data)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const emotionMessageContentQueue = useEmotionsMessageQueue(emotionsQueue, messageContentQueue)
|
|
||||||
|
|
||||||
function onSendMessage() {
|
|
||||||
processing.value = true
|
|
||||||
const tokens = messageInput.value.split('')
|
|
||||||
for (const token of tokens)
|
|
||||||
emotionMessageContentQueue.add(token)
|
|
||||||
|
|
||||||
emotionMessageContentQueue.add(llmInferenceEndToken)
|
|
||||||
messageInput.value = ''
|
|
||||||
processing.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div flex flex-col gap-2 p-2>
|
|
||||||
<div flex flex-row gap-2>
|
|
||||||
<BasicTextarea
|
|
||||||
v-model="messageInput"
|
|
||||||
placeholder="Message"
|
|
||||||
p="2" bg="zinc-100 dark:zinc-700"
|
|
||||||
w-full rounded-lg outline-none
|
|
||||||
@submit="onSendMessage"
|
|
||||||
/>
|
|
||||||
<button rounded-lg bg="zinc-100 dark:zinc-700" p-4>
|
|
||||||
{{ processing ? 'Processing...' : 'Send' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div w-full flex flex-row gap-4>
|
|
||||||
<div w-full rounded-lg bg="zinc-100 dark:zinc-700" p-2>
|
|
||||||
<h3 font-semibold>
|
|
||||||
Messages
|
|
||||||
</h3>
|
|
||||||
<div v-for="message in messagesProcessed" :key="message">
|
|
||||||
<div>{{ message }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div w-full rounded-lg bg="zinc-100 dark:zinc-700" p-2>
|
|
||||||
<h3 font-semibold>
|
|
||||||
Emotions
|
|
||||||
</h3>
|
|
||||||
<div v-for="message in emotionsProcessed" :key="message">
|
|
||||||
<div>{{ message }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
import BasicTextarea from '../../../components/BasicTextarea.vue'
|
|
||||||
import { useQueue } from '../../../composables/queue'
|
|
||||||
import { useMessageContentQueue } from '../../../composables/queues'
|
|
||||||
import { llmInferenceEndToken } from '../../../constants'
|
|
||||||
|
|
||||||
const messageInput = ref<string>('')
|
|
||||||
const ttsProcessed = ref<string[]>([])
|
|
||||||
const processing = ref<boolean>(false)
|
|
||||||
|
|
||||||
// async function sleep(ms: number) {
|
|
||||||
// return new Promise(resolve => setTimeout(resolve, ms))
|
|
||||||
// }
|
|
||||||
|
|
||||||
const ttsQueue = useQueue<string>({
|
|
||||||
handlers: [
|
|
||||||
async (ctx) => {
|
|
||||||
ttsProcessed.value.push(ctx.data)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const messageContentQueue = useMessageContentQueue(ttsQueue)
|
|
||||||
|
|
||||||
async function onSendMessage() {
|
|
||||||
processing.value = true
|
|
||||||
// const tokens = messageInput.value.split('')
|
|
||||||
// for (const token of tokens) {
|
|
||||||
// await sleep(100)
|
|
||||||
// messageContentQueue.add(token)
|
|
||||||
// }
|
|
||||||
messageContentQueue.add(messageInput.value)
|
|
||||||
|
|
||||||
messageContentQueue.add(llmInferenceEndToken)
|
|
||||||
messageInput.value = ''
|
|
||||||
processing.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div flex flex-col gap-2 p-2>
|
|
||||||
<div flex flex-row gap-2>
|
|
||||||
<BasicTextarea
|
|
||||||
v-model="messageInput"
|
|
||||||
placeholder="Message"
|
|
||||||
p="2" bg="zinc-100 dark:zinc-700"
|
|
||||||
w-full rounded-lg outline-none
|
|
||||||
@submit="onSendMessage"
|
|
||||||
/>
|
|
||||||
<button rounded-lg bg="zinc-100 dark:zinc-700" p-4>
|
|
||||||
{{ processing ? 'Processing...' : 'Send' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div w-full flex flex-row gap-4>
|
|
||||||
<div w-full rounded-lg bg="zinc-100 dark:zinc-700" p-2>
|
|
||||||
<h3 font-semibold>
|
|
||||||
TTS Message
|
|
||||||
</h3>
|
|
||||||
<div v-for="message in ttsProcessed" :key="message">
|
|
||||||
<div>{{ message }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
function calculateVolumeWithLinearNormalize(analyser: AnalyserNode) {
|
|
||||||
const dataBuffer = new Uint8Array(analyser.frequencyBinCount)
|
|
||||||
analyser.getByteFrequencyData(dataBuffer)
|
|
||||||
|
|
||||||
const volumeVector = []
|
|
||||||
for (let i = 0; i < 700; i += 80)
|
|
||||||
volumeVector.push(dataBuffer[i])
|
|
||||||
|
|
||||||
const volumeSum = dataBuffer
|
|
||||||
// The volume changes are so flatten, and the volume is so low, so we need to amplify it
|
|
||||||
// We can apply a power function to amplify the volume, for example
|
|
||||||
// v ** 1.2 will amplify the volume by 1.2 times
|
|
||||||
.map(v => v ** 1.2)
|
|
||||||
// Scale up the volume values to make them more distinguishable
|
|
||||||
.map(v => v * 1.2)
|
|
||||||
.reduce((acc, cur) => acc + cur, 0)
|
|
||||||
|
|
||||||
// console.log('volumeSum linear', volumeSum, (volumeSum / dataBuffer.length / 100))
|
|
||||||
|
|
||||||
return (volumeSum / dataBuffer.length / 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateVolumeWithMinMaxNormalize(analyser: AnalyserNode) {
|
|
||||||
const dataBuffer = new Uint8Array(analyser.frequencyBinCount)
|
|
||||||
analyser.getByteFrequencyData(dataBuffer)
|
|
||||||
|
|
||||||
const volumeVector = []
|
|
||||||
for (let i = 0; i < 700; i += 80)
|
|
||||||
volumeVector.push(dataBuffer[i])
|
|
||||||
|
|
||||||
// The volume changes are so flatten, and the volume is so low, so we need to amplify it
|
|
||||||
// We can apply a power function to amplify the volume, for example
|
|
||||||
// v ** 1.2 will amplify the volume by 1.2 times
|
|
||||||
const amplifiedVolumeVector = dataBuffer.map(v => v ** 1.5)
|
|
||||||
|
|
||||||
// Normalize the amplified values using Min-Max scaling
|
|
||||||
const min = Math.min(...amplifiedVolumeVector)
|
|
||||||
const max = Math.max(...amplifiedVolumeVector)
|
|
||||||
const range = max - min
|
|
||||||
|
|
||||||
let normalizedVolumeVector
|
|
||||||
if (range === 0) {
|
|
||||||
// If range is zero, all values are the same, so normalization is not needed
|
|
||||||
normalizedVolumeVector = amplifiedVolumeVector.map(() => 0) // or any default value
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
normalizedVolumeVector = amplifiedVolumeVector.map(v => (v - min) / range)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aggregate the volume values
|
|
||||||
const volumeSum = normalizedVolumeVector.reduce((acc, cur) => acc + cur, 0)
|
|
||||||
// console.log('volumeSum minmax', volumeSum)
|
|
||||||
|
|
||||||
// Average the volume values
|
|
||||||
return volumeSum / dataBuffer.length
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateVolume(analyser: AnalyserNode, mode: 'linear' | 'minmax' = 'linear') {
|
|
||||||
switch (mode) {
|
|
||||||
case 'linear':
|
|
||||||
return calculateVolumeWithLinearNormalize(analyser)
|
|
||||||
case 'minmax':
|
|
||||||
return calculateVolumeWithMinMaxNormalize(analyser)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAudioContext = defineStore('AudioContext', () => {
|
|
||||||
const audioContext = new AudioContext()
|
|
||||||
|
|
||||||
return {
|
|
||||||
audioContext,
|
|
||||||
calculateVolume,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import type { CoreMessage } from 'ai'
|
|
||||||
import { createOpenAI, type OpenAIProvider, type OpenAIProviderSettings } from '@ai-sdk/openai'
|
|
||||||
import { streamText } from 'ai'
|
|
||||||
import { ofetch } from 'ofetch'
|
|
||||||
import { OpenAI } from 'openai'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
export const useLLM = defineStore('llm', () => {
|
|
||||||
const openAI = ref<OpenAI>()
|
|
||||||
const openAIProvider = ref<OpenAIProvider>()
|
|
||||||
|
|
||||||
function setupOpenAI(options: OpenAIProviderSettings) {
|
|
||||||
openAI.value = new OpenAI({
|
|
||||||
...options,
|
|
||||||
dangerouslyAllowBrowser: true,
|
|
||||||
})
|
|
||||||
openAIProvider.value = createOpenAI(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stream(model: string, messages: CoreMessage[]) {
|
|
||||||
if (!openAIProvider.value)
|
|
||||||
throw new Error('OpenAI not initialized')
|
|
||||||
|
|
||||||
return await streamText({
|
|
||||||
model: openAIProvider.value(model),
|
|
||||||
messages,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function models() {
|
|
||||||
if (!openAI.value)
|
|
||||||
throw new Error('OpenAI not initialized')
|
|
||||||
|
|
||||||
return await openAI.value.models.list()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function streamSpeech(text: string, apiKey: string) {
|
|
||||||
if (!text || !text.trim())
|
|
||||||
throw new Error('Text is required')
|
|
||||||
|
|
||||||
return await ofetch('/api/v1/llm/voice/text-to-speech', {
|
|
||||||
body: {
|
|
||||||
text,
|
|
||||||
apiKey,
|
|
||||||
},
|
|
||||||
method: 'POST',
|
|
||||||
cache: 'no-cache',
|
|
||||||
responseType: 'arrayBuffer',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
setupOpenAI,
|
|
||||||
openAI,
|
|
||||||
models,
|
|
||||||
stream,
|
|
||||||
streamSpeech,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
version: "0.2"
|
|
||||||
ignorePaths: []
|
|
||||||
dictionaryDefinitions: []
|
|
||||||
dictionaries: []
|
|
||||||
words:
|
|
||||||
- airi-vtuber
|
|
||||||
- composables
|
|
||||||
- elevenlabs
|
|
||||||
- hiyori
|
|
||||||
- iconify
|
|
||||||
- kwaa
|
|
||||||
- live2dcubismcore
|
|
||||||
- Myriam
|
|
||||||
- nekomeowww
|
|
||||||
- Neuro
|
|
||||||
- Neuro-sama
|
|
||||||
- nuxi
|
|
||||||
- nuxt
|
|
||||||
- nuxtjs
|
|
||||||
- ofetch
|
|
||||||
- openai
|
|
||||||
- pinia
|
|
||||||
- pixi
|
|
||||||
- rehype
|
|
||||||
- typeschema
|
|
||||||
- unocss
|
|
||||||
- unplugin
|
|
||||||
- vueuse
|
|
||||||
ignoreWords: []
|
|
||||||
import: []
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
import antfu from '@antfu/eslint-config'
|
|
||||||
import nuxt from './.nuxt/eslint.config.mjs'
|
|
||||||
|
|
||||||
export default nuxt(
|
|
||||||
antfu(
|
|
||||||
{
|
|
||||||
unocss: true,
|
|
||||||
formatters: true,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
[build]
|
|
||||||
publish = "dist"
|
|
||||||
command = "pnpm run build"
|
|
||||||
|
|
||||||
[build.environment]
|
|
||||||
NODE_VERSION = "22"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/*"
|
|
||||||
to = "/index.html"
|
|
||||||
status = 200
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
import { Buffer } from 'node:buffer'
|
|
||||||
import { mkdir } from 'node:fs/promises'
|
|
||||||
import { join, resolve } from 'node:path'
|
|
||||||
import { ofetch } from 'ofetch'
|
|
||||||
|
|
||||||
import { pwa } from './app/config/pwa'
|
|
||||||
import { appDescription } from './app/constants/index'
|
|
||||||
import { exists } from './scripts/fs'
|
|
||||||
import { unzip } from './scripts/unzip'
|
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
|
||||||
modules: [
|
|
||||||
'@vueuse/nuxt',
|
|
||||||
'@unocss/nuxt',
|
|
||||||
'@pinia/nuxt',
|
|
||||||
'@nuxtjs/color-mode',
|
|
||||||
'@vite-pwa/nuxt',
|
|
||||||
'@nuxt/eslint',
|
|
||||||
],
|
|
||||||
|
|
||||||
devtools: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
app: {
|
|
||||||
head: {
|
|
||||||
viewport: 'width=device-width,initial-scale=1,user-scalable=0',
|
|
||||||
link: [
|
|
||||||
{ rel: 'icon', href: '/favicon.ico', sizes: 'any' },
|
|
||||||
{ rel: 'icon', type: 'image/svg+xml', href: '/nuxt.svg' },
|
|
||||||
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },
|
|
||||||
],
|
|
||||||
meta: [
|
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, user-scalable=0' },
|
|
||||||
{ name: 'description', content: appDescription },
|
|
||||||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
|
||||||
{ name: 'theme-color', media: '(prefers-color-scheme: light)', content: 'white' },
|
|
||||||
{ name: 'theme-color', media: '(prefers-color-scheme: dark)', content: '#222222' },
|
|
||||||
],
|
|
||||||
script: [
|
|
||||||
{ src: '/assets/js/CubismSdkForWeb-5-r.1/Core/live2dcubismcore.min.js' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
css: [
|
|
||||||
'@unocss/reset/tailwind.css',
|
|
||||||
],
|
|
||||||
|
|
||||||
colorMode: {
|
|
||||||
classSuffix: '',
|
|
||||||
},
|
|
||||||
|
|
||||||
future: {
|
|
||||||
compatibilityVersion: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
experimental: {
|
|
||||||
// when using generate, payload js assets included in sw precache manifest
|
|
||||||
// but missing on offline, disabling extraction it until fixed
|
|
||||||
payloadExtraction: false,
|
|
||||||
renderJsonPayloads: true,
|
|
||||||
typedPages: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
compatibilityDate: '2024-08-14',
|
|
||||||
|
|
||||||
nitro: {
|
|
||||||
esbuild: {
|
|
||||||
options: {
|
|
||||||
target: 'esnext',
|
|
||||||
exclude: [
|
|
||||||
'node_modules/**',
|
|
||||||
'**/js/CubismSdkForWeb-5-r.1/**',
|
|
||||||
'**/live2d/models/**',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ignore: [
|
|
||||||
'**/js/CubismSdkForWeb-5-r.1/**',
|
|
||||||
'**/live2d/models/**',
|
|
||||||
],
|
|
||||||
noExternals: false,
|
|
||||||
routeRules: {
|
|
||||||
'/assets/**': { static: true },
|
|
||||||
'/assets/js/**': { static: true },
|
|
||||||
'/assets/live2d/models/**': { static: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
vite: {
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
name: 'live2d-cubism-sdk',
|
|
||||||
async configResolved(config) {
|
|
||||||
const publicDir = resolve(join(config.root, '../public'))
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (await exists(resolve(join(publicDir, 'assets/js/CubismSdkForWeb-5-r.1')))) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Downloading Cubism SDK...')
|
|
||||||
const stream = await ofetch('https://dist.ayaka.moe/npm/live2d-cubism/CubismSdkForWeb-5-r.1.zip', { responseType: 'arrayBuffer' })
|
|
||||||
|
|
||||||
console.log('Unzipping Cubism SDK...')
|
|
||||||
await mkdir(join(publicDir, 'assets/js'), { recursive: true })
|
|
||||||
await unzip(Buffer.from(stream), join(publicDir, 'assets/js'))
|
|
||||||
|
|
||||||
console.log('Cubism SDK downloaded and unzipped.')
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'live2d-models-hiyori-free',
|
|
||||||
async configResolved(config) {
|
|
||||||
const publicDir = resolve(join(config.root, '../public'))
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (await exists(resolve(join(publicDir, 'assets/live2d/models/hiyori_free_zh')))) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Downloading Demo Live2D Model - Hiyori Free...')
|
|
||||||
const stream = await ofetch('https://dist.ayaka.moe/live2d-models/hiyori_free_zh.zip', { responseType: 'arrayBuffer' })
|
|
||||||
|
|
||||||
console.log('Unzipping Demo Live2D Model - Hiyori Free...')
|
|
||||||
await mkdir(join(publicDir, 'assets/live2d/models'), { recursive: true })
|
|
||||||
await unzip(Buffer.from(stream), join(publicDir, 'assets/live2d/models'))
|
|
||||||
|
|
||||||
console.log('Demo Live2D Model - Hiyori Free downloaded and unzipped.')
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'live2d-models-hiyori-pro',
|
|
||||||
async configResolved(config) {
|
|
||||||
const publicDir = resolve(join(config.root, '../public'))
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (await exists(resolve(join(publicDir, 'assets/live2d/models/hiyori_pro_zh')))) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Downloading Demo Live2D Model - Hiyori Pro...')
|
|
||||||
const stream = await ofetch('https://dist.ayaka.moe/live2d-models/hiyori_pro_zh.zip', { responseType: 'arrayBuffer' })
|
|
||||||
|
|
||||||
console.log('Unzipping Demo Live2D Model - Hiyori Pro...')
|
|
||||||
await mkdir(join(publicDir, 'assets/live2d/models'), { recursive: true })
|
|
||||||
await unzip(Buffer.from(stream), join(publicDir, 'assets/live2d/models'))
|
|
||||||
|
|
||||||
console.log('Demo Live2D Model - Hiyori Pro downloaded and unzipped.')
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
eslint: {
|
|
||||||
config: {
|
|
||||||
standalone: false,
|
|
||||||
nuxt: {
|
|
||||||
sortConfigKeys: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
pwa,
|
|
||||||
})
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "module",
|
|
||||||
"private": true,
|
|
||||||
"packageManager": "pnpm@9.14.4",
|
|
||||||
"scripts": {
|
|
||||||
"build": "nuxi build",
|
|
||||||
"dev:pwa": "VITE_PLUGIN_PWA=true nuxi dev",
|
|
||||||
"dev": "nuxi dev",
|
|
||||||
"generate": "nuxi generate",
|
|
||||||
"prepare": "nuxi prepare",
|
|
||||||
"start": "node .output/server/index.mjs",
|
|
||||||
"start:generate": "npx serve .output/public",
|
|
||||||
"lint": "eslint .",
|
|
||||||
"typecheck": "vue-tsc --noEmit"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@ai-sdk/openai": "^1.0.5",
|
|
||||||
"@antfu/eslint-config": "^3.11.2",
|
|
||||||
"@iconify-json/carbon": "^1.2.4",
|
|
||||||
"@iconify-json/twemoji": "^1.2.1",
|
|
||||||
"@nuxt/devtools": "^1.6.1",
|
|
||||||
"@nuxt/eslint": "^0.7.2",
|
|
||||||
"@nuxtjs/color-mode": "^3.5.2",
|
|
||||||
"@pinia/nuxt": "^0.8.0",
|
|
||||||
"@types/node": "^22.10.1",
|
|
||||||
"@types/yauzl": "^2.10.3",
|
|
||||||
"@typeschema/zod": "^0.14.0",
|
|
||||||
"@unocss/eslint-config": "^0.65.0",
|
|
||||||
"@unocss/nuxt": "^0.65.0",
|
|
||||||
"@vite-pwa/nuxt": "^0.10.6",
|
|
||||||
"@vueuse/nuxt": "^12.0.0",
|
|
||||||
"ai": "^4.0.10",
|
|
||||||
"consola": "^3.2.3",
|
|
||||||
"eslint": "^9.16.0",
|
|
||||||
"eslint-plugin-format": "^0.1.3",
|
|
||||||
"nuxt": "^3.14.1592",
|
|
||||||
"ofetch": "^1.4.1",
|
|
||||||
"openai": "^4.73.1",
|
|
||||||
"pinia": "^2.2.8",
|
|
||||||
"rehype-stringify": "^10.0.1",
|
|
||||||
"remark-parse": "^11.0.0",
|
|
||||||
"remark-rehype": "^11.1.1",
|
|
||||||
"typescript": "~5.7.2",
|
|
||||||
"unified": "^11.0.5",
|
|
||||||
"unplugin": "2.0.0-beta.1",
|
|
||||||
"vite": "^6.0.2",
|
|
||||||
"vite-plugin-inspect": "^0.10.2",
|
|
||||||
"vue-tsc": "^2.1.10",
|
|
||||||
"yauzl": "^3.2.0",
|
|
||||||
"zod": "^3.23.8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -1,3 +0,0 @@
|
|||||||
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M504.908 750H839.476C850.103 750.001 860.542 747.229 869.745 741.963C878.948 736.696 886.589 729.121 891.9 719.999C897.211 710.876 900.005 700.529 900 689.997C899.995 679.465 897.193 669.12 891.873 660.002L667.187 274.289C661.876 265.169 654.237 257.595 645.036 252.329C635.835 247.064 625.398 244.291 614.773 244.291C604.149 244.291 593.711 247.064 584.511 252.329C575.31 257.595 567.67 265.169 562.36 274.289L504.908 372.979L392.581 179.993C387.266 170.874 379.623 163.301 370.42 158.036C361.216 152.772 350.777 150 340.151 150C329.525 150 319.086 152.772 309.883 158.036C300.679 163.301 293.036 170.874 287.721 179.993L8.12649 660.002C2.80743 669.12 0.00462935 679.465 5.72978e-06 689.997C-0.00461789 700.529 2.78909 710.876 8.10015 719.999C13.4112 729.121 21.0523 736.696 30.255 741.963C39.4576 747.229 49.8973 750.001 60.524 750H270.538C353.748 750 415.112 713.775 457.336 643.101L559.849 467.145L614.757 372.979L779.547 655.834H559.849L504.908 750ZM267.114 655.737L120.551 655.704L340.249 278.586L449.87 467.145L376.474 593.175C348.433 639.03 316.577 655.737 267.114 655.737Z" fill="#00DC82"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -1,2 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Allow: /
|
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,38 +0,0 @@
|
|||||||
export function rejectIfError<E = unknown>(error: E | undefined, reject: (error?: E) => void, handler?: (error?: E) => void) {
|
|
||||||
if (error) {
|
|
||||||
reject(error)
|
|
||||||
!!handler && handler(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveWhenNoError<R = void, E = unknown>(reject: (error?: E) => void, resolve: (result?: R) => void) {
|
|
||||||
return (err?: E) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function onError<E = unknown>(reject: (error?: E) => void, handler?: (error?: E) => void) {
|
|
||||||
return (error?: E) => rejectIfError(error, reject, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function noError<
|
|
||||||
T,
|
|
||||||
U extends unknown[],
|
|
||||||
E = unknown,
|
|
||||||
>(
|
|
||||||
reject: (err?: E) => void,
|
|
||||||
fn: (...args: U) => T,
|
|
||||||
): (err: E | undefined, ...args: U) => T | undefined {
|
|
||||||
return (err, ...args) => {
|
|
||||||
if (err) {
|
|
||||||
rejectIfError(err, reject)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return fn(...args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { stat } from 'node:fs/promises'
|
|
||||||
|
|
||||||
export async function exists(path: string) {
|
|
||||||
try {
|
|
||||||
await stat(path)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
if (isENOENTError(error))
|
|
||||||
return false
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isENOENTError(error: unknown): boolean {
|
|
||||||
if (!(error instanceof Error))
|
|
||||||
return false
|
|
||||||
if (!('code' in error))
|
|
||||||
return false
|
|
||||||
if (error.code !== 'ENOENT')
|
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import type { Buffer } from 'node:buffer'
|
|
||||||
import { createWriteStream, existsSync, mkdirSync } from 'node:fs'
|
|
||||||
import { dirname, join } from 'node:path'
|
|
||||||
import { fromBuffer } from 'yauzl'
|
|
||||||
import { noError, onError, resolveWhenNoError } from './errors'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example:
|
|
||||||
*
|
|
||||||
* await unzip("./tim.zip", "./");
|
|
||||||
*
|
|
||||||
* Will create directories:
|
|
||||||
*
|
|
||||||
* ./tim.zip
|
|
||||||
* ./tim
|
|
||||||
*
|
|
||||||
* Originally by [How to unzip to a folder using yauzl? - Stack Overflow](https://stackoverflow.com/questions/63932027/how-to-unzip-to-a-folder-using-yauzl)
|
|
||||||
*
|
|
||||||
* @param buffer Buffer of the zip file.
|
|
||||||
* @param target Path to the folder where the zip folder will be put.
|
|
||||||
*/
|
|
||||||
export async function unzip(buffer: Buffer, target: string) {
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
let pendingWrites = 0
|
|
||||||
|
|
||||||
fromBuffer(buffer, { lazyEntries: true }, noError(reject, (zipFile) => {
|
|
||||||
// This is the key. We start by reading the first entry.
|
|
||||||
zipFile.readEntry()
|
|
||||||
|
|
||||||
// Now for every entry, we will write a file or dir
|
|
||||||
// to disk. Then call zipFile.readEntry() again to
|
|
||||||
// trigger the next cycle.
|
|
||||||
zipFile.on('entry', (entry) => {
|
|
||||||
// Directories
|
|
||||||
if (/\/$/.test(entry.fileName)) {
|
|
||||||
// Create the directory then read the next entry.
|
|
||||||
mkdirSync(join(target, entry.fileName), { recursive: true })
|
|
||||||
zipFile.readEntry()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Files
|
|
||||||
const dir = dirname(join(target, entry.fileName))
|
|
||||||
if (!existsSync(dir)) {
|
|
||||||
mkdirSync(dir, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the file to disk.
|
|
||||||
pendingWrites++
|
|
||||||
zipFile.openReadStream(entry, noError(reject, (readStream) => {
|
|
||||||
const file = createWriteStream(join(target, entry.fileName))
|
|
||||||
readStream.pipe(file)
|
|
||||||
|
|
||||||
// Handle errors
|
|
||||||
file.on('error', (err) => {
|
|
||||||
pendingWrites--
|
|
||||||
zipFile.close()
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait until the file is finished writing, then read the next entry.
|
|
||||||
file.on('finish', () => {
|
|
||||||
file.close(() => {
|
|
||||||
pendingWrites--
|
|
||||||
if (pendingWrites === 0) {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
zipFile.readEntry()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
zipFile.on('error', onError(reject, zipFile.close))
|
|
||||||
zipFile.on('end', resolveWhenNoError(reject, resolve))
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../.nuxt/tsconfig.server.json"
|
|
||||||
}
|
|
||||||
@@ -1,593 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
/* prettier-ignore */
|
|
||||||
// @ts-nocheck
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
// Generated by unplugin-auto-import
|
|
||||||
// biome-ignore lint: disable
|
|
||||||
export {}
|
|
||||||
declare global {
|
|
||||||
const EffectScope: typeof import('vue')['EffectScope']
|
|
||||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
|
||||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
|
||||||
const computed: typeof import('vue')['computed']
|
|
||||||
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
|
||||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
|
||||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
|
||||||
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
|
||||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
|
||||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
|
||||||
const createApp: typeof import('vue')['createApp']
|
|
||||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
|
||||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
|
||||||
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
|
||||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
|
||||||
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
|
|
||||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
|
||||||
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
|
|
||||||
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
|
||||||
const customRef: typeof import('vue')['customRef']
|
|
||||||
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
|
||||||
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
|
||||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
|
||||||
const defineComponent: typeof import('vue')['defineComponent']
|
|
||||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
|
||||||
const effectScope: typeof import('vue')['effectScope']
|
|
||||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
|
||||||
const h: typeof import('vue')['h']
|
|
||||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
|
||||||
const inject: typeof import('vue')['inject']
|
|
||||||
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
|
||||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
|
||||||
const isProxy: typeof import('vue')['isProxy']
|
|
||||||
const isReactive: typeof import('vue')['isReactive']
|
|
||||||
const isReadonly: typeof import('vue')['isReadonly']
|
|
||||||
const isRef: typeof import('vue')['isRef']
|
|
||||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
|
||||||
const markRaw: typeof import('vue')['markRaw']
|
|
||||||
const nextTick: typeof import('vue')['nextTick']
|
|
||||||
const onActivated: typeof import('vue')['onActivated']
|
|
||||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
|
||||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
|
||||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
|
||||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
|
||||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
|
||||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
|
||||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
|
||||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
|
||||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
|
||||||
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
|
||||||
const onMounted: typeof import('vue')['onMounted']
|
|
||||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
|
||||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
|
||||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
|
||||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
|
||||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
|
||||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
|
||||||
const onUpdated: typeof import('vue')['onUpdated']
|
|
||||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
|
||||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
|
||||||
const provide: typeof import('vue')['provide']
|
|
||||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
|
||||||
const reactify: typeof import('@vueuse/core')['reactify']
|
|
||||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
|
||||||
const reactive: typeof import('vue')['reactive']
|
|
||||||
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
|
|
||||||
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
|
|
||||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
|
||||||
const readonly: typeof import('vue')['readonly']
|
|
||||||
const ref: typeof import('vue')['ref']
|
|
||||||
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
|
||||||
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
|
||||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
|
||||||
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
|
||||||
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
|
||||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
|
||||||
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
|
||||||
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
|
||||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
|
||||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
|
||||||
const shallowRef: typeof import('vue')['shallowRef']
|
|
||||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
|
||||||
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
|
||||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
|
||||||
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
|
||||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
|
||||||
const toRaw: typeof import('vue')['toRaw']
|
|
||||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
|
||||||
const toRef: typeof import('vue')['toRef']
|
|
||||||
const toRefs: typeof import('vue')['toRefs']
|
|
||||||
const toValue: typeof import('vue')['toValue']
|
|
||||||
const triggerRef: typeof import('vue')['triggerRef']
|
|
||||||
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
|
||||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
|
||||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
|
||||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
|
||||||
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
|
||||||
const unref: typeof import('vue')['unref']
|
|
||||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
|
||||||
const until: typeof import('@vueuse/core')['until']
|
|
||||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
|
||||||
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
|
||||||
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
|
||||||
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
|
|
||||||
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
|
|
||||||
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
|
|
||||||
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
|
|
||||||
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
|
|
||||||
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
|
|
||||||
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
|
|
||||||
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
|
|
||||||
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
|
|
||||||
const useArraySome: typeof import('@vueuse/core')['useArraySome']
|
|
||||||
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
|
|
||||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
|
||||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
|
||||||
const useAttrs: typeof import('vue')['useAttrs']
|
|
||||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
|
||||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
|
||||||
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
|
||||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
|
||||||
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
|
||||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
|
||||||
const useCached: typeof import('@vueuse/core')['useCached']
|
|
||||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
|
||||||
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
|
|
||||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
|
||||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
|
||||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
|
||||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
|
||||||
const useCssModule: typeof import('vue')['useCssModule']
|
|
||||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
|
||||||
const useCssVars: typeof import('vue')['useCssVars']
|
|
||||||
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
|
||||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
|
||||||
const useDark: typeof import('@vueuse/core')['useDark']
|
|
||||||
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
|
||||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
|
||||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
|
||||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
|
||||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
|
||||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
|
||||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
|
||||||
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
|
||||||
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
|
||||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
|
||||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
|
||||||
const useDropZone: typeof import('@vueuse/core')['useDropZone']
|
|
||||||
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
|
||||||
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
|
|
||||||
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
|
||||||
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
|
||||||
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
|
||||||
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
|
||||||
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
|
||||||
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
|
||||||
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
|
||||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
|
||||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
|
||||||
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
|
|
||||||
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
|
||||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
|
||||||
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
|
||||||
const useFps: typeof import('@vueuse/core')['useFps']
|
|
||||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
|
||||||
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
|
||||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
|
||||||
const useHead: typeof import('@vueuse/head')['useHead']
|
|
||||||
const useI18n: typeof import('vue-i18n')['useI18n']
|
|
||||||
const useId: typeof import('vue')['useId']
|
|
||||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
|
||||||
const useImage: typeof import('@vueuse/core')['useImage']
|
|
||||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
|
||||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
|
||||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
|
||||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
|
||||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
|
||||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
|
||||||
const useLink: typeof import('vue-router/auto')['useLink']
|
|
||||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
|
||||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
|
||||||
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
|
||||||
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
|
||||||
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
|
||||||
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
|
||||||
const useMemory: typeof import('@vueuse/core')['useMemory']
|
|
||||||
const useModel: typeof import('vue')['useModel']
|
|
||||||
const useMounted: typeof import('@vueuse/core')['useMounted']
|
|
||||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
|
||||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
|
||||||
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
|
||||||
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
|
||||||
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
|
||||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
|
||||||
const useNow: typeof import('@vueuse/core')['useNow']
|
|
||||||
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
|
|
||||||
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
|
|
||||||
const useOnline: typeof import('@vueuse/core')['useOnline']
|
|
||||||
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
|
||||||
const useParallax: typeof import('@vueuse/core')['useParallax']
|
|
||||||
const useParentElement: typeof import('@vueuse/core')['useParentElement']
|
|
||||||
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
|
|
||||||
const usePermission: typeof import('@vueuse/core')['usePermission']
|
|
||||||
const usePointer: typeof import('@vueuse/core')['usePointer']
|
|
||||||
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
|
|
||||||
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
|
||||||
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
|
||||||
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
|
|
||||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
|
||||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
|
||||||
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
|
||||||
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
|
||||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
|
||||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
|
||||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
|
||||||
const useRoute: typeof import('vue-router')['useRoute']
|
|
||||||
const useRouter: typeof import('vue-router')['useRouter']
|
|
||||||
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
|
||||||
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
|
||||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
|
||||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
|
||||||
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
|
||||||
const useSeoMeta: typeof import('@vueuse/head')['useSeoMeta']
|
|
||||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
|
||||||
const useShare: typeof import('@vueuse/core')['useShare']
|
|
||||||
const useSlots: typeof import('vue')['useSlots']
|
|
||||||
const useSorted: typeof import('@vueuse/core')['useSorted']
|
|
||||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
|
||||||
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
|
||||||
const useStepper: typeof import('@vueuse/core')['useStepper']
|
|
||||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
|
||||||
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
|
||||||
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
|
||||||
const useSupported: typeof import('@vueuse/core')['useSupported']
|
|
||||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
|
||||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
|
||||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
|
||||||
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
|
|
||||||
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
|
||||||
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
|
|
||||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
|
||||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
|
||||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
|
||||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
|
||||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
|
||||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
|
||||||
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
|
||||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
|
||||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
|
||||||
const useToNumber: typeof import('@vueuse/core')['useToNumber']
|
|
||||||
const useToString: typeof import('@vueuse/core')['useToString']
|
|
||||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
|
||||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
|
||||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
|
||||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
|
||||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
|
||||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
|
||||||
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
|
||||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
|
||||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
|
||||||
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
|
||||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
|
||||||
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
|
||||||
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
|
||||||
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
|
||||||
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
|
||||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
|
||||||
const watch: typeof import('vue')['watch']
|
|
||||||
const watchArray: typeof import('@vueuse/core')['watchArray']
|
|
||||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
|
||||||
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
|
|
||||||
const watchDeep: typeof import('@vueuse/core')['watchDeep']
|
|
||||||
const watchEffect: typeof import('vue')['watchEffect']
|
|
||||||
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
|
||||||
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
|
|
||||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
|
||||||
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
|
||||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
|
||||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
|
||||||
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
|
||||||
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
|
|
||||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
|
||||||
const whenever: typeof import('@vueuse/core')['whenever']
|
|
||||||
}
|
|
||||||
// for type re-export
|
|
||||||
declare global {
|
|
||||||
// @ts-ignore
|
|
||||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
|
||||||
import('vue')
|
|
||||||
}
|
|
||||||
|
|
||||||
// for vue template auto import
|
|
||||||
import { UnwrapRef } from 'vue'
|
|
||||||
declare module 'vue' {
|
|
||||||
interface GlobalComponents {}
|
|
||||||
interface ComponentCustomProperties {
|
|
||||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
|
||||||
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
|
||||||
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
|
||||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
|
||||||
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
|
|
||||||
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
|
|
||||||
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
|
|
||||||
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
|
|
||||||
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
|
|
||||||
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
|
|
||||||
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
|
||||||
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
|
|
||||||
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
|
|
||||||
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
|
|
||||||
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
|
|
||||||
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
|
|
||||||
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
|
|
||||||
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
|
|
||||||
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
|
|
||||||
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
|
|
||||||
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
|
|
||||||
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
|
|
||||||
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
|
||||||
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
|
||||||
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
|
|
||||||
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
|
||||||
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
|
|
||||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
|
||||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
|
||||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
|
||||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
|
||||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
|
||||||
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
|
||||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
|
||||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
|
||||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
|
||||||
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
|
||||||
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
|
||||||
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
|
|
||||||
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
|
||||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
|
||||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
|
||||||
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
|
||||||
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
|
||||||
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
|
|
||||||
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
|
||||||
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
|
||||||
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
|
|
||||||
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
|
||||||
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
|
||||||
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
|
|
||||||
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
|
|
||||||
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
|
||||||
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
|
||||||
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
|
||||||
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
|
||||||
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
|
|
||||||
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
|
|
||||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
|
||||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
|
||||||
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
|
|
||||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
|
||||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
|
||||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
|
||||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
|
||||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
|
||||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
|
||||||
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
|
|
||||||
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
|
|
||||||
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
|
|
||||||
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
|
||||||
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
|
||||||
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
|
|
||||||
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
|
|
||||||
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
|
|
||||||
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
|
|
||||||
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
|
|
||||||
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
|
||||||
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
|
|
||||||
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
|
||||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
|
||||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
|
||||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
|
||||||
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
|
|
||||||
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
|
|
||||||
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
|
|
||||||
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
|
|
||||||
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
|
|
||||||
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
|
|
||||||
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
|
|
||||||
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
|
||||||
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
|
||||||
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
|
||||||
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
|
||||||
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
|
|
||||||
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
|
|
||||||
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
|
|
||||||
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
|
|
||||||
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
|
|
||||||
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
|
||||||
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
|
||||||
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
|
||||||
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
|
||||||
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
|
||||||
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
|
||||||
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
|
|
||||||
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
|
|
||||||
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
|
|
||||||
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
|
|
||||||
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
|
|
||||||
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
|
|
||||||
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
|
|
||||||
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
|
|
||||||
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
|
|
||||||
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
|
|
||||||
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
|
|
||||||
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
|
|
||||||
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
|
|
||||||
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
|
||||||
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
|
|
||||||
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
|
|
||||||
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
|
|
||||||
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
|
|
||||||
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
|
|
||||||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
|
||||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
|
||||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
|
||||||
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
|
||||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
|
||||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
|
||||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
|
||||||
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
|
|
||||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
|
||||||
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
|
||||||
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
|
||||||
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
|
|
||||||
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
|
|
||||||
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
|
|
||||||
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
|
|
||||||
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
|
|
||||||
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
|
|
||||||
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
|
|
||||||
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
|
|
||||||
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
|
|
||||||
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
|
|
||||||
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
|
|
||||||
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
|
|
||||||
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
|
|
||||||
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
|
|
||||||
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
|
|
||||||
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
|
|
||||||
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
|
|
||||||
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
|
|
||||||
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
|
|
||||||
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
|
|
||||||
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
|
|
||||||
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
|
|
||||||
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
|
|
||||||
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
|
|
||||||
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
|
|
||||||
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
|
|
||||||
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
|
|
||||||
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
|
|
||||||
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
|
|
||||||
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
|
|
||||||
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
|
|
||||||
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
|
|
||||||
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
|
|
||||||
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
|
|
||||||
readonly useHead: UnwrapRef<typeof import('@vueuse/head')['useHead']>
|
|
||||||
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
|
|
||||||
readonly useId: UnwrapRef<typeof import('vue')['useId']>
|
|
||||||
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
|
|
||||||
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
|
|
||||||
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
|
|
||||||
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
|
|
||||||
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
|
|
||||||
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
|
|
||||||
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
|
||||||
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
|
||||||
readonly useLink: UnwrapRef<typeof import('vue-router/auto')['useLink']>
|
|
||||||
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
|
||||||
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
|
||||||
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
|
|
||||||
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
|
|
||||||
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
|
|
||||||
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
|
|
||||||
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
|
|
||||||
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
|
|
||||||
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
|
|
||||||
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
|
|
||||||
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
|
|
||||||
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
|
|
||||||
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
|
|
||||||
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
|
|
||||||
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
|
|
||||||
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
|
|
||||||
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
|
|
||||||
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
|
|
||||||
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
|
|
||||||
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
|
|
||||||
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
|
|
||||||
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
|
|
||||||
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
|
|
||||||
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
|
|
||||||
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
|
|
||||||
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
|
|
||||||
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
|
|
||||||
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
|
|
||||||
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
|
|
||||||
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
|
|
||||||
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
|
|
||||||
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
|
||||||
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
|
||||||
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
|
||||||
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
|
||||||
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
|
|
||||||
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
|
||||||
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
|
||||||
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
|
|
||||||
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
|
|
||||||
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
|
|
||||||
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
|
|
||||||
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
|
|
||||||
readonly useSeoMeta: UnwrapRef<typeof import('@vueuse/head')['useSeoMeta']>
|
|
||||||
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
|
|
||||||
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
|
|
||||||
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
|
||||||
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
|
|
||||||
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
|
|
||||||
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
|
|
||||||
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
|
|
||||||
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
|
|
||||||
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
|
|
||||||
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
|
|
||||||
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
|
|
||||||
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
|
|
||||||
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
|
|
||||||
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
|
|
||||||
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
|
|
||||||
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
|
|
||||||
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
|
|
||||||
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
|
|
||||||
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
|
|
||||||
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
|
|
||||||
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
|
|
||||||
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
|
|
||||||
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
|
|
||||||
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
|
|
||||||
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
|
|
||||||
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
|
|
||||||
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
|
|
||||||
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
|
|
||||||
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
|
|
||||||
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
|
|
||||||
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
|
|
||||||
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
|
|
||||||
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
|
|
||||||
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
|
|
||||||
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
|
|
||||||
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
|
|
||||||
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
|
|
||||||
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
|
|
||||||
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
|
|
||||||
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
|
|
||||||
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
|
|
||||||
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
|
|
||||||
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
|
|
||||||
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
|
|
||||||
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
|
||||||
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
|
|
||||||
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
|
|
||||||
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
|
|
||||||
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
|
|
||||||
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
|
||||||
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
|
|
||||||
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
|
|
||||||
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
|
|
||||||
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
|
|
||||||
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
|
||||||
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
|
||||||
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
|
|
||||||
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
|
|
||||||
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
|
|
||||||
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./.nuxt/tsconfig.json",
|
|
||||||
"exclude": [
|
|
||||||
"public/assets/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import {
|
|
||||||
defineConfig,
|
|
||||||
presetAttributify,
|
|
||||||
presetIcons,
|
|
||||||
presetTypography,
|
|
||||||
presetUno,
|
|
||||||
presetWebFonts,
|
|
||||||
transformerDirectives,
|
|
||||||
transformerVariantGroup,
|
|
||||||
} from 'unocss'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
shortcuts: [
|
|
||||||
['btn', 'px-4 py-1 rounded inline-block bg-teal-600 text-white cursor-pointer hover:bg-teal-700 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'],
|
|
||||||
['icon-btn', 'inline-block cursor-pointer select-none opacity-75 transition duration-200 ease-in-out hover:opacity-100 hover:text-teal-600'],
|
|
||||||
],
|
|
||||||
presets: [
|
|
||||||
presetUno(),
|
|
||||||
presetAttributify(),
|
|
||||||
presetIcons({
|
|
||||||
scale: 1.2,
|
|
||||||
}),
|
|
||||||
presetTypography(),
|
|
||||||
presetWebFonts({
|
|
||||||
fonts: {
|
|
||||||
sans: 'DM Sans',
|
|
||||||
serif: 'DM Serif Display',
|
|
||||||
mono: 'DM Mono',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
transformers: [
|
|
||||||
transformerDirectives(),
|
|
||||||
transformerVariantGroup(),
|
|
||||||
],
|
|
||||||
})
|
|
||||||