chore(*): use @moeru/std & cleanup code, rename
This commit is contained in:
@@ -60,6 +60,8 @@ export default defineConfig({
|
||||
DownloadLive2DSDK(),
|
||||
Download('https://dist.ayaka.moe/live2d-models/hiyori_free_zh.zip', 'hiyori_free_zh.zip', 'assets/live2d/models'),
|
||||
Download('https://dist.ayaka.moe/live2d-models/hiyori_pro_zh.zip', 'hiyori_pro_zh.zip', 'assets/live2d/models'),
|
||||
Download('https://dist.ayaka.moe/vrm-models/VRoid-Hub/AvatarSample-A/AvatarSample_A.vrm', 'AvatarSample_A.vrm', 'assets/vrm/models/AvatarSample-A'),
|
||||
Download('https://dist.ayaka.moe/vrm-models/VRoid-Hub/AvatarSample-B/AvatarSample_B.vrm', 'AvatarSample_B.vrm', 'assets/vrm/models/AvatarSample-B'),
|
||||
],
|
||||
server: {
|
||||
watch: {
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>アイリ</title>
|
||||
<title>AIRI</title>
|
||||
<description>Open source attempt to create cyber companion</description>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" sizes="any" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@huggingface/transformers": "^3.5.2",
|
||||
"@llama-flow/core": "^0.4.4",
|
||||
"@moeru/std": "0.1.0-beta.1",
|
||||
"@pixiv/three-vrm": "^3.4.1",
|
||||
"@pixiv/three-vrm-animation": "^3.4.1",
|
||||
"@pixiv/three-vrm-core": "^3.4.1",
|
||||
|
||||
@@ -19,8 +19,8 @@ const dark = useDark()
|
||||
<template v-else>
|
||||
<img :src="Logo" h-8 w-8 class="theme-colored">
|
||||
</template>
|
||||
<div font-cute>
|
||||
<span>アイリ</span>
|
||||
<div translate-y="[2px]" font-semibold font-quicksand>
|
||||
<span>AIRI</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { sleep } from '@moeru/std'
|
||||
import { useQueue } from '@proj-airi/stage-ui/composables/queue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
const temp = ref<string>('')
|
||||
|
||||
const audioQueue = useQueue<string>({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { sleep } from '@moeru/std'
|
||||
import { Textarea } from '@proj-airi/ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
@@ -6,10 +7,6 @@ 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
|
||||
|
||||
|
||||
@@ -9,10 +9,6 @@ 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) => {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { cp, mkdir, writeFile } from 'node:fs/promises'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { env } from 'node:process'
|
||||
|
||||
@@ -9,7 +7,6 @@ import { DownloadLive2DSDK } from '@proj-airi/unplugin-live2d-sdk/vite'
|
||||
import { templateCompilerOptions } from '@tresjs/core'
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
import { LFS, SpaceCard } from 'hfup/vite'
|
||||
import { ofetch } from 'ofetch'
|
||||
import Unocss from 'unocss/vite'
|
||||
import VueMacros from 'unplugin-vue-macros/vite'
|
||||
import VueRouter from 'unplugin-vue-router/vite'
|
||||
@@ -18,15 +15,18 @@ import { VitePWA } from 'vite-plugin-pwa'
|
||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||
import Layouts from 'vite-plugin-vue-layouts'
|
||||
|
||||
import { exists } from '../../scripts/fs'
|
||||
|
||||
export default defineConfig({
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
// Internal Packages
|
||||
'@proj-airi/stage-ui/*',
|
||||
'@proj-airi/drizzle-duckdb-wasm',
|
||||
'@proj-airi/drizzle-duckdb-wasm/*',
|
||||
|
||||
// Static Assets: Models, Images, etc.
|
||||
'public/assets/*',
|
||||
|
||||
// Live2D SDK
|
||||
'@framework/live2dcubismframework',
|
||||
'@framework/math/cubismmatrix44',
|
||||
'@framework/type/csmvector',
|
||||
@@ -74,6 +74,7 @@ export default defineConfig({
|
||||
VueRouter({
|
||||
extensions: ['.vue', '.md'],
|
||||
dts: resolve(import.meta.dirname, 'src/typed-router.d.ts'),
|
||||
importMode: 'async',
|
||||
}),
|
||||
|
||||
// https://github.com/JohnCampionJr/vite-plugin-vue-layouts
|
||||
@@ -143,79 +144,11 @@ export default defineConfig({
|
||||
DownloadLive2DSDK(),
|
||||
Download('https://dist.ayaka.moe/live2d-models/hiyori_free_zh.zip', 'hiyori_free_zh.zip', 'assets/live2d/models'),
|
||||
Download('https://dist.ayaka.moe/live2d-models/hiyori_pro_zh.zip', 'hiyori_pro_zh.zip', 'assets/live2d/models'),
|
||||
|
||||
{
|
||||
name: 'vrm-models-sample-a',
|
||||
async configResolved(config) {
|
||||
const cacheDir = resolve(join(config.root, '.cache'))
|
||||
const publicDir = resolve(join(config.root, 'public'))
|
||||
|
||||
try {
|
||||
if (!(await exists(resolve(join(cacheDir, 'assets/vrm/models/AvatarSample-A'))))) {
|
||||
await mkdir(join(cacheDir, 'assets/vrm/models/AvatarSample-A'), { recursive: true })
|
||||
|
||||
console.log('Downloading VRM Model - Avatar Sample A...')
|
||||
const res = await ofetch('https://dist.ayaka.moe/vrm-models/VRoid-Hub/AvatarSample-A/AvatarSample_A.vrm', { responseType: 'arrayBuffer' })
|
||||
|
||||
console.log('Saving VRM Model - Avatar Sample A...')
|
||||
await writeFile(join(cacheDir, 'assets/vrm/models/AvatarSample-A/AvatarSample_A.vrm'), Buffer.from(res))
|
||||
|
||||
console.log('VRM Model - Avatar Sample A downloaded and saved.')
|
||||
}
|
||||
|
||||
if (!(await exists(resolve(join(publicDir, 'assets/vrm/models/AvatarSample-A'))))) {
|
||||
await mkdir(join(publicDir, 'assets/vrm/models/AvatarSample-A'), { recursive: true }).catch(() => { })
|
||||
await cp(join(cacheDir, 'assets/vrm/models/AvatarSample-A'), join(publicDir, 'assets/vrm/models/AvatarSample-A'), { recursive: true })
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'vrm-models-sample-b',
|
||||
async configResolved(config) {
|
||||
const cacheDir = resolve(join(config.root, '.cache'))
|
||||
const publicDir = resolve(join(config.root, 'public'))
|
||||
|
||||
try {
|
||||
if (!(await exists(resolve(join(cacheDir, 'assets/vrm/models/AvatarSample-B'))))) {
|
||||
await mkdir(join(cacheDir, 'assets/vrm/models/AvatarSample-B'), { recursive: true })
|
||||
|
||||
console.log('Downloading VRM Model - Avatar Sample B...')
|
||||
const res = await ofetch('https://dist.ayaka.moe/vrm-models/VRoid-Hub/AvatarSample-B/AvatarSample_B.vrm', { responseType: 'arrayBuffer' })
|
||||
|
||||
console.log('Saving VRM Model - Avatar Sample B...')
|
||||
await writeFile(join(cacheDir, 'assets/vrm/models/AvatarSample-B/AvatarSample_B.vrm'), Buffer.from(res))
|
||||
}
|
||||
|
||||
if (!(await exists(resolve(join(publicDir, 'assets/vrm/models/AvatarSample-B'))))) {
|
||||
await mkdir(join(publicDir, 'assets/vrm/models/AvatarSample-B'), { recursive: true }).catch(() => { })
|
||||
await cp(join(cacheDir, 'assets/vrm/models/AvatarSample-B'), join(publicDir, 'assets/vrm/models/AvatarSample-B'), { recursive: true })
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
},
|
||||
Download('https://dist.ayaka.moe/vrm-models/VRoid-Hub/AvatarSample-A/AvatarSample_A.vrm', 'AvatarSample_A.vrm', 'assets/vrm/models/AvatarSample-A'),
|
||||
Download('https://dist.ayaka.moe/vrm-models/VRoid-Hub/AvatarSample-B/AvatarSample_B.vrm', 'AvatarSample_B.vrm', 'assets/vrm/models/AvatarSample-B'),
|
||||
|
||||
// HuggingFace Spaces
|
||||
LFS({
|
||||
extraGlobs: [
|
||||
'*.vrm',
|
||||
'*.cmo3',
|
||||
'*.png',
|
||||
'*.jpg',
|
||||
'*.jpeg',
|
||||
'*.gif',
|
||||
'*.webp',
|
||||
'*.bmp',
|
||||
],
|
||||
}),
|
||||
LFS({ extraGlobs: ['*.vrm', '*.cmo3', '*.png', '*.jpg', '*.jpeg', '*.gif', '*.webp', '*.bmp'] }),
|
||||
SpaceCard({
|
||||
title: 'アイリ VTuber',
|
||||
emoji: '🧸',
|
||||
@@ -224,7 +157,10 @@ export default defineConfig({
|
||||
sdk: 'static',
|
||||
pinned: false,
|
||||
license: 'mit',
|
||||
models: ['onnx-community/whisper-base'],
|
||||
models: [
|
||||
'onnx-community/whisper-base',
|
||||
'onnx-community/silero-vad',
|
||||
],
|
||||
short_description: 'アイリ VTuber. LLM powered Live2D/VRM living character.',
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -40,5 +40,8 @@
|
||||
"@proj-airi/server-shared": "workspace:^",
|
||||
"crossws": "^0.4.1",
|
||||
"defu": "^6.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@moeru/std": "0.1.0-beta.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { WebSocketBaseEvent, WebSocketEvent, WebSocketEvents } from '@proj-airi/server-shared/types'
|
||||
|
||||
import { sleep } from '@moeru/std'
|
||||
import WebSocket from 'crossws/websocket'
|
||||
import { defu } from 'defu'
|
||||
|
||||
import { sleep } from './utils'
|
||||
|
||||
export interface ClientOptions<C = undefined> {
|
||||
url?: string
|
||||
name: string
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './concurrency'
|
||||
@@ -104,6 +104,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@histoire/plugin-vue": "1.0.0-alpha.2",
|
||||
"@moeru/std": "0.1.0-beta.1",
|
||||
"@types/culori": "^4.0.0",
|
||||
"@types/three": "^0.177.0",
|
||||
"@unocss/reset": "^66.2.3",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Emotion } from '../constants/emotions'
|
||||
import type { UseQueueReturn } from './queue'
|
||||
|
||||
import { sleep } from '@moeru/std'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { llmInferenceEndToken } from '../constants'
|
||||
@@ -86,10 +87,6 @@ export function useDelayMessageQueue() {
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
return useQueue<string>({
|
||||
handlers: [
|
||||
async (ctx) => {
|
||||
|
||||
Generated
+16
@@ -547,6 +547,9 @@ importers:
|
||||
'@llama-flow/core':
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4(@modelcontextprotocol/sdk@1.13.0)(zod@3.25.67)
|
||||
'@moeru/std':
|
||||
specifier: 0.1.0-beta.1
|
||||
version: 0.1.0-beta.1
|
||||
'@pixiv/three-vrm':
|
||||
specifier: ^3.4.1
|
||||
version: 3.4.1(three@0.177.0)
|
||||
@@ -909,6 +912,10 @@ importers:
|
||||
defu:
|
||||
specifier: ^6.1.4
|
||||
version: 6.1.4
|
||||
devDependencies:
|
||||
'@moeru/std':
|
||||
specifier: 0.1.0-beta.1
|
||||
version: 0.1.0-beta.1
|
||||
|
||||
packages/server-shared: {}
|
||||
|
||||
@@ -1116,6 +1123,9 @@ importers:
|
||||
'@histoire/plugin-vue':
|
||||
specifier: 1.0.0-alpha.2
|
||||
version: 1.0.0-alpha.2(histoire@1.0.0-alpha.2(@types/node@24.0.3)(bufferutil@4.0.9)(less@4.3.0)(lightningcss@1.30.1)(terser@5.17.6)(utf-8-validate@5.0.10)(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.17.6)(tsx@4.20.3)(yaml@2.8.0)))(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.17.6)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
'@moeru/std':
|
||||
specifier: 0.1.0-beta.1
|
||||
version: 0.1.0-beta.1
|
||||
'@types/culori':
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
@@ -1300,6 +1310,9 @@ importers:
|
||||
'@guiiai/logg':
|
||||
specifier: ^1.0.10
|
||||
version: 1.0.10
|
||||
'@moeru/std':
|
||||
specifier: 0.1.0-beta.1
|
||||
version: 0.1.0-beta.1
|
||||
'@proj-airi/server-sdk':
|
||||
specifier: ^0.6.1
|
||||
version: 0.6.1
|
||||
@@ -1391,6 +1404,9 @@ importers:
|
||||
'@guiiai/logg':
|
||||
specifier: ^1.0.10
|
||||
version: 1.0.10
|
||||
'@moeru/std':
|
||||
specifier: 0.1.0-beta.1
|
||||
version: 0.1.0-beta.1
|
||||
'@xsai-ext/providers-cloud':
|
||||
specifier: 'catalog:'
|
||||
version: 0.3.0-beta.4
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@guiiai/logg": "^1.0.10",
|
||||
"@moeru/std": "0.1.0-beta.1",
|
||||
"@proj-airi/server-sdk": "^0.6.1",
|
||||
"awilix": "^12.0.5",
|
||||
"es-toolkit": "^1.39.3",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { sleep } from '@moeru/std'
|
||||
import { messages, system, user } from 'neuri/openai'
|
||||
import { beforeAll, describe, expect, it } from 'vitest'
|
||||
|
||||
@@ -5,7 +6,6 @@ import { initBot, useBot } from '../../composables/bot'
|
||||
import { config, initEnv } from '../../composables/config'
|
||||
import { createNeuriAgent } from '../../composables/neuri'
|
||||
import { generateActionAgentPrompt } from '../../libs/llm-agent/prompt'
|
||||
import { sleep } from '../../utils/helper'
|
||||
import { initLogger } from '../../utils/logger'
|
||||
|
||||
describe('actions agent', { timeout: 0 }, () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Mineflayer } from '../../libs/mineflayer'
|
||||
|
||||
import { sleep } from '../../utils/helper'
|
||||
import { sleep } from '@moeru/std'
|
||||
|
||||
import { useLogger } from '../../utils/logger'
|
||||
import { breakBlockAt } from '../blocks'
|
||||
import { goToPosition, moveAway } from '../movement'
|
||||
|
||||
@@ -3,10 +3,10 @@ import type { Block } from 'prismarine-block'
|
||||
|
||||
import type { Mineflayer } from '../../libs/mineflayer'
|
||||
|
||||
import { sleep } from '@moeru/std'
|
||||
import pathfinder from 'mineflayer-pathfinder'
|
||||
import { Vec3 } from 'vec3'
|
||||
|
||||
import { sleep } from '../../utils/helper'
|
||||
import { useLogger } from '../../utils/logger'
|
||||
import { getNearestBlock, makeItem } from '../../utils/mcdata'
|
||||
import { goToPosition } from '../movement'
|
||||
|
||||
@@ -3,9 +3,9 @@ import type { Item } from 'prismarine-item'
|
||||
|
||||
import type { Mineflayer } from '../libs/mineflayer'
|
||||
|
||||
import { sleep } from '@moeru/std'
|
||||
import pathfinderModel from 'mineflayer-pathfinder'
|
||||
|
||||
import { sleep } from '../utils/helper'
|
||||
import { isHostile } from '../utils/mcdata'
|
||||
import { log } from './base'
|
||||
import { getNearbyEntities, getNearestEntityWhere } from './world'
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { Entity } from 'prismarine-entity'
|
||||
|
||||
import type { Mineflayer } from '../libs/mineflayer'
|
||||
|
||||
import { sleep } from '@moeru/std'
|
||||
import { randomInt } from 'es-toolkit'
|
||||
import pathfinder from 'mineflayer-pathfinder'
|
||||
import { Vec3 } from 'vec3'
|
||||
|
||||
import { sleep } from '../utils/helper'
|
||||
import { useLogger } from '../utils/logger'
|
||||
import { log } from './base'
|
||||
import { getNearestBlock, getNearestEntityWhere } from './world'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
import { sleep } from '@moeru/std'
|
||||
|
||||
/**
|
||||
* Returns a retirable anonymous function with configured retryLimit and delayInterval
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@grammyjs/files": "^1.1.1",
|
||||
"@guiiai/logg": "^1.0.10",
|
||||
"@moeru/std": "0.1.0-beta.1",
|
||||
"@xsai-ext/providers-cloud": "catalog:",
|
||||
"@xsai/embed": "catalog:",
|
||||
"@xsai/generate-text": "catalog:",
|
||||
|
||||
@@ -3,12 +3,13 @@ import type { Message } from 'grammy/types'
|
||||
import type { BotSelf } from '../../../types'
|
||||
|
||||
import { useLogg } from '@guiiai/logg'
|
||||
import { sleep } from '@moeru/std'
|
||||
import { parse } from 'best-effort-json-parser'
|
||||
import { randomInt } from 'es-toolkit'
|
||||
|
||||
import { recordMessage } from '../../../models'
|
||||
import { listJoinedChats } from '../../../models/chats'
|
||||
import { cancellable, sleep } from '../../../utils/promise'
|
||||
import { cancellable } from '../../../utils/promise'
|
||||
|
||||
export function parseMayStructuredMessage(responseText: string) {
|
||||
const logger = useLogg('parseMayStructuredMessage').useGlobalConfig()
|
||||
|
||||
@@ -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,9 +1,3 @@
|
||||
export function sleep(ms: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
||||
|
||||
export interface CancellablePromise<T> {
|
||||
promise: Promise<T>
|
||||
cancel: () => void
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
const timerMap = new Map()
|
||||
|
||||
export function setClockInterval(func: (...args: any[]) => any, interval: number) {
|
||||
let start: number
|
||||
let tick: number
|
||||
let clockTimer: ReturnType<typeof setTimeout>
|
||||
const timerId = Math.floor(Math.random() * 1e10)
|
||||
|
||||
const recurFunc = () => {
|
||||
func()
|
||||
const realExecuteTime = new Date().getTime()
|
||||
if (!start) {
|
||||
start = realExecuteTime
|
||||
}
|
||||
|
||||
tick = tick || start
|
||||
const diff = realExecuteTime - tick
|
||||
tick += interval
|
||||
|
||||
// Since setTimeout is not accurate, we need to adjust the interval
|
||||
clockTimer = setTimeout(recurFunc, interval - diff)
|
||||
timerMap.set(timerId, clockTimer)
|
||||
}
|
||||
|
||||
recurFunc()
|
||||
return timerId
|
||||
}
|
||||
Reference in New Issue
Block a user