diff --git a/.gitignore b/.gitignore
index e74dd37..897d507 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ dist-ssr
*.sln
*.sw?
TEST/
+docs/
diff --git a/docs/superpowers/plans/2026-06-09-platform-architecture-plan.md b/docs/superpowers/plans/2026-06-09-platform-architecture-plan.md
deleted file mode 100644
index 09d9709..0000000
--- a/docs/superpowers/plans/2026-06-09-platform-architecture-plan.md
+++ /dev/null
@@ -1,1211 +0,0 @@
-# 平台化架构重构实现计划
-
-> **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:** 将 dialogBox 中的 Painting/Video 硬分支重构为平台描述符模式,新平台只需新建文件夹实现标准接口。
-
-**Architecture:** 每个平台导出 `definePlatform()` 工厂函数,返回 ModelSelector 组件 + controls 列表 + buildTaskBody 等标准接口。dialogBox 退化为纯渲染引擎,通过 `createPlatform(type)` 获取 descriptor 并委托所有平台特定逻辑。
-
-**Tech Stack:** Vue 3 Composition API + Pinia + Vite
-
----
-
-### Task 1: 创建平台注册表和基础设施
-
-**Files:**
-- Create: `src/platforms/registry.js`
-
-- [ ] **Step 1: 创建 registry.js**
-
-```js
-// src/platforms/registry.js
-
-/** 平台注册表:id → definePlatform 工厂函数 */
-const registry = {}
-
-/** 注册平台 */
-export function registerPlatform(id, factory) {
- registry[id] = factory
-}
-
-/** 根据平台类型创建平台实例 */
-export function createPlatform(type) {
- const factory = registry[type]
- if (!factory) throw new Error(`未注册的平台: ${type}`)
- return factory()
-}
-
-/** 获取所有已注册平台 ID */
-export function getRegisteredPlatforms() {
- return Object.keys(registry)
-}
-```
-
-- [ ] **Step 2: 验证文件无语法错误**
-
-Run: `node -e "import('./src/platforms/registry.js').then(m => console.log('OK'))"` 或在 Vite dev 下验证
-
-- [ ] **Step 3: 提交**
-
-```bash
-git add src/platforms/registry.js
-git commit -m "feat: 新增平台注册表基础设施"
-```
-
----
-
-### Task 2: 创建 Painting 平台包
-
-**Files:**
-- Create: `src/platforms/painting/index.js`
-- Create: `src/platforms/painting/modelSelector.vue`
-- Create: `src/platforms/painting/controls/proportion.vue`
-- Create: `src/platforms/painting/controls/dimension.vue`
-- Create: `src/platforms/painting/controls/quality.vue`
-- Create: `src/platforms/painting/controls/quantity.vue`
-- Create: `src/platforms/painting/imageUploader.vue`
-
-- [ ] **Step 1: 迁移 modelSelector(从 `dialogBox/model/painting.vue`)**
-
-Copy `src/components/dialogBox/model/painting.vue` → `src/platforms/painting/modelSelector.vue`,无需修改内容。
-
-- [ ] **Step 2: 迁移 proportion(从 `dialogBox/proportion/painting.vue`)**
-
-Copy `src/components/dialogBox/proportion/painting.vue` → `src/platforms/painting/controls/proportion.vue`,无需修改内容。
-
-- [ ] **Step 3: 迁移 dimension(从 `dialogBox/dimension/index.vue`)**
-
-Copy `src/components/dialogBox/dimension/index.vue` → `src/platforms/painting/controls/dimension.vue`,无需修改内容。
-
-- [ ] **Step 4: 提取 quality 为独立组件(原 dialogBox inline)**
-
-新建 `src/platforms/painting/controls/quality.vue`:
-
-```vue
-
-
-
-
-
-
-
-```
-
-- [ ] **Step 5: 迁移 quantity(从 `dialogBox/quantity/index.vue`)**
-
-Copy `src/components/dialogBox/quantity/index.vue` → `src/platforms/painting/controls/quantity.vue`,无需修改内容。
-
-- [ ] **Step 6: 迁移 imageUploader(从 `dialogBox/imageUploader/index.vue`)**
-
-Copy `src/components/dialogBox/imageUploader/index.vue` → `src/platforms/painting/imageUploader.vue`,无需修改内容。
-
-- [ ] **Step 7: 创建 Painting descriptor**
-
-新建 `src/platforms/painting/index.js`:
-
-```js
-import { ref, reactive, computed, markRaw } from 'vue'
-import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
-import { getModelConfig } from '@/config/models/index.js'
-import PaintingModelSelector from './modelSelector.vue'
-import PaintingProportion from './controls/proportion.vue'
-import DimensionInput from './controls/dimension.vue'
-import QualitySelect from './controls/quality.vue'
-import Quantity from './controls/quantity.vue'
-import ImageUploader from './imageUploader.vue'
-import { registerPlatform } from '../registry.js'
-
-function getDimConfig(config) {
- if (!config) return null
- const dimParam = config.params.find(p => p.ui === 'dimension')
- if (dimParam) return { type: 'combined', config: dimParam.dimension, paramName: dimParam.name }
- const wParam = config.params.find(p => p.ui === 'dimensionWidth')
- const hParam = config.params.find(p => p.ui === 'dimensionHeight')
- if (wParam && hParam) return { type: 'split', wParam, hParam }
- return null
-}
-
-export function definePaintingPlatform() {
- const model = ref('Flux 2')
- const modelType = ref('text')
- const proportion = ref('1:1')
- const resolution = ref('2k')
- const customWidth = ref(1024)
- const customHight = ref(1024)
- const dimWidth = ref(1024)
- const dimHeight = ref(1024)
- const quantity = ref(1)
- const quality = ref('medium')
- const modelConfig = ref(null)
- const promptPlaceholder = ref('描述你想生成的画面和动作。')
- const paramValues = reactive({})
-
- const state = {
- model, modelType,
- proportion, resolution,
- customWidth, customHight,
- dimWidth, dimHeight,
- quantity, quality,
- paramValues, modelConfig,
- }
-
- function syncDefaults(config) {
- modelConfig.value = config
- if (!config) return
- config.params.forEach(p => {
- if (!(p.name in paramValues)) {
- paramValues[p.name] = p.default ?? (p.name === 'outputFormat' ? 'png' : '')
- }
- })
- const ratioParam = config.params.find(p => p.ui === 'proportion')
- if (ratioParam) proportion.value = ratioParam.default || '1:1'
- const resParam = config.params.find(p => p.ui === 'resolution')
- if (resParam) resolution.value = resParam.default || '2k'
- const qtyParam = config.params.find(p => p.ui === 'quantity')
- if (qtyParam) quantity.value = qtyParam.default || 1
- const cwParam = config.params.find(p => p.name === 'customWidth')
- if (cwParam) customWidth.value = cwParam.default || 1024
- const chParam = config.params.find(p => p.name === 'customHight')
- if (chParam) customHight.value = chParam.default || 1024
- const qualityParam = config.params.find(p => p.name === 'quality')
- if (qualityParam) quality.value = qualityParam.default || 'medium'
- const dc = getDimConfig(config)
- if (dc?.type === 'split') {
- dimWidth.value = dc.wParam.default || 1024
- dimHeight.value = dc.hParam.default || 1024
- } else if (dc?.type === 'combined') {
- const dimParam = config.params.find(p => p.name === dc.paramName)
- const raw = dimParam?.default || ''
- const parsed = dc.config.parse(raw)
- dimWidth.value = parsed.width
- dimHeight.value = parsed.height
- }
- }
-
- // 同步 UI refs → paramValues(由 dialogBox 在合适的时机调用)
- function syncParamValues() {
- const ratioParam = modelConfig.value?.params?.find(p => p.ui === 'proportion')
- if (ratioParam) paramValues[ratioParam.name] = proportion.value
- const resParam = modelConfig.value?.params?.find(p => p.ui === 'resolution')
- if (resParam) paramValues[resParam.name] = resolution.value
- const qtyParam = modelConfig.value?.params?.find(p => p.ui === 'quantity')
- if (qtyParam) paramValues[qtyParam.name] = quantity.value
- if (modelConfig.value?.params?.find(p => p.name === 'customWidth')) {
- paramValues.customWidth = customWidth.value
- }
- if (modelConfig.value?.params?.find(p => p.name === 'customHight')) {
- paramValues.customHight = customHight.value
- }
- if (modelConfig.value?.params?.find(p => p.name === 'quality')) {
- paramValues.quality = quality.value
- }
- const dc = getDimConfig(modelConfig.value)
- if (dc?.type === 'split') {
- paramValues[dc.wParam.name] = dimWidth.value
- paramValues[dc.hParam.name] = dimHeight.value
- } else if (dc?.type === 'combined') {
- paramValues[dc.paramName] = dc.config.format(dimWidth.value, dimHeight.value)
- }
- }
-
- const controls = [
- {
- name: 'proportion',
- component: markRaw(PaintingProportion),
- show: (config) => !!config?.params?.find(p => p.ui === 'proportion'),
- props: (config) => {
- const ratioParam = config?.params?.find(p => p.ui === 'proportion')
- const resParam = config?.params?.find(p => p.ui === 'resolution')
- return {
- modelValue: proportion.value,
- 'onUpdate:modelValue': (v) => { proportion.value = v },
- resolution: resolution.value,
- 'onUpdate:resolution': (v) => { resolution.value = v },
- width: customWidth.value,
- 'onUpdate:width': (v) => { customWidth.value = v },
- height: customHight.value,
- 'onUpdate:height': (v) => { customHight.value = v },
- proportionOptions: ratioParam?.options
- ?.filter(o => o !== 'custom')
- .map(o => ({ value: o, label: o })) || [],
- resolutionOptions: resParam?.options
- ?.map(o => ({ value: o, label: o.toUpperCase() })) || [],
- allowCustom: ratioParam?.options?.includes('custom') || false,
- }
- },
- },
- {
- name: 'dimension',
- component: markRaw(DimensionInput),
- show: (config) => !!config?.params?.find(p => p.ui === 'dimension' || p.ui === 'dimensionWidth'),
- props: (config) => {
- const dc = getDimConfig(config)
- return {
- width: dimWidth.value,
- 'onUpdate:width': (v) => { dimWidth.value = v },
- height: dimHeight.value,
- 'onUpdate:height': (v) => { dimHeight.value = v },
- minW: dc?.config?.width?.min || dc?.wParam?.min || 256,
- maxW: dc?.config?.width?.max || dc?.wParam?.max || 6197,
- minH: dc?.config?.height?.min || dc?.hParam?.min || 256,
- maxH: dc?.config?.height?.max || dc?.hParam?.max || 4096,
- }
- },
- },
- {
- name: 'quality',
- component: markRaw(QualitySelect),
- show: (config) => !!config?.params?.find(p => p.name === 'quality'),
- props: (config) => {
- const q = config?.params?.find(p => p.name === 'quality')
- return {
- modelValue: quality.value,
- 'onUpdate:modelValue': (v) => { quality.value = v },
- options: q?.options?.map(o => ({ value: o, label: o })) || [],
- }
- },
- },
- {
- name: 'quantity',
- component: markRaw(Quantity),
- show: (config) => !!config?.params?.find(p => p.ui === 'quantity'),
- props: (config) => {
- const qtyParam = config?.params?.find(p => p.ui === 'quantity')
- return {
- modelValue: quantity.value,
- 'onUpdate:modelValue': (v) => { quantity.value = v },
- max: qtyParam?.options?.length ? Math.max(...qtyParam.options) : 4,
- }
- },
- },
- ]
-
- const platform = {
- id: 'painting',
- label: 'AI绘画2026',
- ModelSelector: markRaw(PaintingModelSelector),
- modelSelectorProps: null,
- controls,
- ImageUploader: markRaw(ImageUploader),
- state,
- model,
- modelType,
- modelConfig,
- promptPlaceholder,
-
- async loadModels() {
- const code = getPlatformCode('Painting')
- return fetchPlatformModels(code)
- },
-
- async loadConfig(modelName) {
- const config = getModelConfig(modelName)
- syncDefaults(config)
- return config
- },
-
- getDefaultModel() {
- return 'Flux 2'
- },
-
- showImageUploader() {
- if (modelType.value !== 'text') return true
- return modelConfig.value?.inputType === 'image' || modelConfig.value?.inputType === 'both'
- },
-
- imageUploadLimit() {
- if (!modelConfig.value) return 4
- const imageParam = modelConfig.value.params.find(p => p.ui === 'imageUpload')
- return imageParam?.maxCount || modelConfig.value.maxImages || 4
- },
-
- isImageRequired() {
- return !!(modelConfig.value?.params?.find(p => p.ui === 'imageUpload'))
- },
-
- buildTaskBody(shared) {
- syncParamValues()
- const modelParams = { ...paramValues }
- if (shared.prompt.value) modelParams.prompt = shared.prompt.value
- return modelParams
- },
-
- // 从历史记录恢复参数到 UI state
- fillFromResult(resultData) {
- if (resultData.model !== undefined) model.value = resultData.model
- if (resultData.modelType !== undefined) modelType.value = resultData.modelType
- if (resultData.proportion !== undefined) proportion.value = resultData.proportion
- if (resultData.resolution !== undefined) resolution.value = resultData.resolution
- if (resultData.customWidth !== undefined) customWidth.value = resultData.customWidth
- if (resultData.customHight !== undefined) customHight.value = resultData.customHight
- if (resultData.quantity !== undefined) quantity.value = resultData.quantity
- if (resultData.modelParams !== undefined) Object.assign(paramValues, resultData.modelParams)
- // 从恢复的 modelParams 同步 dimension/quality UI refs
- const dc = getDimConfig(modelConfig.value)
- if (dc?.type === 'split') {
- if (paramValues[dc.wParam.name] !== undefined) dimWidth.value = paramValues[dc.wParam.name]
- if (paramValues[dc.hParam.name] !== undefined) dimHeight.value = paramValues[dc.hParam.name]
- } else if (dc?.type === 'combined') {
- if (paramValues[dc.paramName]) {
- const parsed = dc.config.parse(paramValues[dc.paramName])
- dimWidth.value = parsed.width
- dimHeight.value = parsed.height
- }
- }
- if (paramValues.quality !== undefined) quality.value = paramValues.quality
- },
- }
-
- return platform
-}
-
-// 自注册
-registerPlatform('Painting', definePaintingPlatform)
-```
-
-- [ ] **Step 8: 提交**
-
-```bash
-git add src/platforms/painting/ src/platforms/registry.js
-git commit -m "feat: 新增 Painting 平台包(descriptor + 控件迁移)"
-```
-
----
-
-### Task 3: 创建 Video 平台包
-
-**Files:**
-- Create: `src/platforms/video/index.js`
-- Create: `src/platforms/video/modelSelector.vue`
-- Create: `src/platforms/video/controls/pattern.vue`
-- Create: `src/platforms/video/controls/proportion.vue`
-- Create: `src/platforms/video/controls/time.vue`
-- Create: `src/platforms/video/imageUploader.vue`
-
-- [ ] **Step 1: 迁移 modelSelector(从 `dialogBox/model/video.vue`)**
-
-Copy `src/components/dialogBox/model/video.vue` → `src/platforms/video/modelSelector.vue`,无需修改内容。
-
-- [ ] **Step 2: 迁移 pattern(从 `dialogBox/pattern/index.vue`)**
-
-Copy `src/components/dialogBox/pattern/index.vue` → `src/platforms/video/controls/pattern.vue`,无需修改内容。
-
-- [ ] **Step 3: 迁移 proportion(从 `dialogBox/proportion/video.vue`)**
-
-Copy `src/components/dialogBox/proportion/video.vue` → `src/platforms/video/controls/proportion.vue`,无需修改内容。
-
-- [ ] **Step 4: 迁移 time(从 `dialogBox/Time/index.vue`)**
-
-Copy `src/components/dialogBox/Time/index.vue` → `src/platforms/video/controls/time.vue`,无需修改内容。
-
-- [ ] **Step 5: 迁移 imageUploader(从 `dialogBox/videoImageUploader/index.vue`)**
-
-Copy `src/components/dialogBox/videoImageUploader/index.vue` → `src/platforms/video/imageUploader.vue`,无需修改内容。
-
-- [ ] **Step 6: 创建 Video descriptor**
-
-新建 `src/platforms/video/index.js`:
-
-```js
-import { ref, reactive, markRaw } from 'vue'
-import { fetchModelConfig } from '@/utils/modelConfig'
-import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
-import VideoModelSelector from './modelSelector.vue'
-import Pattern from './controls/pattern.vue'
-import VideoProportion from './controls/proportion.vue'
-import Time from './controls/time.vue'
-import VideoImageUploader from './imageUploader.vue'
-import { registerPlatform } from '../registry.js'
-
-export function defineVideoPlatform() {
- const model = ref('LTX2.0')
- const modelType = ref('text')
- const proportion = ref('16:9')
- const resolution = ref('1k')
- const duration = ref(5)
- const videoPattern = ref('文生视频')
-
- const resolutionOptions = ref([
- { value: '1k', label: '标清 1K' },
- { value: '2k', label: '高清 2K' },
- { value: '4k', label: '超清 4K' },
- ])
- const proportionOptions = ref([
- { value: '智能', label: '智能' },
- { value: '21:9', label: '21:9' },
- { value: '16:9', label: '16:9' },
- { value: '4:3', label: '4:3' },
- { value: '1:1', label: '1:1' },
- { value: '3:4', label: '3:4' },
- { value: '9:16', label: '9:16' },
- ])
- const durationOptions = ref([])
- const modelDisplayConfig = ref(null)
- const promptPlaceholder = ref('描述你想生成的画面和动作。')
-
- const state = {
- model, modelType,
- proportion, resolution,
- duration, videoPattern,
- resolutionOptions, proportionOptions, durationOptions,
- modelDisplayConfig,
- }
-
- async function loadConfig(modelName, modelTypeVal) {
- const config = await fetchModelConfig('Video', modelName, modelTypeVal)
- modelDisplayConfig.value = config
- if (config?.display) {
- const d = config.display
- if (d.resolution) {
- resolution.value = d.resolution.default || '1k'
- resolutionOptions.value = d.resolution.options || []
- }
- if (d.proportion) {
- proportion.value = d.proportion.default || '16:9'
- proportionOptions.value = d.proportion.options || []
- }
- if (d.duration) {
- duration.value = d.duration.default || 5
- durationOptions.value = d.duration.options || []
- }
- if (d.promptPlaceholder) {
- promptPlaceholder.value = d.promptPlaceholder.default || '描述你想生成的画面和动作。'
- }
- }
- return config
- }
-
- const controls = [
- {
- name: 'pattern',
- component: markRaw(Pattern),
- show: () => true,
- props: () => ({
- modelValue: videoPattern.value,
- 'onUpdate:modelValue': (v) => { videoPattern.value = v },
- }),
- },
- {
- name: 'proportion',
- component: markRaw(VideoProportion),
- show: () => true,
- props: () => ({
- modelValue: proportion.value,
- 'onUpdate:modelValue': (v) => { proportion.value = v },
- resolution: resolution.value,
- 'onUpdate:resolution': (v) => { resolution.value = v },
- proportionOptions: proportionOptions.value,
- resolutionOptions: resolutionOptions.value,
- }),
- },
- {
- name: 'time',
- component: markRaw(Time),
- show: () => true,
- props: () => ({
- modelValue: duration.value,
- 'onUpdate:modelValue': (v) => { duration.value = v },
- options: durationOptions.value,
- }),
- },
- ]
-
- const platform = {
- id: 'video',
- label: 'AI视频2026',
- ModelSelector: markRaw(VideoModelSelector),
- modelSelectorProps: () => ({ videoPattern: videoPattern.value }),
- controls,
- ImageUploader: markRaw(VideoImageUploader),
- state,
- model,
- modelType,
- modelDisplayConfig,
- promptPlaceholder,
-
- async loadModels() {
- const code = getPlatformCode('Video')
- return fetchPlatformModels(code)
- },
-
- async loadConfig(modelName, modelTypeVal) {
- return loadConfig(modelName, modelTypeVal)
- },
-
- getDefaultModel() {
- return 'LTX2.0'
- },
-
- showImageUploader() {
- return modelType.value !== 'text'
- },
-
- imageUploadLimit() {
- return modelDisplayConfig.value?.display?.images || 1
- },
-
- isImageRequired() {
- return modelType.value !== 'text'
- },
-
- buildTaskBody(shared) {
- // 与 Painting 统一:直接返回扁平参数
- const modelParams = {
- prompt: shared.prompt.value,
- proportion: proportion.value,
- resolution: resolution.value,
- duration: duration.value,
- videoPattern: videoPattern.value,
- }
- return modelParams
- },
-
- fillFromResult(resultData) {
- if (resultData.model !== undefined) model.value = resultData.model
- if (resultData.modelType !== undefined) modelType.value = resultData.modelType
- if (resultData.proportion !== undefined) proportion.value = resultData.proportion
- if (resultData.resolution !== undefined) resolution.value = resultData.resolution
- if (resultData.duration !== undefined) duration.value = resultData.duration
- if (resultData.videoPattern !== undefined) videoPattern.value = resultData.videoPattern
- },
- }
-
- return platform
-}
-
-registerPlatform('Video', defineVideoPlatform)
-```
-
-- [ ] **Step 7: 提交**
-
-```bash
-git add src/platforms/video/
-git commit -m "feat: 新增 Video 平台包(descriptor + 控件迁移)"
-```
-
----
-
-### Task 4: 重写 dialogBox 为通用编排壳
-
-**Files:**
-- Modify: `src/components/dialogBox/index.vue`(重写)
-- Modify: `src/utils/createTask.js`
-
-- [ ] **Step 1: 简化 createTask.js 为纯透传**
-
-修改 `src/utils/createTask.js`:
-
-```js
-// src/utils/createTask.js
-// 所有平台 descriptor 的 buildTaskBody() 已直接返回扁平 modelParams,
-// 此文件仅做透传,后续可直接移除
-export async function createTask(data) {
- return data.body
-}
-```
-
-- [ ] **Step 2: 重写 dialogBox/index.vue**
-
-用以下内容完整替换 `src/components/dialogBox/index.vue`:
-
-```vue
-
-
-
-
-
-
-
-
-
-```
-
-- [ ] **Step 3: 验证 dialogBox 的 import 路径正确**
-
-检查所有 import 路径是否存在:
-- `@/platforms/registry.js` → `src/platforms/registry.js`
-- `@/platforms/painting/index.js` → `src/platforms/painting/index.js`
-- `@/platforms/video/index.js` → `src/platforms/video/index.js`
-
-- [ ] **Step 4: 提交**
-
-```bash
-git add src/components/dialogBox/index.vue src/utils/createTask.js
-git commit -m "refactor: dialogBox 重构为通用平台编排壳,委托所有平台特定逻辑"
-```
-
----
-
-### Task 5: 删除旧代码
-
-**Files:**
-- Delete: `src/config/models/` 目录
-- Delete: `src/config/runninghub/` 目录
-- Delete: `src/utils/modelConfig.js`
-- Delete: `src/components/dialogBox/model/painting.vue`
-- Delete: `src/components/dialogBox/model/video.vue`
-- Delete: `src/components/dialogBox/proportion/painting.vue`
-- Delete: `src/components/dialogBox/proportion/video.vue`
-- Delete: `src/components/dialogBox/dimension/index.vue`
-- Delete: `src/components/dialogBox/quantity/index.vue`
-- Delete: `src/components/dialogBox/pattern/index.vue`
-- Delete: `src/components/dialogBox/Time/index.vue`
-- Delete: `src/components/dialogBox/imageUploader/index.vue`
-- Delete: `src/components/dialogBox/videoImageUploader/index.vue`
-- Modify: `src/config/index.js`
-
-- [ ] **Step 1: 删除 dialogBox 下的旧平台组件**
-
-```bash
-rm -rf src/components/dialogBox/model/
-rm -rf src/components/dialogBox/proportion/
-rm -rf src/components/dialogBox/dimension/
-rm -rf src/components/dialogBox/quantity/
-rm -rf src/components/dialogBox/pattern/
-rm -rf src/components/dialogBox/Time/
-rm -rf src/components/dialogBox/imageUploader/
-rm -rf src/components/dialogBox/videoImageUploader/
-```
-
-- [ ] **Step 2: 删除旧 config 目录**
-
-```bash
-rm -rf src/config/models/
-rm -rf src/config/runninghub/
-rm src/config/index.js
-# 注意:modelConfig.js 暂时保留,Video descriptor 用 fetchModelConfig() 获取 display 配置驱动 UI,
-# 待 Video 后端化(API 返回参数 schema)后可删除
-```
-
-- [ ] **Step 3: 删除 config/index.js(不再被引用)**
-
-```bash
-rm src/config/index.js
-```
-
-原来 `config/index.js` 仅导出 runninghub 适配器供 `createTask.js` 使用,现在 createTask 已简化,此文件无用。
-
-- [ ] **Step 4: 搜索并修复残留引用**
-
-```bash
-grep -r "config/models" src/ --include="*.js" --include="*.vue"
-grep -r "components/dialogBox/model" src/ --include="*.js" --include="*.vue"
-grep -r "components/dialogBox/proportion" src/ --include="*.js" --include="*.vue"
-grep -r "utils/modelConfig" src/ --include="*.js" --include="*.vue"
-```
-
-修复任何残留引用,更新为新的 import 路径。
-
-- [ ] **Step 5: 提交**
-
-```bash
-git add -A
-git commit -m "chore: 删除旧架构代码(config/models、runninghub、dialogBox 旧组件)"
-```
-
----
-
-### Task 6: 验证构建与运行
-
-- [ ] **Step 1: 启动开发服务器**
-
-```bash
-pnpm dev
-```
-
-- [ ] **Step 2: 检查 Painting 流程**
-
-1. 打开首页 → 确认标题显示 "AI绘画2026"
-2. 确认模型选择器显示模型列表(从 API 加载)
-3. 切换模型 → 确认比例/分辨率/尺寸等控件正确显示/隐藏
-4. 输入 prompt → 点击发送 → 确认请求发出
-5. 确认生成结果在虚拟滚动列表中显示
-
-- [ ] **Step 3: 检查 Video 流程**
-
-1. 切换到 Video 模式 → 确认标题显示 "AI视频2026"
-2. 确认 Pattern 选择器 + 模型选择器正常工作
-3. 切换 Pattern → 确认模型列表更新
-4. 输入 prompt → 点击发送 → 确认请求发出
-
-- [ ] **Step 4: 检查平台切换**
-
-1. 从 Painting 切换到 Video → 确认所有控件正确替换
-2. 从 Video 切换到 Painting → 确认控件恢复
-
-- [ ] **Step 5: 检查历史回填**
-
-1. 点击历史记录中的某项 → 确认参数正确回填到当前平台控件
-
-- [ ] **Step 6: 修复发现的问题并提交**
-
-```bash
-git add -A
-git commit -m "fix: 验证后修复平台切换和历史回填问题"
-```
-
----
-
-### Task 7: 审查后端化方案文档
-
-- [ ] **Step 1: 阅读当前后端化方案**
-
-读取 `docs/模型参数后端化方案.md`
-
-- [ ] **Step 2: 对照新架构,指出文档需要变更的部分**
-
-新架构下,各平台的 `loadConfig()` 在 descriptor 内实现,后端化只需改变 descriptor 的内部实现:
-
-1. **统一 API 端点**:所有平台通过同一 API 获取模型列表和参数 schema,不再有 Painting 本地 JS / Video 远程 JSON 的差异
-2. **参数元数据格式**:后端返回的模型参数需要包含 `ui` 字段(当前 Painting 在本地 config 中定义),以便 descriptor 的 `show()` 和 `props()` 正确工作
-3. **文档需补充**:后端 API 返回的模型参数 schema 格式约定(每个参数需包含 `name`、`type`、`ui`、`default`、`options`、`min/max` 等字段)
-4. **不再需要的内容**:文档中 RunningHub 原始 API 的 workflow/seed/width/height 等概念,在新架构中这些由 descriptor 内部消化,不再暴露给 API 调用方
-
-- [ ] **Step 3: 将审查结论写入计划备注或直接修改 doc**
-
----
-
-### 完成标准
-
-- [ ] `pnpm build` 无错误
-- [ ] Painting 流程:模型选择 → 参数调整 → 发送 → 结果展示
-- [ ] Video 流程:Pattern → 模型选择 → 参数调整 → 发送 → 结果展示
-- [ ] 平台切换:控件正确替换,无残留状态
-- [ ] 历史回填:参数正确恢复到控件
-- [ ] 新增平台只需 3 步:新建文件夹 → 实现 descriptor → registry 注册一行
diff --git a/docs/superpowers/plans/2026-06-09-模型参数后端化-前端适配.md b/docs/superpowers/plans/2026-06-09-模型参数后端化-前端适配.md
deleted file mode 100644
index 5ce6719..0000000
--- a/docs/superpowers/plans/2026-06-09-模型参数后端化-前端适配.md
+++ /dev/null
@@ -1,735 +0,0 @@
-# 模型参数后端化 — 前端适配实现计划
-
-> **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:** 将 Painting/Video 平台的模型参数配置从硬编码迁移至后端 API 获取
-
-**Architecture:** 新建 modelConfigHelper.js 共享工具函数,在 modelApi.js 加缓存层,Painting/Video 双平台统一走 API + params 驱动。方案 A 最小改动。
-
-**Tech Stack:** Vue 3 Composition API + Vite 7 + Pinia + Axios
-
----
-
-### Task 1: 新建 `src/utils/modelConfigHelper.js`
-
-**Files:**
-- Create: `src/utils/modelConfigHelper.js`
-
-- [ ] **Step 1: 写入完整文件**
-
-```js
-// 模型配置共享工具函数
-// 供 Painting / Video descriptor 使用
-
-/**
- * 检测 dimension 配置模式
- * @param {Object|null} config - 模型配置对象
- * @returns {Object|null}
- * - combined: { type: 'combined', config: dimension子对象, paramName: string }
- * - split: { type: 'split', wParam: 宽度参数, hParam: 高度参数 }
- * - null: 无 dimension 参数
- */
-export function getDimConfig(config) {
- if (!config) return null
- const dimParam = config.params.find(p => p.ui === 'dimension')
- if (dimParam) return { type: 'combined', config: dimParam.dimension, paramName: dimParam.name }
- const wParam = config.params.find(p => p.ui === 'dimensionWidth')
- const hParam = config.params.find(p => p.ui === 'dimensionHeight')
- if (wParam && hParam) return { type: 'split', wParam, hParam }
- return null
-}
-
-/**
- * 检查 showWhen 条件是否满足
- * @param {Object} param - 参数定义(可能含 showWhen)
- * @param {Object} paramValues - 当前所有参数值
- * @returns {boolean}
- */
-export function checkShowWhen(param, paramValues) {
- if (!param.showWhen) return true
- return Object.entries(param.showWhen).every(([key, expected]) => {
- return paramValues[key] === expected
- })
-}
-
-/**
- * 将 API 返回的 config 同步到响应式 state
- *
- * state 对象需包含以下属性(均为 ref 或 reactive):
- * modelConfig, paramValues, proportion, resolution, quantity, quality,
- * customWidth, customHight, dimWidth, dimHeight, promptPlaceholder
- */
-export function syncDefaults(config, state) {
- const {
- modelConfig, paramValues, proportion, resolution, quantity, quality,
- customWidth, customHight, dimWidth, dimHeight, promptPlaceholder,
- } = state
-
- modelConfig.value = config
- if (!config) return
-
- // 1. dimension.separator → 生成 parse/format(在遍历 params 之前完成)
- config.params.forEach(p => {
- if (p.ui === 'dimension' && p.dimension?.separator && !p.dimension.parse) {
- const sep = p.dimension.separator
- p.dimension.parse = (val) => {
- const parts = (val || '').split(sep)
- return { width: parseInt(parts[0]) || 0, height: parseInt(parts[1]) || 0 }
- }
- p.dimension.format = (w, h) => `${w}${sep}${h}`
- }
- })
-
- // 2. 初始化 paramValues(已存在的 key 保留,避免切换模型时丢失值)
- config.params.forEach(p => {
- if (!(p.name in paramValues)) {
- paramValues[p.name] = p.default ?? (p.name === 'outputFormat' ? 'png' : '')
- }
- })
-
- // 3. 同步专用 ref
- const ratioParam = config.params.find(p => p.ui === 'proportion')
- if (ratioParam) proportion.value = ratioParam.default || '1:1'
-
- const resParam = config.params.find(p => p.ui === 'resolution')
- if (resParam) resolution.value = resParam.default || '2k'
-
- const qtyParam = config.params.find(p => p.ui === 'quantity')
- if (qtyParam) quantity.value = qtyParam.default || 1
-
- const cwParam = config.params.find(p => p.name === 'customWidth')
- if (cwParam) customWidth.value = cwParam.default || 1024
-
- const chParam = config.params.find(p => p.name === 'customHight')
- if (chParam) customHight.value = chParam.default || 1024
-
- const qualityParam = config.params.find(p => p.name === 'quality')
- if (qualityParam) quality.value = qualityParam.default || 'medium'
-
- // 4. dimension 初始化
- const dc = getDimConfig(config)
- if (dc?.type === 'split') {
- dimWidth.value = dc.wParam.default || 1024
- dimHeight.value = dc.hParam.default || 1024
- } else if (dc?.type === 'combined') {
- const dimParam = config.params.find(p => p.name === dc.paramName)
- const raw = dimParam?.default || ''
- const parsed = dc.config.parse(raw)
- dimWidth.value = parsed.width
- dimHeight.value = parsed.height
- }
-
- // 5. promptPlaceholder 同步
- if (config.promptPlaceholder) {
- promptPlaceholder.value = config.promptPlaceholder
- }
-}
-
-/**
- * 将专用 ref 的当前值回写到 paramValues
- * (在 buildTaskBody 之前调用)
- */
-export function syncParamValues(config, state) {
- const {
- paramValues, proportion, resolution, quantity,
- customWidth, customHight, dimWidth, dimHeight, quality,
- } = state
-
- const ratioParam = config?.params?.find(p => p.ui === 'proportion')
- if (ratioParam) paramValues[ratioParam.name] = proportion.value
-
- const resParam = config?.params?.find(p => p.ui === 'resolution')
- if (resParam) paramValues[resParam.name] = resolution.value
-
- const qtyParam = config?.params?.find(p => p.ui === 'quantity')
- if (qtyParam) paramValues[qtyParam.name] = quantity.value
-
- if (config?.params?.find(p => p.name === 'customWidth')) {
- paramValues.customWidth = customWidth.value
- }
- if (config?.params?.find(p => p.name === 'customHight')) {
- paramValues.customHight = customHight.value
- }
- if (config?.params?.find(p => p.name === 'quality')) {
- paramValues.quality = quality.value
- }
-
- const dc = getDimConfig(config)
- if (dc?.type === 'split') {
- paramValues[dc.wParam.name] = dimWidth.value
- paramValues[dc.hParam.name] = dimHeight.value
- } else if (dc?.type === 'combined') {
- paramValues[dc.paramName] = dc.config.format(dimWidth.value, dimHeight.value)
- }
-}
-```
-
-- [ ] **Step 2: 验证语法**
-
-Run: `npx eslint src/utils/modelConfigHelper.js --fix`
-Expected: 无错误
-
-- [ ] **Step 3: Commit**
-
-```bash
-git add src/utils/modelConfigHelper.js
-git commit -m "feat: 新增 modelConfigHelper 共享工具函数
-
-提取 getDimConfig / checkShowWhen / syncDefaults / syncParamValues,
-供 Painting 和 Video 平台共用。syncDefaults 新增 dimension.separator
-解析和 promptPlaceholder 同步能力。"
-```
-
----
-
-### Task 2: 新增 API 函数
-
-**Files:**
-- Modify: `src/apis/display/index.js`
-
-- [ ] **Step 1: 在文件末尾追加两个 API 函数**
-
-```js
-// 批量获取模型配置(POST /suanli/v1/models/configs)
-export function requestModelConfigsBatch(modelIds) {
- return service.post('/suanli/v1/models/configs', { modelIds })
-}
-
-// 单条查询模型配置(GET /suanli/v1/models/:modelId/config)
-export function requestModelConfig(modelId) {
- return service.get(`/suanli/v1/models/${modelId}/config`)
-}
-```
-
-- [ ] **Step 2: 验证语法**
-
-Run: `npx eslint src/apis/display/index.js --fix`
-Expected: 无错误
-
-- [ ] **Step 3: Commit**
-
-```bash
-git add src/apis/display/index.js
-git commit -m "feat: 新增模型配置 API(批量 + 单条)"
-```
-
----
-
-### Task 3: 添加缓存层
-
-**Files:**
-- Modify: `src/utils/modelApi.js`
-
-- [ ] **Step 1: 在文件末尾追加缓存相关函数**
-
-在 `clearPlatformModelCache` 函数之前插入以下代码:
-
-```js
-// ==================== 模型配置缓存 ====================
-
-const CONFIG_CACHE_PREFIX = 'model_config_'
-const CONFIG_CACHE_TTL = 60 * 1000 // 60 秒
-const pendingConfigRequests = new Map()
-
-// 导入 API 函数(在文件顶部添加)
-// import { requestModelConfigsBatch, requestModelConfig } from '@/apis/display/index.js'
-
-/**
- * 批量预加载模型配置到缓存
- * @param {string[]} modelIds - 模型 UUID 列表
- */
-export async function preloadModelConfigs(modelIds) {
- if (!modelIds.length) return
- const result = await requestModelConfigsBatch(modelIds)
- const data = result?.data || {}
- const now = Date.now()
- modelIds.forEach(id => {
- const config = data[id]
- if (config) {
- const cacheEntry = { config, timestamp: now }
- try {
- localStorage.setItem(CONFIG_CACHE_PREFIX + id, JSON.stringify(cacheEntry))
- } catch { /* localStorage 满时静默失败 */ }
- }
- })
-}
-
-/**
- * 获取单个模型配置(优先读缓存,未命中调 API)
- * @param {string} modelId - 模型 UUID
- * @returns {Promise