This commit is contained in:
2026-06-15 23:14:16 -06:00
parent 4660d10829
commit 6e1263feac
12 changed files with 719 additions and 25 deletions

View File

@@ -783,7 +783,7 @@ export function createGenerateRouter(apiKey: string | undefined): Hono {
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: 'deepseek-chat',
model: 'deepseek-v4-flash',
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: `请围绕主题"${topic.trim()}"生成一份教案。` },

View File

@@ -0,0 +1,371 @@
# Fix Broken Lessons Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在工作区工具栏新增「修复 X 处提示」按钮,点击后弹出 `FixBrokenDialog`,依次重新生成所有有警告的教案并原位替换,完成后显示摘要。
**Architecture:**`useTeachingBook` 中新增 `regenerateLesson(id)` 用于原位替换单篇;新建 `FixBrokenDialog.vue` 负责进度显示confirm → running → done/error`WorkspaceToolbar` 新增 `fixBroken` emit 与条件显示按钮;`WorkspaceView` 协调状态并驱动循环。
**Tech Stack:** Vue 3 + TypeScript与现有 BatchGenerateDialog 模式保持一致。
---
## File Structure
| 文件 | 操作 |
|---|---|
| `src/composables/useTeachingBook.ts` | 修改:新增 `regenerateLesson`,接口加 `regenerateLesson` |
| `src/components/FixBrokenDialog.vue` | 新建:修复进度对话框 |
| `src/components/WorkspaceToolbar.vue` | 修改:新增 `fixBroken` emit 与按钮 |
| `src/components/WorkspaceView.vue` | 修改:新增 fix 状态与处理逻辑 |
---
## Task 1: `regenerateLesson` in `useTeachingBook.ts`
**Files:**
- Modify: `src/composables/useTeachingBook.ts`
- [ ] **Step 1: 在 `TeachingBookStore` 接口加入 `regenerateLesson`**
`generateLesson` 行下方追加:
```typescript
regenerateLesson: (id: DesignId) => Promise<GenerateLessonResult>
```
- [ ] **Step 2: 实现 `regenerateLesson` 函数**
`generateLesson` 函数后插入(`return {` 之前):
```typescript
async function regenerateLesson(id: DesignId): Promise<GenerateLessonResult> {
const existing = book.value.designs.find((d) => d.id === id)
if (!existing) return { ok: false, message: '找不到该教案。' }
const topic = existing.originalFilename.replace(/\.md$/i, '')
try {
const result = await booksApi.generateLesson(topic)
const newDesign = parseTeachingDesign(result.filename, result.markdown)
const index = book.value.designs.findIndex((d) => d.id === id)
if (index !== -1) {
book.value.designs.splice(index, 1, newDesign)
if (book.value.selectedId === id) {
book.value.selectedId = newDesign.id
}
}
touch()
return { ok: true }
} catch (error) {
return { ok: false, message: error instanceof Error ? error.message : '修复失败。' }
}
}
```
- [ ] **Step 3: 在 `return` 对象中暴露 `regenerateLesson`**
`generateLesson,` 行之后加一行:
```typescript
regenerateLesson,
```
- [ ] **Step 4: 运行测试确认无破坏**
```bash
bun run test -- src/composables/useTeachingBook.test.ts
```
期望:全部通过。
- [ ] **Step 5: Commit**
```bash
git add src/composables/useTeachingBook.ts
git commit -m "feat: add regenerateLesson to useTeachingBook"
```
---
## Task 2: 新建 `FixBrokenDialog.vue`
**Files:**
- Create: `src/components/FixBrokenDialog.vue`
- [ ] **Step 1: 创建组件文件**
内容如下:
```vue
<script setup lang="ts">
import { watch, ref } from 'vue'
type Phase = 'confirm' | 'running' | 'done' | 'error'
const props = defineProps<{
running: boolean
done: number
total: number
currentTopic: string
error: string | null
}>()
const emit = defineEmits<{
start: []
cancel: []
close: []
}>()
const phase = ref<Phase>('confirm')
watch(
() => props.running,
(val) => {
if (val) {
phase.value = 'running'
} else if (phase.value === 'running') {
phase.value = props.error ? 'error' : 'done'
}
},
)
function handleClose(): void {
phase.value = 'confirm'
emit('close')
}
</script>
<template>
<div class="dialog-overlay" role="dialog" aria-modal="true" aria-labelledby="fix-dialog-title">
<div class="dialog">
<h2 id="fix-dialog-title">修复问题教案</h2>
<!-- 确认 -->
<template v-if="phase === 'confirm'">
<p> <strong>{{ total }}</strong> 篇教案存在解析问题点击开始将重新生成并原位替换</p>
<div class="dialog-actions">
<button type="button" @click="emit('start')">开始修复</button>
<button type="button" @click="emit('close')">取消</button>
</div>
</template>
<!-- 修复中 -->
<template v-else-if="phase === 'running'">
<p class="batch-progress-label">
正在修复第 <strong>{{ done + 1 }}</strong> / {{ total }}
</p>
<p class="batch-current-topic">{{ currentTopic }}</p>
<div class="batch-progress-bar">
<div class="batch-progress-fill" :style="{ width: `${(done / total) * 100}%` }" />
</div>
<div class="dialog-actions">
<button type="button" @click="emit('cancel')">停止</button>
</div>
</template>
<!-- 出错 -->
<template v-else-if="phase === 'error'">
<p class="app-notice app-notice--error" role="alert">{{ error }}</p>
<p>已修复 {{ done }} / {{ total }} 修复中止</p>
<div class="dialog-actions">
<button type="button" @click="handleClose">关闭</button>
</div>
</template>
<!-- 完成 -->
<template v-else-if="phase === 'done'">
<p>已修复 <strong>{{ done }}</strong> / {{ total }} 篇教案</p>
<div class="dialog-actions">
<button type="button" @click="handleClose">关闭</button>
</div>
</template>
</div>
</div>
</template>
```
- [ ] **Step 2: Commit**
```bash
git add src/components/FixBrokenDialog.vue
git commit -m "feat: add FixBrokenDialog component"
```
---
## Task 3: WorkspaceToolbar — 新增修复按钮
**Files:**
- Modify: `src/components/WorkspaceToolbar.vue`
- [ ] **Step 1: 新增 `fixBroken` emit**
`defineEmits` 改为:
```typescript
defineEmits<{
upload: []
print: []
export: []
clear: []
generate: []
batchGenerate: []
fixBroken: []
back: []
}>()
```
- [ ] **Step 2: 在 `warningCount` span 前加修复按钮**
把:
```html
<span v-if="warningCount > 0" class="workspace-toolbar-warning">
{{ warningCount }} 处提示
</span>
```
替换为:
```html
<template v-if="warningCount > 0">
<button type="button" data-testid="fix-broken" @click="$emit('fixBroken')">
修复 {{ warningCount }} 处提示
</button>
</template>
```
- [ ] **Step 3: Commit**
```bash
git add src/components/WorkspaceToolbar.vue
git commit -m "feat: add fix-broken button to WorkspaceToolbar"
```
---
## Task 4: WorkspaceView — 协调 fix 流程
**Files:**
- Modify: `src/components/WorkspaceView.vue`
- [ ] **Step 1: import FixBrokenDialog**
在现有 import 列表里加(与 `BatchGenerateDialog` 相邻):
```typescript
import FixBrokenDialog from './FixBrokenDialog.vue'
```
- [ ] **Step 2: 解构 `regenerateLesson`**
`useTeachingBook` 解构对象中加:
```typescript
regenerateLesson,
```
- [ ] **Step 3: 新增 fix 状态 refs**
`batchCancelled` ref 之后插入:
```typescript
const showFixDialog = ref(false)
const fixRunning = ref(false)
const fixDone = ref(0)
const fixTotal = ref(0)
const fixCurrentTopic = ref('')
const fixError = ref<string | null>(null)
const fixCancelled = ref(false)
```
- [ ] **Step 4: 新增 fix 处理函数**
`closeBatchDialog` 函数之后插入:
```typescript
function openFixDialog(): void {
fixTotal.value = book.value.designs.filter((d) => d.warnings.length > 0).length
fixDone.value = 0
fixError.value = null
showFixDialog.value = true
}
async function handleFixStart(): Promise<void> {
const broken = book.value.designs.filter((d) => d.warnings.length > 0)
fixCancelled.value = false
fixRunning.value = true
for (const lesson of broken) {
if (fixCancelled.value) break
fixCurrentTopic.value = lesson.originalFilename.replace(/\.md$/i, '')
const result = await regenerateLesson(lesson.id)
if (!result.ok) {
fixError.value = result.message
break
}
fixDone.value++
}
fixRunning.value = false
}
function handleFixCancel(): void {
fixCancelled.value = true
}
function closeFixDialog(): void {
showFixDialog.value = false
fixDone.value = 0
fixTotal.value = 0
fixError.value = null
}
```
- [ ] **Step 5: 在 template 中挂载 FixBrokenDialog**
`<BatchGenerateDialog ... />` 之后加:
```html
<FixBrokenDialog
v-if="showFixDialog"
:running="fixRunning"
:done="fixDone"
:total="fixTotal"
:current-topic="fixCurrentTopic"
:error="fixError"
@start="handleFixStart"
@cancel="handleFixCancel"
@close="closeFixDialog"
/>
```
- [ ] **Step 6: 在 WorkspaceToolbar 上绑定 `@fix-broken`**
把:
```html
@batch-generate="showBatchDialog = true"
```
改为:
```html
@batch-generate="showBatchDialog = true"
@fix-broken="openFixDialog"
```
- [ ] **Step 7: 运行全量测试**
```bash
bun run test
```
期望:`markdownTable``markdownParser``useTeachingBook``PrintBook` 相关测试全部通过App.test.ts 的 2 个已知失败不影响)。
- [ ] **Step 8: Commit**
```bash
git add src/components/WorkspaceView.vue
git commit -m "feat: wire fix-broken flow in WorkspaceView"
```