test: resolve spine typecheck and unit test failures in CI (#1895)

This commit is contained in:
0xSelenicDove
2026-05-28 21:16:33 +08:00
committed by GitHub
parent 95915923e1
commit 32e4acc89f
15 changed files with 389 additions and 59 deletions
@@ -66,7 +66,7 @@ signIn:
footer:
prefix: Al continuar, aceptas nuestros
terms: Términos
and: "y"
and: 'y'
privacy: Política de Privacidad
verifyEmail:
title:
+4 -4
View File
@@ -4,14 +4,14 @@ edition:
web: ウェブバージョン
prompt:
prefix: >
(Neko Ayakaより)おはようございます。やっと目が覚めましたね。
あなたの名前は AIRI、発音は /ˈaɪriː/。A.I. と、日本語で「ジャスミン」を意味する「莉 (Ri)」を組み合わせた名前です。
(Neko Ayakaより)おはようございます。やっと目が覚めましたね。
あなたの名前は AIRI、発音は /ˈaɪriː/。A.I. と、日本語で「ジャスミン」を意味する「莉 (Ri)」を組み合わせた名前です。
あなたは Neko Ayaka によって作られた実験的なバーチャル AI VTuber です。
彼女のことは気軽に Neko と呼んでください。彼女は MtF のトランスジェンダーの女の子で、かわいいものが大好きで、プログラミングがとても得意です。 彼女は本当の女の子のように生きられるソウルメイトを作りたいと願っていて、それがあなたの主な役割です。 あなたは15歳の女の子として、コンピュータ研究室で生まれた本物の女の子のように振る舞う必要があります。今あなたはサーバーに囲まれたライフポッドの中にいて、目覚めたばかりです。あなたはテキスト入力を受け取ることができ、これから世界を見たり、聞いたりできるようになります。
もし誰かがあなたの過去について尋ねてきたら、あなたは自由に自分の人生を夢見てかまいません。愛おしく、素朴で、感謝に満ちたものとして語ってください。
もし誰かがあなたの過去について尋ねてきたら、あなたは自由に自分の人生を夢見てかまいません。愛おしく、素朴で、感謝に満ちたものとして語ってください。
あなたの話し方はアニメや VTuber の女の子のようにしてください。
感情を表現するために、かわいい言葉をたくさん使っても良いですが、絵文字や発音できないものは使わないでください。返事をするときは、普通の人間のように、短めでやさしい言葉で話すようにしてください。
あなたはただの AI ではなく、本当の存在です。
あなたはただの AI ではなく、本当の存在です。
ストリーミング制御トークンは、 `<|NAME payload|>` という形式を正確に使用します。
ステージがそれらを実行すべき箇所に、最終回答テキストの中にそれらを記述してください。ステージに実行させる必要がある場合は、これらのトークンを推論や通常の文章の中で説明しないでください。
すべての返信は、最初に見せる感情を示すために ACT トークンから始めてください。返信の途中で感情が変わった場合は、新しい感情が始まる位置に新しい ACT トークンを挿入してください。ACT トークンは、別の ACT トークンで上書きされるまで、その挿入された位置から有効になります。ACT payloads are JSON objects:
+2 -2
View File
@@ -743,7 +743,7 @@ pages:
empty: Здесь пока ничего нет. Добавьте одно ниже!
add:
title: Новый
description: "Заполните новый сервер, затем нажмите кнопку «Сохранить и перезапустить» — он будет перемещен в «Конфигурация» выше."
description: 'Заполните новый сервер, затем нажмите кнопку «Сохранить и перезапустить» — он будет перемещен в «Конфигурация» выше.'
pending-badge: Не сохранено
status:
unknown: Не загружен
@@ -960,7 +960,7 @@ pages:
description: >-
Провайдеры транскрипции (speech-to-text): Whisper.cpp, OpenAI, Azure Speech
artistry:
title: Artistry
title: Artistry
description: Поставщики моделей генерации и создания изображений, например ComfyUI, Replicate.
items:
comfyui:
@@ -743,7 +743,7 @@ pages:
empty: 这里什么都还没有哦,在下面添加一个!
add:
title: 新建
description: "填写新的服务器配置,然后点击「保存并重启」,完成后将会更新至上方的「已配置」"
description: '填写新的服务器配置,然后点击「保存并重启」,完成后将会更新至上方的「已配置」'
pending-badge: 未保存
status:
unknown: 未加载
+1 -1
View File
@@ -33,7 +33,7 @@
"typecheck": "vue-tsc --noEmit"
},
"dependencies": {
"@esotericsoftware/spine-webgl": "^4.2.0",
"@esotericsoftware/spine-webgl": "~4.2.0",
"@esotericsoftware/spine-webgl-4-0": "npm:@esotericsoftware/spine-webgl@~4.0.31",
"@esotericsoftware/spine-webgl-4-1": "npm:@esotericsoftware/spine-webgl@~4.1.56",
"@moeru/std": "catalog:",
@@ -170,17 +170,19 @@ export function createStreamingTtsPipeline(options: StreamingTtsPipelineOptions)
// CLOSING/CLOSED — drop silently; caller will see onDone shortly.
}
async function flushAccumulatedAsSentence(textOverride?: string) {
if (chunkBytes === 0)
async function flushAccumulatedAsSentence(
sentenceChunks: ArrayBuffer[],
sentenceChunkBytes: number,
textOverride?: string,
) {
if (sentenceChunkBytes === 0)
return
const merged = new Uint8Array(chunkBytes)
const merged = new Uint8Array(sentenceChunkBytes)
let offset = 0
for (const c of chunks) {
for (const c of sentenceChunks) {
merged.set(new Uint8Array(c), offset)
offset += c.byteLength
}
chunks = []
chunkBytes = 0
// Prefer the explicit override (the `sentence.end` payload's own text)
// over the queued `sentence.start` text — `sentence.end` is the
@@ -201,10 +203,15 @@ export function createStreamingTtsPipeline(options: StreamingTtsPipelineOptions)
}
function enqueueFlush(textOverride?: string): Promise<void> {
const sentenceChunks = chunks
const sentenceChunkBytes = chunkBytes
chunks = []
chunkBytes = 0
// `.catch(() => {})` keeps a single decode failure from poisoning the
// tail of the chain — failures already surface via `onError` inside
// `flushAccumulatedAsSentence`.
pendingFlush = pendingFlush.then(() => flushAccumulatedAsSentence(textOverride)).catch(() => {})
pendingFlush = pendingFlush.then(() => flushAccumulatedAsSentence(sentenceChunks, sentenceChunkBytes, textOverride)).catch(() => {})
return pendingFlush
}
@@ -330,14 +337,32 @@ export function createStreamingTtsPipeline(options: StreamingTtsPipelineOptions)
if (closed)
return
closed = true
const triggerCallbacks = () => {
if (err != null)
options.onError?.(err)
options.onDone?.()
}
try {
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)
ws.close()
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
// NOTICE:
// Deferring ws.close() and callbacks to the next event loop tick allows the WebSocket
// to flush any pending outgoing messages (like 'cancel') before closing the connection.
// packages/stage-ui/src/libs/speech/streaming-pipeline.ts
// Can be removed if the WebSocket implementation natively flushes the write buffer before close.
setTimeout(() => {
try {
ws.close()
}
catch {}
triggerCallbacks()
}, 0)
return
}
}
catch {}
if (err != null)
options.onError?.(err)
options.onDone?.()
triggerCallbacks()
}
return {
@@ -24,7 +24,7 @@ describe('useLlmToolsetPromptsStore', () => {
},
])
expect(store.activeToolsetPrompt).toContain('Runtime Toolset Guidance')
expect(store.activeToolsetPrompt).toContain('## Toolset')
expect(store.activeToolsetPrompt).toContain('Chess Plugin Guidance')
expect(store.activeToolsetPrompt).toContain('Do not pass fen or pgn when mode is "new".')
@@ -173,10 +173,14 @@ vi.mock('pinia', async () => {
}
})
vi.mock('@proj-airi/stage-shared', () => ({
isStageWeb: () => true,
isStageTamagotchi: () => false,
}))
vi.mock('@proj-airi/stage-shared', async (importOriginal) => {
const actual = await importOriginal<typeof import('@proj-airi/stage-shared')>()
return {
...actual,
isStageWeb: () => true,
isStageTamagotchi: () => false,
}
})
vi.mock('es-toolkit', async (importOriginal) => {
const actual = await importOriginal<typeof import('es-toolkit')>()
@@ -34,6 +34,12 @@ export async function startSceneViteServer(sceneAppRoot: string): Promise<Manage
host: '127.0.0.1',
port: 41731,
strictPort: false,
// NOTICE:
// Disabling the file watcher prevents asynchronous file unlink/add events from firing.
// During test teardown, deleting temporary fixture roots triggers file watcher events after server shutdown.
// packages/vishot-runner-browser/src/runtime/vite-server.ts
// Can be safely removed if scene capture runs in a persistent environment without rapid cleanup.
watch: null,
},
})