Files
YG_FT/docs/superpowers/plans/2026-07-10-data-process-create-wizard.md
wuyongtao a67ca2c19c feat: 添加平台管理、计算模块适配器及前端页面更新
- 新增 platform API 端点和存储
- 新增 llama_factory 适配器
- 新增前端 compute、guide、system 等视图页面
- 新增 echarts 插件和 mock 数据
- 更新 Docker 配置、后端配置及文档
- 更新前端路由、API、侧边栏等组件

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-21 09:23:43 +08:00

459 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Data Process Create Wizard 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:**`/data-process/create` 实现为固定四步向导,并在第二步提供“右侧选择切片、左侧自动定位并高亮原文”的可编辑对照体验。
**Architecture:** `DataProcessCreateView.vue` 只负责向导状态、步骤切换和跨步骤数据;每个步骤拆成独立 Vue 组件。源文定位和切片生成由纯 TypeScript 模块负责,第二步组件只消费偏移范围并同步滚动、高亮和编辑状态。现有 Vue 3、Element Plus、SCSS 和 Font Awesome 继续使用,不引入新依赖。
**Tech Stack:** Vue 3.5、TypeScript 5.7、Vite 6、Element Plus 2.9、SCSS、Node.js 回归脚本、`vue-tsc`
## Global Constraints
- 顶部固定四步:`创建任务``数据预览``开始生成``结果编辑与保存`
- 结构化和非结构化类型不得改变步骤数量。
- 第二步桌面端左侧约 58% 为只读源文件,右侧约 42% 为切片或记录列表及编辑器。
- 点击右侧条目时,左侧必须定位并高亮 `sourceStart``sourceEnd` 的原始范围。
- 编辑切片不得改写源文件;来源映射始终指向初始原文。
- 每一步只能有一个主操作,不得同时出现“下一步”和“开始生成”等竞争动作。
- 页面继续使用现有全局白色页面画布,不新增整页嵌套白卡。
- 不增加第三方依赖。
---
### Task 1: 建立四步向导回归测试
**Files:**
- Create: `frontend/scripts/regression-data-process-wizard.mjs`
- Modify: `frontend/package.json`
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
**Interfaces:**
- Consumes: Vue SFC 源码、`@vue/compiler-sfc``@vue/compiler-dom`
- Produces: `npm run test:data-process-wizard`,验证固定步骤、组件边界、对照定位标记和底部唯一主操作。
- [ ] **Step 1: 写入当前实现必然失败的结构回归检查**
```js
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { parse as parseSfc } from '@vue/compiler-sfc'
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
const viewSource = await readFile(
path.resolve(scriptDir, '../src/views/data-process/DataProcessCreateView.vue'),
'utf8',
)
const previewSource = await readFile(
path.resolve(scriptDir, '../src/views/data-process/create/PreviewCompareStep.vue'),
'utf8',
)
assert.match(viewSource, /const WIZARD_STEPS = \[/)
for (const title of ['创建任务', '数据预览', '开始生成', '结果编辑与保存']) {
assert.ok(viewSource.includes(`title: '${title}'`), `缺少固定步骤:${title}`)
}
assert.doesNotMatch(viewSource, /all\.filter|steps\s*=\s*computed/)
assert.match(previewSource, /class="source-viewer"/)
assert.match(previewSource, /class="preview-workspace"/)
assert.match(previewSource, /scrollIntoView/)
assert.match(previewSource, /sourceStart/)
assert.match(previewSource, /sourceEnd/)
const { descriptor } = parseSfc(viewSource)
assert.ok(descriptor.template?.content.includes('TaskSetupStep'))
assert.ok(descriptor.template?.content.includes('PreviewCompareStep'))
assert.ok(descriptor.template?.content.includes('GenerationStep'))
assert.ok(descriptor.template?.content.includes('ResultEditorStep'))
console.log('数据处理四步向导回归检查通过')
```
- [ ] **Step 2: 在 `package.json` 注册命令**
```json
{
"scripts": {
"test:data-process-wizard": "node scripts/regression-data-process-wizard.mjs"
}
}
```
- [ ] **Step 3: 运行测试并确认失败原因正确**
Run: `cd frontend && npm run test:data-process-wizard`
Expected: FAIL首先因 `PreviewCompareStep.vue` 不存在或固定步骤断言不成立而失败。
### Task 2: 建立向导类型、草稿状态和来源映射模型
**Files:**
- Create: `frontend/src/views/data-process/create/types.ts`
- Create: `frontend/src/views/data-process/create/previewModel.ts`
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
**Interfaces:**
- Consumes: 上传文件解析出的字符串。
- Produces: `ProcessType``StepId``PreviewItem``ResultItem``DataProcessDraft``buildPreviewItems(sourceText, processType)``sourceLines(sourceText)`
- [ ] **Step 1: 在回归脚本增加模型文件和关键字段断言**
```js
const typesSource = await readFile(
path.resolve(scriptDir, '../src/views/data-process/create/types.ts'),
'utf8',
)
const modelSource = await readFile(
path.resolve(scriptDir, '../src/views/data-process/create/previewModel.ts'),
'utf8',
)
for (const field of ['sourceStart', 'sourceEnd', 'originalContent', 'editedContent']) {
assert.ok(typesSource.includes(field), `PreviewItem 缺少字段:${field}`)
}
assert.match(modelSource, /export function buildPreviewItems/)
assert.match(modelSource, /export function sourceLines/)
```
- [ ] **Step 2: 定义稳定类型**
```ts
export type ProcessType = 'structured' | 'unstructured'
export type StepId = 'create' | 'preview' | 'generate' | 'results'
export interface PreviewItem {
id: string
originalContent: string
editedContent: string
sourceStart: number | null
sourceEnd: number | null
sourceStartLine: number | null
sourceEndLine: number | null
tokenCount: number
status: 'original' | 'modified' | 'manual' | 'invalid'
}
export interface ResultItem {
id: string
instruction: string
input: string
output: string
status: 'valid' | 'modified' | 'invalid'
error?: string
}
```
- [ ] **Step 3: 实现可重复的来源偏移生成**
```ts
export function buildPreviewItems(sourceText: string, processType: ProcessType): PreviewItem[] {
const lines = sourceText.split('\n')
const groupSize = processType === 'structured' ? 1 : 3
let cursor = 0
const ranges = lines.map((line, index) => {
const start = cursor
cursor += line.length + (index < lines.length - 1 ? 1 : 0)
return { line, lineNumber: index + 1, start, end: start + line.length }
})
const items: PreviewItem[] = []
for (let index = 0; index < ranges.length; index += groupSize) {
const group = ranges.slice(index, index + groupSize)
if (!group.length || group.every((item) => !item.line.trim())) continue
const content = group.map((item) => item.line).join('\n')
items.push({
id: `preview-${items.length + 1}`,
originalContent: content,
editedContent: content,
sourceStart: group[0].start,
sourceEnd: group[group.length - 1].end,
sourceStartLine: group[0].lineNumber,
sourceEndLine: group[group.length - 1].lineNumber,
tokenCount: Math.max(1, Math.ceil(content.length / 2)),
status: 'original',
})
}
return items
}
```
- [ ] **Step 4: 运行回归检查和类型检查**
Run: `cd frontend && npm run test:data-process-wizard && npm run type-check`
Expected: 回归测试继续因组件未完成而失败;`previewModel.ts``types.ts` 不产生 TypeScript 错误。
### Task 3: 实现向导壳层和第一步创建任务
**Files:**
- Create: `frontend/src/views/data-process/create/TaskSetupStep.vue`
- Modify: `frontend/src/views/data-process/DataProcessCreateView.vue`
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
**Interfaces:**
- Consumes: `ProcessType`、Element Plus 表单与上传组件。
- Produces: `TaskSetupStep``v-model:name``v-model:description``v-model:processType``file-change``remove-file` 事件;父页面提供固定 `WIZARD_STEPS` 和统一底部操作。
- [ ] **Step 1: 将父页面步骤定义改为不可变四步**
```ts
const WIZARD_STEPS = [
{ id: 'create', title: '创建任务', desc: '填写任务信息与上传源数据' },
{ id: 'preview', title: '数据预览', desc: '核对源文件与预览内容' },
{ id: 'generate', title: '开始生成', desc: '确认摘要并启动处理' },
{ id: 'results', title: '结果编辑与保存', desc: '检查、修改并保存结果' },
] as const
```
- [ ] **Step 2: 创建第一步组件,保留现有校验并改为视觉选择块**
```ts
const props = defineProps<{
name: string
description: string
processType: ProcessType
file: File | null
fileCount: number
}>()
const emit = defineEmits<{
'update:name': [value: string]
'update:description': [value: string]
'update:processType': [value: ProcessType]
'file-change': [file: UploadFile]
'remove-file': []
}>()
```
- [ ] **Step 3: 在父页面统一步骤导航和底部动作文案**
```ts
const primaryActionLabel = computed(() => ({
create: '继续:数据预览',
preview: '确认预览并继续',
generate: generation.progress === 100 ? '查看生成结果' : '开始生成',
results: '保存任务',
}[currentStepId.value]))
```
- [ ] **Step 4: 运行回归检查和类型检查**
Run: `cd frontend && npm run test:data-process-wizard && npm run type-check`
Expected: 回归测试因后续三个组件缺失而失败;第一步相关代码通过类型检查。
### Task 4: 实现左右源文件与切片同步预览
**Files:**
- Create: `frontend/src/views/data-process/create/PreviewCompareStep.vue`
- Modify: `frontend/src/views/data-process/DataProcessCreateView.vue`
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
**Interfaces:**
- Consumes: `sourceText: string``items: PreviewItem[]``selectedId: string | null``processType: ProcessType`
- Produces: `update:selectedId``update:item-content``restore:item`;选中条目变化时调用 `scrollIntoView({ block: 'center' })`
- [ ] **Step 1: 增加源文范围与选中态的结构断言**
```js
for (const marker of [
'source-viewer',
'source-line',
'is-highlighted',
'preview-item',
'preview-editor',
'scrollIntoView',
]) {
assert.ok(previewSource.includes(marker), `第二步缺少结构:${marker}`)
}
```
- [ ] **Step 2: 通过行偏移判断高亮范围**
```ts
function isLineHighlighted(lineStart: number, lineEnd: number) {
if (!selectedItem.value || selectedItem.value.sourceStart == null || selectedItem.value.sourceEnd == null) {
return false
}
return lineEnd >= selectedItem.value.sourceStart
&& lineStart <= selectedItem.value.sourceEnd
}
```
- [ ] **Step 3: 选中切片后定位首个高亮行**
```ts
watch(selectedItem, async (item) => {
if (!item || item.sourceStart == null) return
await nextTick()
sourceViewerRef.value
?.querySelector<HTMLElement>(`[data-offset="${item.sourceStart}"]`)
?.scrollIntoView({ block: 'center', behavior: 'smooth' })
})
```
- [ ] **Step 4: 编辑时只更新 `editedContent` 和状态**
```ts
function updateContent(item: PreviewItem, value: string) {
emit('update:item-content', item.id, value)
}
```
父组件处理事件时不得修改 `sourceText``sourceStart``sourceEnd`
```ts
function updatePreviewContent(id: string, value: string) {
const item = draft.previewItems.find((entry) => entry.id === id)
if (!item) return
item.editedContent = value
item.status = value === item.originalContent ? 'original' : 'modified'
draft.dirty = true
}
```
- [ ] **Step 5: 完成搜索、仅看已修改、上一片、下一片和恢复原文**
Run: `cd frontend && npm run test:data-process-wizard && npm run type-check`
Expected: 第二步结构断言通过,类型检查通过;回归测试只因第三、四步组件缺失而失败。
### Task 5: 实现生成与结果编辑两个独立步骤
**Files:**
- Create: `frontend/src/views/data-process/create/GenerationStep.vue`
- Create: `frontend/src/views/data-process/create/ResultEditorStep.vue`
- Modify: `frontend/src/views/data-process/DataProcessCreateView.vue`
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
**Interfaces:**
- Consumes: 任务摘要、预览条目、生成状态和结果条目。
- Produces: `start``stop``retry``update:result``restore:result``save` 事件。
- [ ] **Step 1: 生成步骤只保留摘要、进度与状态**
```ts
const emit = defineEmits<{
start: []
stop: []
retry: []
}>()
```
生成完成前底部唯一主操作为 `开始生成`;生成进行中为禁用的 `正在生成`;完成后变为 `查看生成结果`
- [ ] **Step 2: 清理并托管模拟生成计时器**
```ts
let generationTimer: ReturnType<typeof setInterval> | null = null
function stopGenerationTimer() {
if (generationTimer) clearInterval(generationTimer)
generationTimer = null
}
onBeforeUnmount(stopGenerationTimer)
```
- [ ] **Step 3: 将预览条目转换为结构化结果字段**
```ts
function createResults(items: PreviewItem[]): ResultItem[] {
return items.slice(0, 12).map((item, index) => ({
id: `result-${index + 1}`,
instruction: item.editedContent.split('\n')[0] || `数据条目 ${index + 1}`,
input: '',
output: item.editedContent.split('\n').slice(1).join('\n') || item.editedContent,
status: 'valid',
}))
}
```
- [ ] **Step 4: 使用左侧结果列表和右侧字段编辑器替代原始 JSON 文本框**
```ts
function validateResult(item: ResultItem) {
item.error = item.instruction.trim() && item.output.trim() ? undefined : '指令和输出不能为空'
item.status = item.error ? 'invalid' : 'modified'
}
```
- [ ] **Step 5: 运行回归、页面表面和类型检查**
Run: `cd frontend && npm run test:data-process-wizard && npm run test:page-surface && npm run type-check`
Expected: 三项检查全部 PASS。
### Task 6: 完成视觉实现、响应式和浏览器验收
**Files:**
- Modify: `frontend/src/views/data-process/DataProcessCreateView.vue`
- Modify: `frontend/src/views/data-process/create/TaskSetupStep.vue`
- Modify: `frontend/src/views/data-process/create/PreviewCompareStep.vue`
- Modify: `frontend/src/views/data-process/create/GenerationStep.vue`
- Modify: `frontend/src/views/data-process/create/ResultEditorStep.vue`
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
**Interfaces:**
- Consumes: 已完成的四步组件和现有全局页面画布。
- Produces: 与确认修订稿一致的桌面布局,以及 900px 以下的上下布局。
- [ ] **Step 1: 落实单层页面、固定步骤和底部操作栏样式**
```scss
.wizard-footer {
position: sticky;
bottom: 0;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 64px;
background: rgba(255, 255, 255, 0.98);
border-top: 1px solid #eef0f5;
}
```
- [ ] **Step 2: 落实桌面左右对照和 900px 响应式**
```scss
.preview-workspace {
display: grid;
grid-template-columns: minmax(0, 58fr) minmax(380px, 42fr);
}
@media (max-width: 900px) {
.preview-workspace {
grid-template-columns: minmax(0, 1fr);
}
}
```
- [ ] **Step 3: 启动页面并逐步验证四步交互**
Run: `cd frontend && npm run dev -- --host 0.0.0.0 --port 16801`
Browser checks at `http://localhost:16801/data-process/create`:
1. 第一步上传文本并选择非结构化数据。
2. 第二步点击至少三个右侧切片,确认左侧滚动目标和高亮范围变化。
3. 修改一个切片并切换前后条目,确认修改状态和内容保留。
4. 完成生成并进入第四步,修改结果字段并保存。
5. 返回前一步,确认草稿和选中项未丢失。
6. 以 1440×1024 和 900px 窄屏分别截图,确认无横向溢出和底部遮挡。
- [ ] **Step 4: 执行完整验证**
Run: `cd frontend && npm run test:data-process-wizard && npm run test:page-surface && npm run type-check && npm run build`
Expected: 所有回归脚本、类型检查和生产构建全部 PASS。
## Self-Review Result
- 规格中的四步稳定语义由 Tasks 1、3、5 覆盖。
- 左右对照、来源映射、滚动高亮、编辑不改源文件由 Tasks 2、4 覆盖。
- 生成状态、计时器清理、结果字段校验由 Task 5 覆盖。
- 单层白底、响应式、路由转场连续性和最终验收由 Task 6 覆盖。
- 未引入新依赖;计划中所有类型和事件名在前置任务中已有定义。