Expand AITURK Turkish settings and desktop surfaces for beta 5

This commit is contained in:
2026-09-06 05:03:32 +03:00
parent 237d1e2060
commit 5e76f58637
108 changed files with 6675 additions and 1034 deletions
@@ -0,0 +1,42 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, expect, it } from 'vitest'
import { I18nProvider, useI18n } from '@/i18n'
import { EN_SURFACES } from '@/i18n/en-surfaces'
import { TR_SURFACES } from '@/i18n/tr-surfaces'
import { $petActivity, $petState } from '@/store/pet'
import { PetBubble } from './pet-bubble'
afterEach(() => {
cleanup()
$petActivity.set({})
})
it('changes the visible status language while preserving the actual failure state', () => {
$petActivity.set({ error: true })
function Surface() {
const { setLocale } = useI18n()
return (
<>
<button onClick={() => void setLocale('tr')}>Türkçe</button>
<PetBubble />
</>
)
}
const view = render(
<I18nProvider configClient={null} initialLocale="en">
<Surface />
</I18nProvider>
)
expect(EN_SURFACES.pet.failed.some(line => view.container.textContent?.includes(line))).toBe(true)
fireEvent.click(screen.getByRole('button', { name: 'Türkçe' }))
expect(TR_SURFACES.pet.failed.some(line => view.container.textContent?.includes(line))).toBe(true)
expect(EN_SURFACES.pet.failed.some(line => view.container.textContent?.includes(line))).toBe(false)
expect($petState.get()).toBe('failed')
expect($petActivity.get()).toEqual({ error: true })
})
+18 -45
View File
@@ -1,6 +1,7 @@
import { useStore } from '@nanostores/react'
import { useEffect, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { useI18n } from '@/i18n'
import { AlertCircle, Clock, type IconComponent } from '@/lib/icons'
import { $petActivity, $petState, type PetState } from '@/store/pet'
@@ -27,46 +28,6 @@ interface Spec {
// Phrasings per mood, picked at random (no immediate repeat) for a bit of life.
// Keep them short — the bubble is tiny and never wraps.
const SPECS: Partial<Record<PetState, Spec>> = {
run: {
lines: [
'working…',
'on it…',
'crunching…',
'tinkering…',
'cooking…',
'in the weeds…',
'wiring it up…',
'making moves…',
'heads down…',
'hammering away…'
]
},
review: {
lines: [
'thinking…',
'reading…',
'reviewing…',
'pondering…',
'connecting dots…',
'sizing it up…',
'tracing it…',
'mulling…',
'scheming…',
'hmm…'
]
},
failed: {
glyph: AlertCircle,
lines: ['hit a snag', 'welp', 'that broke', 'oof', 'snagged'],
tone: 'error'
},
waiting: {
glyph: Clock,
lines: ['your turn', 'all yours', 'over to you', 'balls in your court', 'awaiting orders'],
tone: 'wait'
}
}
const TONE_COLOR: Record<Tone, string> = {
error: 'var(--ui-red)',
@@ -89,6 +50,18 @@ function pick(lines: string[], prev: string): string {
}
export function PetBubble() {
const { t } = useI18n()
const specs = useMemo<Partial<Record<PetState, Spec>>>(
() => ({
run: { lines: t.surfaces.pet.run },
review: { lines: t.surfaces.pet.review },
failed: { glyph: AlertCircle, lines: t.surfaces.pet.failed, tone: 'error' },
waiting: { glyph: Clock, lines: t.surfaces.pet.waiting, tone: 'wait' }
}),
[t]
)
const state = useStore($petState)
const activity = useStore($petActivity)
const [line, setLine] = useState('')
@@ -96,14 +69,14 @@ export function PetBubble() {
// Finish beats are carried by the sprite/mail icon; idle only speaks up when
// it's actually the user's turn. Everything else maps to a mood spec.
const specKey: null | PetState =
state in SPECS ? state : state === 'idle' && activity.awaitingInput ? 'waiting' : null
state in specs ? state : state === 'idle' && activity.awaitingInput ? 'waiting' : null
const rotating = specKey === 'run' || specKey === 'review'
// Pick a fresh line on every mood change, then keep rotating (random, no
// repeat) only while the agent is actively working/thinking.
useEffect(() => {
const spec = specKey ? SPECS[specKey] : null
const spec = specKey ? specs[specKey] : null
if (!spec) {
setLine('')
@@ -120,9 +93,9 @@ export function PetBubble() {
const id = window.setInterval(() => setLine(prev => pick(spec.lines, prev)), 2600)
return () => window.clearInterval(id)
}, [specKey, rotating])
}, [specKey, rotating, specs])
const spec = specKey ? SPECS[specKey] : null
const spec = specKey ? specs[specKey] : null
if (!spec) {
return null