refactor: use @moeru/std (#226)

This commit is contained in:
藍+85CD
2025-06-25 15:31:30 +08:00
committed by GitHub
parent b7619af258
commit c13811b392
20 changed files with 67 additions and 145 deletions
-1
View File
@@ -25,7 +25,6 @@
"@xsai/generate-text": "catalog:",
"@xsai/shared-chat": "catalog:",
"@xsai/stream-text": "catalog:",
"defu": "^6.1.4",
"es-toolkit": "^1.39.4",
"vue": "^3.5.17"
},
-1
View File
@@ -45,7 +45,6 @@
"@xsai/stream-text": "catalog:",
"@xsai/utils-chat": "catalog:",
"culori": "^4.0.1",
"defu": "^6.1.4",
"drizzle-kit": "^0.31.2",
"drizzle-orm": "^0.44.2",
"jszip": "^3.10.1",
-1
View File
@@ -50,7 +50,6 @@
"@xsai/stream-text": "catalog:",
"@xsai/utils-chat": "catalog:",
"culori": "^4.0.1",
"defu": "^6.1.4",
"driver.js": "^1.3.6",
"drizzle-kit": "^0.31.2",
"drizzle-orm": "^0.44.2",
-1
View File
@@ -38,7 +38,6 @@
"@guiiai/logg": "^1.0.10",
"@proj-airi/server-shared": "workspace:^",
"crossws": "^0.4.1",
"defu": "^6.1.4",
"h3": "^1.15.3",
"listhen": "^1.9.0"
}
+10 -13
View File
@@ -2,7 +2,6 @@ import type { WebSocketBaseEvent, WebSocketEvent, WebSocketEvents } from '@proj-
import { sleep } from '@moeru/std'
import WebSocket from 'crossws/websocket'
import { defu } from 'defu'
export interface ClientOptions<C = undefined> {
url?: string
@@ -17,7 +16,7 @@ export interface ClientOptions<C = undefined> {
export class Client<C = undefined> {
private connected = false
private opts: Required<ClientOptions<C>>
private opts: Required<Omit<ClientOptions<C>, 'token'>> & Pick<ClientOptions<C>, 'token'>
private websocket: WebSocket | undefined
private eventListeners: Map<keyof WebSocketEvents<C>, Array<(data: WebSocketBaseEvent<any, any>) => void | Promise<void>>> = new Map()
@@ -25,17 +24,15 @@ export class Client<C = undefined> {
private shouldClose = false
constructor(options: ClientOptions<C>) {
this.opts = defu<Required<ClientOptions<C>>, Required<Omit<ClientOptions<C>, 'name' | 'token'>>[]>(
options,
{
url: 'ws://localhost:6121/ws',
possibleEvents: [],
onError: () => { },
onClose: () => { },
autoConnect: true,
autoReconnect: true,
},
)
this.opts = {
url: 'ws://localhost:6121/ws',
possibleEvents: [],
onError: () => { },
onClose: () => { },
autoConnect: true,
autoReconnect: true,
...options,
}
if (this.opts.autoConnect) {
try {
-1
View File
@@ -78,7 +78,6 @@
"@xsai/stream-text": "catalog:",
"@xsai/tool": "catalog:",
"culori": "^4.0.1",
"defu": "^6.1.4",
"gpuu": "^1.0.4",
"jszip": "^3.10.1",
"localforage": "^1.10.0",
+4 -4
View File
@@ -1,21 +1,21 @@
import type { RealTimeVADOptions } from '@ricky0123/vad-web'
import type { MaybeRef } from '@vueuse/shared'
import { merge } from '@moeru/std'
import { getDefaultRealTimeVADOptions, MicVAD } from '@ricky0123/vad-web'
import { usePermission } from '@vueuse/core'
import { tryOnMounted } from '@vueuse/shared'
import { defu } from 'defu'
import { onUnmounted, ref, toRef, unref, watch } from 'vue'
export function useMicVAD(deviceId: MaybeRef<ConstrainDOMString | undefined>, options?: Partial<RealTimeVADOptions> & { auto?: boolean }) {
const opts = defu<Partial<RealTimeVADOptions> & { auto?: boolean }, Array<Omit<RealTimeVADOptions, 'stream'> & { auto?: boolean }>>(options ?? {}, {
export function useMicVAD(deviceId: MaybeRef<ConstrainDOMString | undefined>, options: Partial<RealTimeVADOptions> & { auto?: boolean } = {}) {
const opts = merge<Omit<RealTimeVADOptions, 'stream'> & { auto?: boolean }, Partial<RealTimeVADOptions> & { auto?: boolean }>({
...getDefaultRealTimeVADOptions('v5'),
preSpeechPadFrames: 30,
positiveSpeechThreshold: 0.5, // default is 0.5
negativeSpeechThreshold: 0.5 - 0.15, // default is 0.5 - 0.15
minSpeechFrames: 30, // default is 9
auto: true,
})
}, options)
const micVad = ref<MicVAD>()
const microphoneAccess = usePermission('microphone')
+3 -3
View File
@@ -1,7 +1,7 @@
import type { MessageEvents, MessageGenerate, ProgressMessageEvents } from '../libs/workers/types'
import { merge } from '@moeru/std'
import { useWebWorker } from '@vueuse/core'
import { defu } from 'defu'
import { onUnmounted, ref, watch } from 'vue'
export interface UseWhisperOptions {
@@ -16,7 +16,7 @@ export interface UseWhisperOptions {
}
export function useWhisper(url: string, options?: Partial<UseWhisperOptions>) {
const opts = defu<Partial<UseWhisperOptions>, UseWhisperOptions[]>(options, {
const opts = merge<UseWhisperOptions>({
onLoading: () => {},
onInitiate: () => {},
onProgress: () => {},
@@ -25,7 +25,7 @@ export function useWhisper(url: string, options?: Partial<UseWhisperOptions>) {
onStart: () => {},
onUpdate: () => {},
onComplete: () => {},
})
}, options)
const {
post: whisperPost,
+2 -2
View File
@@ -3,13 +3,13 @@ import type { Message, SystemMessage } from '@xsai/shared-chat'
import type { ChatAssistantMessage, ChatMessage, ChatSlices } from '../types/chat'
import { readableStreamToAsyncIterator } from '@moeru/std'
import { defineStore, storeToRefs } from 'pinia'
import { ref, toRaw } from 'vue'
import { useQueue } from '../composables'
import { useLlmmarkerParser } from '../composables/llmmarkerParser'
import { useLLM } from '../stores/llm'
import { asyncIteratorFromReadableStream } from '../utils'
import { useAiriCardStore } from './modules'
export interface ErrorMessage {
@@ -175,7 +175,7 @@ export const useChatStore = defineStore('chat', () => {
let fullText = ''
for await (const textPart of asyncIteratorFromReadableStream(res.textStream, async v => v)) {
for await (const textPart of readableStreamToAsyncIterator(res.textStream, async v => v)) {
slicesQueue.add({
type: 'text',
text: textPart,
-1
View File
@@ -1,2 +1 @@
export * from './eye-motions'
export * from './iterator'
-18
View File
@@ -1,18 +0,0 @@
export async function* asyncIteratorFromReadableStream<T, F = Uint8Array>(res: ReadableStream<F>, func: (value: F) => Promise<T>): AsyncGenerator<T, void, unknown> {
// react js - TS2504: Type 'ReadableStream<Uint8Array>' must have a '[Symbol.asyncIterator]()' method that returns an async iterator - Stack Overflow
// https://stackoverflow.com/questions/76700924/ts2504-type-readablestreamuint8array-must-have-a-symbol-asynciterator
const reader = res.getReader()
try {
while (true) {
const { done, value } = await reader.read()
if (done) {
return
}
yield func(value)
}
}
finally {
reader.releaseLock()
}
}
+4 -19
View File
@@ -7,7 +7,7 @@ settings:
catalogs:
default:
'@moeru/std':
specifier: ^0.1.0-beta.4
specifier: 0.1.0-beta.4
version: 0.1.0-beta.4
'@proj-airi/drizzle-duckdb-wasm':
specifier: ^0.4.28
@@ -230,9 +230,6 @@ importers:
'@xsai/stream-text':
specifier: 'catalog:'
version: 0.3.0-beta.5
defu:
specifier: ^6.1.4
version: 6.1.4
es-toolkit:
specifier: ^1.39.4
version: 1.39.4
@@ -354,9 +351,6 @@ importers:
culori:
specifier: ^4.0.1
version: 4.0.1
defu:
specifier: ^6.1.4
version: 6.1.4
drizzle-kit:
specifier: ^0.31.2
version: 0.31.2
@@ -643,9 +637,6 @@ importers:
culori:
specifier: ^4.0.1
version: 4.0.1
defu:
specifier: ^6.1.4
version: 6.1.4
driver.js:
specifier: ^1.3.6
version: 1.3.6
@@ -897,9 +888,6 @@ importers:
crossws:
specifier: ^0.4.1
version: 0.4.1
defu:
specifier: ^6.1.4
version: 6.1.4
h3:
specifier: ^1.15.3
version: 1.15.3
@@ -1056,9 +1044,6 @@ importers:
culori:
specifier: ^4.0.1
version: 4.0.1
defu:
specifier: ^6.1.4
version: 6.1.4
gpuu:
specifier: ^1.0.4
version: 1.0.4
@@ -1492,12 +1477,12 @@ importers:
'@modelcontextprotocol/sdk':
specifier: ^1.13.1
version: 1.13.1
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.4
'@proj-airi/server-sdk':
specifier: ^0.6.1
version: 0.6.1
defu:
specifier: ^6.1.4
version: 6.1.4
dotenv:
specifier: ^16.5.0
version: 16.5.0
+15 -15
View File
@@ -7,22 +7,22 @@ packages:
- '!**/dist/**'
catalog:
'@moeru/std': ^0.1.0-beta.4
'@moeru/std': 0.1.0-beta.4
'@proj-airi/drizzle-duckdb-wasm': ^0.4.28
'@xsai-ext/providers-cloud': &xsai ^0.3.0-beta.5
'@xsai-ext/providers-local': *xsai
'@xsai-ext/shared-providers': *xsai
'@xsai/embed': *xsai
'@xsai/generate-speech': *xsai
'@xsai/generate-text': *xsai
'@xsai/generate-transcription': *xsai
'@xsai/model': *xsai
'@xsai/shared': *xsai
'@xsai/shared-chat': *xsai
'@xsai/stream-text': *xsai
'@xsai/tool': *xsai
'@xsai/utils-chat': *xsai
xsschema: *xsai
'@xsai-ext/providers-cloud': ^0.3.0-beta.5
'@xsai-ext/providers-local': ^0.3.0-beta.5
'@xsai-ext/shared-providers': ^0.3.0-beta.5
'@xsai/embed': ^0.3.0-beta.5
'@xsai/generate-speech': ^0.3.0-beta.5
'@xsai/generate-text': ^0.3.0-beta.5
'@xsai/generate-transcription': ^0.3.0-beta.5
'@xsai/model': ^0.3.0-beta.5
'@xsai/shared': ^0.3.0-beta.5
'@xsai/shared-chat': ^0.3.0-beta.5
'@xsai/stream-text': ^0.3.0-beta.5
'@xsai/tool': ^0.3.0-beta.5
'@xsai/utils-chat': ^0.3.0-beta.5
xsschema: ^0.3.0-beta.5
catalogs:
rolldown-vite:
+6 -7
View File
@@ -2,11 +2,11 @@ import type { Agent, Neuri } from 'neuri'
import type { ChatHistory } from './types'
import { withRetry } from '@moeru/std'
import { agent } from 'neuri'
import { system, user } from 'neuri/openai'
import { config as appConfig } from '../../composables/config'
import { toRetriable } from '../../utils/helper'
import { useLogger } from '../../utils/logger'
import { generateChatAgentPrompt } from './adapter'
@@ -56,13 +56,12 @@ export async function generateChatResponse(
return content
}
const retriableHandler = toRetriable<any, string>(
config.retryLimit ?? 3,
config.delayInterval ?? 1000,
handleCompletion,
)
const retryHandler = withRetry<any, string>(handleCompletion, {
retry: config.retryLimit ?? 3,
retryDelay: config.delayInterval ?? 1000,
})
return await retriableHandler(c)
return await retryHandler(c)
})
if (!content) {
@@ -3,9 +3,9 @@ import type { Neuri, NeuriContext } from 'neuri'
import type { Logger } from '../../utils/logger'
import type { MineflayerWithAgents } from './types'
import { withRetry } from '@moeru/std'
import { system, user } from 'neuri/openai'
import { toRetriable } from '../../utils/helper'
import { handleLLMCompletion } from './completion'
import { generateStatusPrompt } from './prompt'
@@ -29,11 +29,13 @@ export async function handleChatMessage(username: string, message: string, bot:
[...bot.memory.chatHistory, system(statusPrompt)],
async (c: NeuriContext) => {
logger.log('handling response...')
return toRetriable<NeuriContext, string>(
3,
1000,
return withRetry<NeuriContext, string>(
ctx => handleLLMCompletion(ctx, bot, logger),
{ onError: err => logger.withError(err).log('error occurred') },
{
retry: 3,
retryDelay: 1000,
onError: err => logger.withError(err).log('error occurred'),
},
)(c)
},
)
@@ -4,8 +4,9 @@ import type { ChatCompletion, Message } from 'neuri/openai'
import type { Logger } from '../../utils/logger'
import type { LLMConfig, LLMResponse } from './types'
import { withRetry } from '@moeru/std'
import { config } from '../../composables/config'
import { toRetriable } from '../../utils/helper'
import { useLogger } from '../../utils/logger'
export abstract class BaseLLMHandler {
@@ -39,10 +40,9 @@ export abstract class BaseLLMHandler {
}
protected createRetryHandler<T>(handler: (context: NeuriContext) => Promise<T>) {
return toRetriable<NeuriContext, T>(
this.config.retryLimit ?? 3,
this.config.delayInterval ?? 1000,
handler,
)
return withRetry<NeuriContext, T>(handler, {
retry: this.config.retryLimit ?? 3,
retryDelay: this.config.delayInterval ?? 1000,
})
}
}
@@ -3,9 +3,9 @@ import type { Neuri, NeuriContext } from 'neuri'
import type { Logger } from '../../utils/logger'
import type { MineflayerWithAgents } from './types'
import { withRetry } from '@moeru/std'
import { system, user } from 'neuri/openai'
import { toRetriable } from '../../utils/helper'
import { handleLLMCompletion } from './completion'
import { generateStatusPrompt } from './prompt'
@@ -29,10 +29,12 @@ export async function handleVoiceInput(event: any, bot: MineflayerWithAgents, ag
logger.log('Plan executed successfully')
// Generate response
const retryHandler = toRetriable<NeuriContext, string>(
3,
1000,
const retryHandler = withRetry<NeuriContext, string>(
ctx => handleLLMCompletion(ctx, bot, logger),
{
retry: 3,
retryDelay: 1000,
},
)
const content = await agent.handleStateless(
-39
View File
@@ -1,39 +0,0 @@
import { sleep } from '@moeru/std'
/**
* Returns a retirable anonymous function with configured retryLimit and delayInterval
*
* @param retryLimit Number of retry attempts
* @param delayInterval Delay between retries in milliseconds
* @param func Function to be called
* @returns A wrapped function with the same signature as func
*/
export function toRetriable<A, R>(
retryLimit: number,
delayInterval: number,
func: (...args: A[]) => Promise<R>,
hooks?: {
onError?: (err: unknown) => void
},
): (...args: A[]) => Promise<R> {
let retryCount = 0
return async function (args: A): Promise<R> {
try {
return await func(args)
}
catch (err) {
if (hooks?.onError) {
hooks.onError(err)
}
if (retryCount < retryLimit) {
retryCount++
await sleep(delayInterval)
return await toRetriable(retryLimit - retryCount, delayInterval, func)(args)
}
else {
throw err
}
}
}
}
+1 -1
View File
@@ -15,8 +15,8 @@
"@browserbasehq/stagehand": "^2.3.1",
"@guiiai/logg": "^1.0.10",
"@modelcontextprotocol/sdk": "^1.13.1",
"@moeru/std": "catalog:",
"@proj-airi/server-sdk": "^0.6.1",
"defu": "^6.1.4",
"dotenv": "^16.5.0",
"h3": "^1.15.3",
"listhen": "^1.9.0",
@@ -4,7 +4,7 @@ import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { defu } from 'defu'
import { merge } from '@moeru/std'
import { config as configDotenv } from 'dotenv'
import { logger } from '../utils/logger'
@@ -72,7 +72,7 @@ export class ConfigManager {
// Use defu to deeply merge configurations
// Values in fileConfig take precedence over this.config
this.config = defu(fileConfig, this.config)
this.config = merge(this.config, fileConfig)
logger.config.log(`Configuration loaded from ${filePath}`)
}
@@ -93,7 +93,7 @@ export class ConfigManager {
*/
updateConfig(newConfig: Partial<Config>): void {
// Use defu to merge new configuration
this.config = defu(newConfig, this.config)
this.config = merge(this.config, newConfig)
}
}