补齐模型管理与基础生成预览链路

This commit is contained in:
zwt13703
2026-07-02 15:00:11 +08:00
parent b15a3a1f18
commit a2bad58591
10 changed files with 850 additions and 52 deletions
+192 -19
View File
@@ -1,21 +1,194 @@
<template><div style="display:flex;height:calc(100vh - 112px)"><div style="width:220px;border-right:1px solid #f0f0f0;overflow-y:auto;padding:12px"><h4 style="margin-bottom:12px">段落列表</h4><div v-for="(p,i) in paragraphs" :key="p.id" :class="['para-item',{active:selectedId===p.id}]" @click="selectPara(p.id)"><span class="idx">{{i+1}}</span><span class="title">{{p.title||'未命名'}}</span><a-tag :color="p.edit_mode==='ai'?'blue':'orange'" style="margin-left:auto">{{p.edit_mode==='ai'?'AI':'手动'}}</a-tag></div></div><div style="flex:1;overflow-y:auto;padding:24px;background:#f0f1f3;display:flex;justify-content:center;align-items:flex-start"><div style="width:794px;background:#fff;padding:80px 72px;min-height:500px;box-shadow:0 2px 8px rgba(0,0,0,.08)" v-html="previewHtml"></div></div><div style="width:380px;border-left:1px solid #f0f0f0;overflow-y:auto;padding:16px"><h4>段落配置</h4><div v-if="selectedPara"><a-form layout="vertical"><a-form-item label="编辑方式"><a-select v-model:value="selectedPara.edit_mode"><a-select-option value="ai">AI 生成</a-select-option><a-select-option value="manual">人工编辑</a-select-option></a-select></a-form-item><a-form-item v-if="selectedPara.edit_mode==='ai'" label="生成模型"><a-select v-model:value="selectedPara.model_id" allowClear placeholder="使用默认模型"><a-select-option v-for="m in models" :key="m.id" :value="m.id">{{m.name}}</a-select-option></a-select></a-form-item><a-form-item label="输出格式"><a-select v-model:value="selectedPara.output_format"><a-select-option value="text">正式报告段落</a-select-option><a-select-option value="table">表格形式</a-select-option><a-select-option value="mixed">混合内容</a-select-option><a-select-option value="chart">图表形式</a-select-option></a-select></a-form-item><a-form-item label="需要提示词"><a-switch v-model:checked="selectedPara.need_prompt" /></a-form-item><a-form-item v-if="selectedPara.need_prompt" label="预设提示词"><a-textarea v-model:value="selectedPara.prompt_text" :rows="4" placeholder="在此输入提示词..." /></a-form-item><a-form-item label="需要参考文件"><a-switch v-model:checked="selectedPara.need_file" /></a-form-item><a-form-item v-if="selectedPara.need_file" label="备注说明"><a-textarea v-model:value="selectedPara.file_note" :rows="2" placeholder="提示用户上传什么文件" /></a-form-item></a-form><a-button v-if="selectedPara.edit_mode==='ai'" type="primary" block @click="testPara">立即测试</a-button></div></div></div></template>
<template>
<div style="display:flex;height:calc(100vh - 112px)">
<div style="width:220px;border-right:1px solid #f0f0f0;overflow-y:auto;padding:12px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
<h4 style="margin:0">段落列表</h4>
<a-button type="primary" size="small" @click="saveTemplate">保存</a-button>
</div>
<div
v-for="(p, i) in paragraphs"
:key="p.id"
:class="['para-item', { active: selectedId === p.id }]"
@click="selectPara(p.id)"
>
<span class="idx">{{ i + 1 }}</span>
<span class="title">{{ p.title || '未命名' }}</span>
<a-tag :color="p.edit_mode === 'ai' ? 'blue' : 'orange'" style="margin-left:auto">
{{ p.edit_mode === 'ai' ? 'AI' : '手动' }}
</a-tag>
</div>
</div>
<div style="flex:1;overflow-y:auto;padding:24px;background:#f0f1f3;display:flex;justify-content:center;align-items:flex-start">
<div style="width:794px;background:#fff;padding:80px 72px;min-height:500px;box-shadow:0 2px 8px rgba(0,0,0,.08)" v-html="previewHtml" />
</div>
<div style="width:380px;border-left:1px solid #f0f0f0;overflow-y:auto;padding:16px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
<h4 style="margin:0">段落配置</h4>
<a-button @click="saveTemplate">保存配置</a-button>
</div>
<div v-if="selectedPara">
<a-form layout="vertical">
<a-form-item label="编辑方式">
<a-select v-model:value="selectedPara.edit_mode">
<a-select-option value="ai">AI 生成</a-select-option>
<a-select-option value="manual">人工编辑</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="selectedPara.edit_mode === 'ai'" label="生成模型">
<a-select v-model:value="selectedPara.model_id" allowClear placeholder="使用默认模型">
<a-select-option v-for="m in models" :key="m.id" :value="m.id">{{ m.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="输出格式">
<a-select v-model:value="selectedPara.output_format">
<a-select-option value="text">正式报告段落</a-select-option>
<a-select-option value="table">表格形式</a-select-option>
<a-select-option value="mixed">混合内容</a-select-option>
<a-select-option value="chart">图表形式</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="需要提示词">
<a-switch v-model:checked="selectedPara.need_prompt" />
</a-form-item>
<a-form-item v-if="selectedPara.need_prompt" label="预设提示词">
<a-textarea v-model:value="selectedPara.prompt_text" :rows="4" placeholder="在此输入提示词..." />
</a-form-item>
<a-form-item label="需要参考文件">
<a-switch v-model:checked="selectedPara.need_file" />
</a-form-item>
<a-form-item v-if="selectedPara.need_file" label="备注说明">
<a-textarea v-model:value="selectedPara.file_note" :rows="2" placeholder="提示用户上传什么文件" />
</a-form-item>
</a-form>
<a-button v-if="selectedPara.edit_mode === 'ai'" type="primary" block @click="testPara">立即测试</a-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useTemplateStore } from "@/stores/template";
import { useModelStore } from "@/stores/model";
import { message } from "ant-design-vue";
const route = useRoute();
const router = useRouter();
const store = useTemplateStore();
const modelStore = useModelStore();
const paragraphs = ref<any[]>([]);
const models = ref<any[]>([]);
const selectedId = ref(0);
const selectedPara = computed(()=>paragraphs.value.find(p=>p.id===selectedId.value));
const previewHtml = computed(()=>{return paragraphs.value.map(p=>'<div style="margin:8px 0;padding:8px;border:2px solid transparent;border-radius:4px'+(selectedId.value===p.id?';border-color:#5b5bd6;background:#f0f0ff':'')+'"><h3>'+(p.title||"")+'</h3><p>'+(p.content||"点击左侧配置此段落")+'</p></div>').join("")});
onMounted(async()=>{const id = Number(route.params.id);await store.fetchOne(id);paragraphs.value=store.paragraphs as any;await modelStore.fetchList();models.value=modelStore.models as any;if(paragraphs.value.length)selectedId.value=paragraphs.value[0].id});
function selectPara(id:number){selectedId.value=id;const el=document.getElementById('para-'+id);if(el)el.scrollIntoView({behavior:'smooth',block:'center'})}
async function testPara(){message.info('段落测试功能待实现')}
import { computed, ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { Modal, message } from 'ant-design-vue'
import { useTemplateStore } from '@/stores/template'
import { useModelStore } from '@/stores/model'
import { generateApi } from '@/api/generate'
const route = useRoute()
const store = useTemplateStore()
const modelStore = useModelStore()
const paragraphs = ref<any[]>([])
const models = ref<any[]>([])
const selectedId = ref(0)
const selectedPara = computed(() => paragraphs.value.find((item) => item.id === selectedId.value))
const previewHtml = computed(() =>
paragraphs.value
.map(
(item) =>
`<div style="margin:8px 0;padding:8px;border:2px solid transparent;border-radius:4px${
selectedId.value === item.id ? ';border-color:#5b5bd6;background:#f0f0ff' : ''
}"><h3>${item.title || ''}</h3><p>${item.content || '点击左侧配置此段落'}</p></div>`,
)
.join(''),
)
function selectPara(id: number) {
selectedId.value = id
}
async function saveTemplate() {
const templateId = Number(route.params.id)
await store.save(templateId)
message.success('模板配置已保存')
}
async function testPara() {
if (!selectedPara.value) {
return
}
const templateId = Number(route.params.id)
const response: any = await generateApi.test({
paragraph_id: selectedPara.value.id,
template_id: templateId,
prompt_text: selectedPara.value.prompt_text || '',
model_id: selectedPara.value.model_id || 0,
file_paths: [],
})
const blocks = response.data?.content?.content || []
const text = blocks
.map((block: any) => {
if (block.type === 'table') {
return `${block.title || '表格'}${(block.rows || []).length}`
}
return block.text || ''
})
.filter(Boolean)
.join('\n\n')
Modal.info({
title: '段落测试结果',
width: 640,
content: text || '当前未返回可展示内容。',
})
}
onMounted(async () => {
const id = Number(route.params.id)
await store.fetchOne(id)
paragraphs.value = store.paragraphs as any
await modelStore.fetchList()
models.value = modelStore.models as any
if (paragraphs.value.length) {
selectedId.value = paragraphs.value[0].id
}
})
</script>
<style scoped>.para-item{display:flex;align-items:center;gap:8px;padding:8px;border-radius:6px;cursor:pointer;margin-bottom:2px;font-size:13px}.para-item:hover{background:#f5f5f5}.para-item.active{background:#f0f0ff;color:#5b5bd6;font-weight:500}.para-item .idx{width:20px;height:20px;border-radius:50%;background:#f0f0f0;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0}.para-item.active .idx{background:#5b5bd6;color:#fff}.para-item .title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}</style>
<style scoped>
.para-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 6px;
cursor: pointer;
margin-bottom: 2px;
font-size: 13px;
}
.para-item:hover {
background: #f5f5f5;
}
.para-item.active {
background: #f0f0ff;
color: #5b5bd6;
font-weight: 500;
}
.para-item .idx {
width: 20px;
height: 20px;
border-radius: 50%;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 700;
flex-shrink: 0;
}
.para-item.active .idx {
background: #5b5bd6;
color: #fff;
}
.para-item .title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>