release v0.4.0

This commit is contained in:
saturn
2026-04-02 21:44:46 +08:00
parent 71ef6ff818
commit 77a1303510
6 changed files with 84 additions and 17 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "waoowaoo",
"version": "0.3.0",
"version": "0.4.0",
"private": true,
"engines": {
"node": ">=18.18.0",

View File

@@ -18,6 +18,7 @@ import { ModelCapabilityDropdown } from '@/components/ui/config-modals/ModelCapa
import { AppIcon } from '@/components/ui/icons'
import { readConfiguredAnalysisModel, shouldGuideToModelSetup } from '@/lib/workspace/model-setup'
import { useRouter } from '@/i18n/navigation'
import { readApiErrorMessage } from '@/lib/api/read-error-message'
// 有效的stage值
const VALID_STAGES = ['config', 'script', 'assets', 'text-storyboard', 'storyboard', 'videos', 'voice', 'editor'] as const
@@ -209,8 +210,7 @@ export default function ProjectDetailPage() {
})
if (!res.ok) {
const data = await res.json()
throw new Error(data.error || t('createFailed'))
throw new Error(await readApiErrorMessage(res, t('createFailed')))
}
const data = await res.json()

View File

@@ -146,13 +146,15 @@ export default function StoryInputComposer({
options={styleOptions}
/>
</div>
<div className="w-[152px] flex-shrink-0">
<StylePresetSelector
value={stylePresetValue}
onChange={onStylePresetChange}
options={stylePresetOptions}
/>
</div>
{stylePresetOptions.length > 0 ? (
<div className="w-[152px] flex-shrink-0">
<StylePresetSelector
value={stylePresetValue}
onChange={onStylePresetChange}
options={stylePresetOptions}
/>
</div>
) : null}
</div>
<div className="ml-auto flex min-w-max items-center gap-2">
{secondaryActions}

View File

@@ -1,16 +1,25 @@
export const STYLE_PRESETS = [
export interface StylePresetOption {
value: string
label: string
description: string
enabled: boolean
}
const ALL_STYLE_PRESETS: readonly StylePresetOption[] = [
{
value: 'horror-suspense',
label: '恐怖悬疑',
description: '压迫氛围',
enabled: false,
},
] as const
]
export type StylePresetOption = (typeof STYLE_PRESETS)[number]
export type StylePresetValue = StylePresetOption['value']
export const STYLE_PRESETS: readonly StylePresetOption[] = ALL_STYLE_PRESETS.filter(
(preset) => preset.enabled,
)
export const DEFAULT_STYLE_PRESET_VALUE: StylePresetValue = 'horror-suspense'
export const DEFAULT_STYLE_PRESET_VALUE = STYLE_PRESETS[0]?.value ?? ''
export function getStylePresetOption(value: string): StylePresetOption {
return STYLE_PRESETS.find((preset) => preset.value === value) ?? STYLE_PRESETS[0]
export function getStylePresetOption(value: string): StylePresetOption | null {
return STYLE_PRESETS.find((preset) => preset.value === value) ?? STYLE_PRESETS[0] ?? null
}

View File

@@ -48,4 +48,31 @@ describe('StoryInputComposer', () => {
expect(html).toContain('AI 帮我写')
expect(html).toContain('开始创作')
})
it('hides the style preset selector when no preset is enabled', () => {
Reflect.set(globalThis, 'React', React)
const html = renderToStaticMarkup(
createElement(StoryInputComposer, {
value: '测试内容',
onValueChange: () => undefined,
placeholder: '请输入内容',
minRows: 8,
videoRatio: '9:16',
onVideoRatioChange: () => undefined,
ratioOptions: [{ value: '9:16', label: '9:16' }],
artStyle: 'realistic',
onArtStyleChange: () => undefined,
styleOptions: [{ value: 'realistic', label: '真人风格' }],
stylePresetValue: '',
onStylePresetChange: () => undefined,
stylePresetOptions: [],
primaryAction: createElement('button', { type: 'button' }, '开始创作'),
}),
)
expect(html).toContain('RatioSelector')
expect(html).toContain('StyleSelector')
expect(html).not.toContain('StylePresetSelector')
})
})

View File

@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest'
import { readApiErrorMessage } from '@/lib/api/read-error-message'
function buildJsonResponse(body: unknown, status = 400): Response {
return new Response(JSON.stringify(body), {
status,
headers: { 'Content-Type': 'application/json' },
})
}
describe('readApiErrorMessage', () => {
it('returns nested api error message instead of [object Object]', async () => {
const response = buildJsonResponse({
error: {
code: 'INVALID_PARAMS',
message: 'Episode name is required',
},
message: 'Invalid parameters',
})
await expect(readApiErrorMessage(response, '创建失败')).resolves.toBe('Episode name is required')
})
it('falls back when the response body is not json', async () => {
const response = new Response('bad gateway', { status: 502 })
await expect(readApiErrorMessage(response, '创建失败')).resolves.toBe('创建失败')
})
})