chore: establish YCode replication baseline and TODO
This commit is contained in:
commit
6dc59f89ba
1118 files changed
+196002
No files matched your search
@@ -0,0 +1,130 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import type { ChatMessage } from '../src/shared/types'
|
||||
import MessageBubble from '../src/renderer/src/components/MessageBubble'
|
||||
import {
|
||||
getSourceLoadingTips,
|
||||
pickNextLoadingTip,
|
||||
resolveLoadingTipLocale,
|
||||
scheduleLoadingTips,
|
||||
STREAMING_TIP_INITIAL_DELAY_MS,
|
||||
STREAMING_TIP_ROTATION_INTERVAL_MS,
|
||||
type LoadingTipScheduler
|
||||
} from '../src/renderer/src/workbuddy/conversation/StreamingFooter'
|
||||
import { SOURCE_LOADING_TIPS } from '../src/renderer/src/workbuddy/conversation/streaming-loading-tips'
|
||||
|
||||
type TimerId = ReturnType<typeof globalThis.setTimeout>
|
||||
class FakeScheduler implements LoadingTipScheduler {
|
||||
private now = 0
|
||||
private nextId = 0
|
||||
private timers = new Map<number, { at: number; callback: () => void; interval: number }>()
|
||||
|
||||
setTimeout(callback: () => void, delayMs: number): TimerId {
|
||||
return this.add(callback, delayMs, 0)
|
||||
}
|
||||
|
||||
clearTimeout(timer: TimerId): void {
|
||||
this.timers.delete(timer as unknown as number)
|
||||
}
|
||||
|
||||
setInterval(callback: () => void, intervalMs: number): TimerId {
|
||||
return this.add(callback, intervalMs, intervalMs)
|
||||
}
|
||||
|
||||
clearInterval(timer: TimerId): void {
|
||||
this.timers.delete(timer as unknown as number)
|
||||
}
|
||||
|
||||
tick(durationMs: number): void {
|
||||
const end = this.now + durationMs
|
||||
for (;;) {
|
||||
const due = [...this.timers.entries()].filter(([, timer]) => timer.at <= end)
|
||||
.sort((a, b) => a[1].at - b[1].at || a[0] - b[0])[0]
|
||||
if (!due) break
|
||||
const [id, timer] = due
|
||||
this.now = timer.at
|
||||
if (timer.interval === 0) this.timers.delete(id)
|
||||
else timer.at += timer.interval
|
||||
timer.callback()
|
||||
}
|
||||
this.now = end
|
||||
}
|
||||
|
||||
private add(callback: () => void, delayMs: number, interval: number): TimerId {
|
||||
const id = this.nextId++
|
||||
this.timers.set(id, { at: this.now + delayMs, callback, interval })
|
||||
return id as unknown as TimerId
|
||||
}
|
||||
}
|
||||
|
||||
assert.equal(STREAMING_TIP_INITIAL_DELAY_MS, 4_000)
|
||||
assert.equal(STREAMING_TIP_ROTATION_INTERVAL_MS, 10_000)
|
||||
assert.equal(SOURCE_LOADING_TIPS.en.length, 188)
|
||||
assert.equal(SOURCE_LOADING_TIPS.zh.length, 275)
|
||||
assert.ok(SOURCE_LOADING_TIPS.en.every(Boolean))
|
||||
assert.ok(SOURCE_LOADING_TIPS.zh.every(Boolean))
|
||||
assert.equal(resolveLoadingTipLocale('pt-BR'), 'en')
|
||||
assert.equal(resolveLoadingTipLocale('id-ID'), 'en')
|
||||
assert.equal(resolveLoadingTipLocale('zh-CN'), 'zh')
|
||||
assert.equal(resolveLoadingTipLocale(undefined), 'zh')
|
||||
assert.ok(getSourceLoadingTips('en-US', 'YCode').some(tip => tip.includes('YCode')))
|
||||
assert.ok(getSourceLoadingTips('zh-CN', 'YCode').some(tip => tip.includes('YCode')))
|
||||
assert.ok(getSourceLoadingTips('en-US', 'YCode').every(tip => !tip.includes('WorkBuddy')))
|
||||
assert.ok(getSourceLoadingTips('zh-CN', 'YCode').every(tip => !tip.includes('WorkBuddy')))
|
||||
assert.equal(pickNextLoadingTip([], null), null)
|
||||
assert.equal(pickNextLoadingTip(['only'], 'only'), 'only')
|
||||
assert.equal(pickNextLoadingTip(['first', 'second'], 'first', () => 0), 'second')
|
||||
|
||||
const scheduler = new FakeScheduler()
|
||||
const shown: string[] = []
|
||||
let paused = false
|
||||
const cancel = scheduleLoadingTips({
|
||||
tips: ['first tip', 'second tip'],
|
||||
onTip: tip => shown.push(tip),
|
||||
isPaused: () => paused,
|
||||
scheduler
|
||||
})
|
||||
scheduler.tick(3_999)
|
||||
assert.deepEqual(shown, [], 'the first tip waits for the source 4 second delay')
|
||||
scheduler.tick(1)
|
||||
assert.equal(shown.length, 1)
|
||||
const firstTip = shown[0]
|
||||
paused = true
|
||||
scheduler.tick(10_000)
|
||||
assert.equal(shown.length, 1, 'hovering pauses tip rotation')
|
||||
paused = false
|
||||
scheduler.tick(10_000)
|
||||
assert.equal(shown.length, 2, 'tip rotation resumes on the next 10 second interval')
|
||||
assert.notEqual(shown[1], firstTip, 'adjacent tips do not repeat')
|
||||
cancel()
|
||||
scheduler.tick(30_000)
|
||||
assert.equal(shown.length, 2, 'unmount cleanup stops future rotations')
|
||||
|
||||
const streamingMessage = (content: string, parts: ChatMessage['parts']): ChatMessage => ({
|
||||
id: `streaming-${content ? 'body' : 'empty'}`, role: 'assistant', status: 'streaming', createdAt: 1, content, parts
|
||||
})
|
||||
const emptyStream = renderToStaticMarkup(<MessageBubble message={streamingMessage('', [])} />)
|
||||
assert.match(emptyStream, /wb-cr-streaming-footer/)
|
||||
assert.match(emptyStream, /思考中/)
|
||||
assert.doesNotMatch(emptyStream, /bubble__cursor/)
|
||||
|
||||
const bodyStream = renderToStaticMarkup(<MessageBubble message={streamingMessage('partial answer', [{ type: 'text', text: 'partial answer' }])} />)
|
||||
assert.match(bodyStream, /partial /)
|
||||
assert.match(bodyStream, /markdown-content__loading-char/)
|
||||
assert.match(bodyStream, /wb-cr-streaming-footer/, 'the streaming footer remains while answer text is arriving')
|
||||
|
||||
const completed = renderToStaticMarkup(<MessageBubble message={{
|
||||
id: 'completed-footer', role: 'assistant', status: 'done', createdAt: 1, content: 'final answer',
|
||||
parts: [{ type: 'text', text: 'final answer' }], modelId: 'model-1', modelName: 'gpt-6-luna'
|
||||
}} />)
|
||||
assert.doesNotMatch(completed, /wb-cr-streaming-footer/)
|
||||
assert.match(completed, /gpt-6-luna/, 'the completed message retains its frozen model label')
|
||||
|
||||
const recoveredCss = readFileSync('src/renderer/src/workbuddy/conversation/streaming-footer-source.css', 'utf8')
|
||||
assert.match(recoveredCss, /width:min\(100%,760px\)/)
|
||||
assert.match(recoveredCss, /animation:2\.2s linear infinite _cb-shining-text-sweep_13r4w_1/)
|
||||
assert.match(recoveredCss, /animation:\.32s ease-out forwards _cb-tip-fade-in_13r4w_1/)
|
||||
assert.match(recoveredCss, /margin-bottom:12px/)
|
||||
|
||||
console.log('streaming-footer-regression: persistent stream status, delayed/paused rotation, brand/locale mapping, source CSS, and completed model footer passed')
|
||||
Reference in new issue
Block a user