update
This commit is contained in:
@@ -86,6 +86,68 @@ describe('generate route', () => {
|
||||
expect(res.status).toBe(502)
|
||||
})
|
||||
|
||||
it('returns 400 when theme is missing on outline', async () => {
|
||||
const app = new Hono().route('/api/generate', createGenerateRouter('test-key'))
|
||||
|
||||
const res = await app.request('/api/generate/outline', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({}),
|
||||
})
|
||||
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('uses the requested count in the outline prompt', async () => {
|
||||
let sentBody = ''
|
||||
globalThis.fetch = mock(async (_url: unknown, init: { body?: string } = {}) => {
|
||||
sentBody = init.body ?? ''
|
||||
return new Response(
|
||||
JSON.stringify({ choices: [{ message: { content: 'A\nB\nC' } }] }),
|
||||
{ status: 200 },
|
||||
)
|
||||
}) as unknown as typeof fetch
|
||||
|
||||
const app = new Hono().route('/api/generate', createGenerateRouter('test-key'))
|
||||
const res = await app.request('/api/generate/outline', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ theme: 'Web 前端', count: 25 }),
|
||||
})
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(sentBody).toContain('共约25条')
|
||||
const body = (await res.json()) as { titles: string[] }
|
||||
expect(body.titles).toEqual(['A', 'B', 'C'])
|
||||
})
|
||||
|
||||
it('defaults the outline count to 18 and clamps out-of-range values', async () => {
|
||||
const captured: string[] = []
|
||||
globalThis.fetch = mock(async (_url: unknown, init: { body?: string } = {}) => {
|
||||
captured.push(init.body ?? '')
|
||||
return new Response(
|
||||
JSON.stringify({ choices: [{ message: { content: 'A' } }] }),
|
||||
{ status: 200 },
|
||||
)
|
||||
}) as unknown as typeof fetch
|
||||
|
||||
const app = new Hono().route('/api/generate', createGenerateRouter('test-key'))
|
||||
|
||||
await app.request('/api/generate/outline', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ theme: 'Web 前端' }),
|
||||
})
|
||||
await app.request('/api/generate/outline', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ theme: 'Web 前端', count: 999 }),
|
||||
})
|
||||
|
||||
expect(captured[0]).toContain('共约18条')
|
||||
expect(captured[1]).toContain('共约50条')
|
||||
})
|
||||
|
||||
it('returns 502 when Deepseek response has no content', async () => {
|
||||
globalThis.fetch = mock(async () => new Response(JSON.stringify({ choices: [] }), { status: 200 })) as unknown as typeof fetch
|
||||
|
||||
|
||||
@@ -90,13 +90,18 @@ export function createGenerateRouter(apiKey: string | undefined): Hono {
|
||||
})
|
||||
|
||||
app.post('/outline', async (c) => {
|
||||
const body = (await c.req.json().catch(() => null)) as { theme?: unknown } | null
|
||||
const body = (await c.req.json().catch(() => null)) as
|
||||
| { theme?: unknown; count?: unknown }
|
||||
| null
|
||||
const theme = body?.theme
|
||||
|
||||
if (typeof theme !== 'string' || theme.trim() === '') {
|
||||
return c.json({ error: '请提供课程主题。' }, 400)
|
||||
}
|
||||
|
||||
const rawCount = typeof body?.count === 'number' ? body.count : 18
|
||||
const count = Math.min(50, Math.max(1, Math.round(rawCount)))
|
||||
|
||||
if (!apiKey) {
|
||||
return c.json({ error: '未配置 DEEPSEEK_API_KEY。' }, 500)
|
||||
}
|
||||
@@ -115,7 +120,7 @@ export function createGenerateRouter(apiKey: string | undefined): Hono {
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'你是教学设计专家。根据用户提供的课程主题,生成一份完整的课时大纲标题列表,共约18条。' +
|
||||
`你是教学设计专家。根据用户提供的课程主题,生成一份完整的课时大纲标题列表,共约${count}条。` +
|
||||
'每个标题格式为"项目名——课时任务",一行一个,不加序号、不加任何说明,直接输出标题列表。',
|
||||
},
|
||||
{ role: 'user', content: `课程主题:${theme.trim()}` },
|
||||
|
||||
@@ -21,6 +21,7 @@ const emit = defineEmits<{
|
||||
|
||||
const phase = ref<Phase>('theme')
|
||||
const theme = ref(props.defaultTheme ?? '')
|
||||
const count = ref(18)
|
||||
const outlineText = ref('')
|
||||
const outlineError = ref<string | null>(null)
|
||||
|
||||
@@ -45,7 +46,7 @@ async function handleGenerateOutline(): Promise<void> {
|
||||
phase.value = 'outline-loading'
|
||||
outlineError.value = null
|
||||
try {
|
||||
const result = await booksApi.generateOutline(theme.value.trim())
|
||||
const result = await booksApi.generateOutline(theme.value.trim(), count.value)
|
||||
outlineText.value = result.titles.join('\n')
|
||||
phase.value = 'outline'
|
||||
} catch (error) {
|
||||
@@ -63,6 +64,7 @@ function handleStart(): void {
|
||||
function handleClose(): void {
|
||||
phase.value = 'theme'
|
||||
theme.value = props.defaultTheme ?? ''
|
||||
count.value = 18
|
||||
outlineText.value = ''
|
||||
outlineError.value = null
|
||||
emit('close')
|
||||
@@ -84,6 +86,10 @@ function handleClose(): void {
|
||||
placeholder="例如:Web 前端开发项目式教学"
|
||||
@keydown.enter="handleGenerateOutline"
|
||||
/>
|
||||
<label class="batch-count-field">
|
||||
生成数量
|
||||
<input v-model.number="count" type="number" min="1" max="50" />
|
||||
</label>
|
||||
<div class="dialog-actions">
|
||||
<button type="button" :disabled="!theme.trim()" @click="handleGenerateOutline">生成大纲</button>
|
||||
<button type="button" @click="handleClose">取消</button>
|
||||
|
||||
@@ -55,6 +55,9 @@ export function generateLesson(topic: string, signal?: AbortSignal): Promise<Gen
|
||||
return authedFetch('/api/generate', { method: 'POST', body: JSON.stringify({ topic }), signal })
|
||||
}
|
||||
|
||||
export function generateOutline(theme: string): Promise<{ titles: string[] }> {
|
||||
return authedFetch('/api/generate/outline', { method: 'POST', body: JSON.stringify({ theme }) })
|
||||
export function generateOutline(theme: string, count = 18): Promise<{ titles: string[] }> {
|
||||
return authedFetch('/api/generate/outline', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ theme, count }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -849,6 +849,20 @@ table {
|
||||
border-color: var(--green-600);
|
||||
}
|
||||
|
||||
.batch-count-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.batch-count-field input {
|
||||
flex: 0 0 auto;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.batch-topics-count {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
|
||||
Reference in New Issue
Block a user