release v0.4.0
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "waoowaoo",
|
"name": "waoowaoo",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.0",
|
"node": ">=18.18.0",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { ModelCapabilityDropdown } from '@/components/ui/config-modals/ModelCapa
|
|||||||
import { AppIcon } from '@/components/ui/icons'
|
import { AppIcon } from '@/components/ui/icons'
|
||||||
import { readConfiguredAnalysisModel, shouldGuideToModelSetup } from '@/lib/workspace/model-setup'
|
import { readConfiguredAnalysisModel, shouldGuideToModelSetup } from '@/lib/workspace/model-setup'
|
||||||
import { useRouter } from '@/i18n/navigation'
|
import { useRouter } from '@/i18n/navigation'
|
||||||
|
import { readApiErrorMessage } from '@/lib/api/read-error-message'
|
||||||
|
|
||||||
// 有效的stage值
|
// 有效的stage值
|
||||||
const VALID_STAGES = ['config', 'script', 'assets', 'text-storyboard', 'storyboard', 'videos', 'voice', 'editor'] as const
|
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) {
|
if (!res.ok) {
|
||||||
const data = await res.json()
|
throw new Error(await readApiErrorMessage(res, t('createFailed')))
|
||||||
throw new Error(data.error || t('createFailed'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ export default function StoryInputComposer({
|
|||||||
options={styleOptions}
|
options={styleOptions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{stylePresetOptions.length > 0 ? (
|
||||||
<div className="w-[152px] flex-shrink-0">
|
<div className="w-[152px] flex-shrink-0">
|
||||||
<StylePresetSelector
|
<StylePresetSelector
|
||||||
value={stylePresetValue}
|
value={stylePresetValue}
|
||||||
@@ -153,6 +154,7 @@ export default function StoryInputComposer({
|
|||||||
options={stylePresetOptions}
|
options={stylePresetOptions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto flex min-w-max items-center gap-2">
|
<div className="ml-auto flex min-w-max items-center gap-2">
|
||||||
{secondaryActions}
|
{secondaryActions}
|
||||||
|
|||||||
@@ -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',
|
value: 'horror-suspense',
|
||||||
label: '恐怖悬疑',
|
label: '恐怖悬疑',
|
||||||
description: '压迫氛围',
|
description: '压迫氛围',
|
||||||
|
enabled: false,
|
||||||
},
|
},
|
||||||
] as const
|
]
|
||||||
|
|
||||||
export type StylePresetOption = (typeof STYLE_PRESETS)[number]
|
export const STYLE_PRESETS: readonly StylePresetOption[] = ALL_STYLE_PRESETS.filter(
|
||||||
export type StylePresetValue = StylePresetOption['value']
|
(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 {
|
export function getStylePresetOption(value: string): StylePresetOption | null {
|
||||||
return STYLE_PRESETS.find((preset) => preset.value === value) ?? STYLE_PRESETS[0]
|
return STYLE_PRESETS.find((preset) => preset.value === value) ?? STYLE_PRESETS[0] ?? null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,4 +48,31 @@ describe('StoryInputComposer', () => {
|
|||||||
expect(html).toContain('AI 帮我写')
|
expect(html).toContain('AI 帮我写')
|
||||||
expect(html).toContain('开始创作')
|
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')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
29
tests/unit/helpers/read-error-message.test.ts
Normal file
29
tests/unit/helpers/read-error-message.test.ts
Normal 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('创建失败')
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user