feat: 完善范式润色功能和相关组件

- 新增范式选择模态框 (ParadigmSelectorModal)
- 新增差异标注面板 (DiffAnnotationPanel)
- 新增精确差异处理工具 (preciseDiff.js)
- 更新各面板组件支持范式润色功能
- 更新范式配置文件 (paradigms.js)
- 更新依赖配置

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-10 03:02:21 +08:00
parent 56fda55709
commit f9f0785106
14 changed files with 1426 additions and 493 deletions

View File

@@ -2,17 +2,9 @@
<aside class="w-[400px] flex flex-col border-r border-slate-700 bg-slate-800 shrink-0 h-screen">
<!-- 头部 -->
<header class="p-4 border-b border-slate-700 flex items-center justify-between shrink-0">
<div class="flex items-center gap-4">
<h1 class="font-bold text-lg text-white flex items-center gap-2">
<span class="text-2xl"></span> 写作范式分析
</h1>
<button
@click="switchPage('writer')"
class="text-xs px-2 py-1 rounded bg-slate-700 text-slate-300 hover:bg-slate-600 transition"
>
返回写作
</button>
</div>
<h1 class="font-bold text-lg text-white flex items-center gap-2">
<span class="text-2xl">🎯</span> 写作范式分析
</h1>
<span class="text-xs px-2 py-1 rounded bg-blue-900 text-blue-300 border border-blue-700">Pro版</span>
</header>
@@ -219,159 +211,33 @@
</div>
</div>
<!-- 写作约束高级 -->
<div>
<label class="block text-sm text-slate-400 mb-2">写作约束每行一条</label>
<!-- 核心字段完整 Prompt -->
<div class="border-t border-slate-700 pt-4 mt-4">
<label class="block text-sm text-slate-400 mb-2 flex items-center gap-2">
<span></span> 完整 Prompt核心字段
<span class="text-xs text-slate-500 font-normal">粘贴您的完整提示词</span>
</label>
<textarea
v-model="editForm.constraintsInput"
rows="4"
class="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition resize-none font-mono"
placeholder="如:&#10;开篇必须明确阐述问题&#10;结尾需总结核心要点"
v-model="editForm.specializedPrompt"
rows="12"
class="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition resize-y font-mono leading-relaxed"
placeholder="在此粘贴您的完整 Prompt...
示例:
# Role
你是一个具有较高政治敏感性的AI助手...
# 任务目标
你需要逐一分析文章中的每一条问题...
# 检查步骤
1. 检查问题表述的清晰度
2. 深度分析思想根源
..."
></textarea>
</div>
<!-- 维度配置新架构 -->
<div class="border-t border-slate-700 pt-4 mt-4">
<div class="flex items-center justify-between mb-3">
<label class="text-sm text-slate-400 font-medium">📐 维度配置高级</label>
<button
@click="toggleDimensionEditor"
class="text-xs text-blue-400 hover:text-blue-300"
>
{{ showDimensionEditor ? '收起' : '展开' }}
</button>
</div>
<div v-if="showDimensionEditor" class="space-y-3">
<!-- 维度集选择 -->
<div>
<label class="block text-xs text-slate-500 mb-1">选择维度集模板</label>
<select
v-model="editForm.dimensionSetId"
@change="onDimensionSetChange"
class="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none"
>
<option :value="null">不使用维度集</option>
<option v-for="ds in dimensionSetOptions" :key="ds.id" :value="ds.id">
{{ ds.name }} - {{ ds.description }}
</option>
</select>
</div>
<!-- 维度列表编辑 -->
<div v-if="editForm.dimensions.length > 0">
<label class="block text-xs text-slate-500 mb-2">维度列表可编辑</label>
<div class="space-y-2 max-h-60 overflow-y-auto">
<div
v-for="(dim, index) in editForm.dimensions"
:key="dim.id || index"
class="bg-slate-900/50 rounded p-2 border border-slate-700"
>
<div class="flex items-center gap-2 mb-1">
<input
v-model="dim.name"
class="flex-1 bg-slate-800 border border-slate-600 rounded px-2 py-1 text-xs"
placeholder="维度名称"
/>
<button
@click="removeDimension(index)"
class="text-red-400 hover:text-red-300 text-xs px-1"
>
</button>
</div>
<input
v-model="dim.focus"
class="w-full bg-slate-800 border border-slate-600 rounded px-2 py-1 text-xs text-slate-400"
placeholder="关注重点(如:理想信念、宗旨意识)"
/>
</div>
</div>
<button
@click="addDimension"
class="mt-2 text-xs text-green-400 hover:text-green-300 flex items-center gap-1"
>
<span>+</span> 添加维度
</button>
</div>
<!-- 快速添加维度 -->
<div v-else class="text-center py-4 text-slate-500 text-xs">
<p>选择一个维度集模板或手动添加维度</p>
<button
@click="addDimension"
class="mt-2 text-green-400 hover:text-green-300"
>
+ 添加自定义维度
</button>
</div>
</div>
</div>
<!-- 素材库配置新增 -->
<div class="border-t border-slate-700 pt-4 mt-4">
<div class="flex items-center justify-between mb-3">
<label class="text-sm text-slate-400 font-medium">📚 素材库配置</label>
<button
@click="toggleReferenceEditor"
class="text-xs text-blue-400 hover:text-blue-300"
>
{{ showReferenceEditor ? '收起' : '展开' }}
</button>
</div>
<div v-if="showReferenceEditor" class="space-y-3">
<!-- 自动匹配开关 -->
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">自动匹配相关素材</span>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" v-model="editForm.autoMatchRefs" class="sr-only peer">
<div class="w-9 h-5 bg-slate-600 peer-focus:ring-2 peer-focus:ring-blue-500 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-blue-600"></div>
</label>
</div>
<!-- 素材类型筛选 -->
<div>
<label class="block text-xs text-slate-500 mb-2">手动选择素材按类型</label>
<div class="flex flex-wrap gap-1 mb-2">
<button
v-for="type in referenceTypes"
:key="type.id"
@click="filterReferencesByType(type.id)"
:class="['text-xs px-2 py-1 rounded transition',
selectedRefType === type.id
? 'bg-blue-600 text-white'
: 'bg-slate-700 text-slate-300 hover:bg-slate-600']"
>
{{ type.icon }} {{ type.name }}
</button>
</div>
</div>
<!-- 素材列表 -->
<div class="max-h-40 overflow-y-auto space-y-1">
<div
v-for="ref in filteredReferences"
:key="ref.id"
@click="toggleReference(ref.id)"
:class="['p-2 rounded cursor-pointer border transition text-xs',
editForm.selectedRefs.includes(ref.id)
? 'bg-blue-900/30 border-blue-500'
: 'bg-slate-900/50 border-slate-700 hover:border-slate-500']"
>
<div class="flex items-center justify-between">
<span class="font-medium text-white">{{ ref.title }}</span>
<span v-if="editForm.selectedRefs.includes(ref.id)" class="text-green-400"></span>
</div>
<div class="text-slate-500 text-[10px] mt-1">{{ ref.excerptCount }} 条可引用内容</div>
</div>
</div>
<!-- 已选素材摘要 -->
<div v-if="editForm.selectedRefs.length > 0" class="text-xs text-slate-400">
已选择 {{ editForm.selectedRefs.length }} 个素材
</div>
</div>
<p class="text-xs text-slate-500 mt-2">
💡 提示这是 AI 调用时的核心指令完整的 Prompt 应包含角色目标步骤和输出格式
</p>
</div>
</div>
@@ -401,17 +267,14 @@ import { storeToRefs } from 'pinia'
import { useAppStore } from '../stores/app'
import { useDatabaseStore } from '../stores/database.js'
import DeepSeekAPI from '../api/deepseek.js'
import { getParadigmList, getParadigmDimensions } from '../config/paradigms.js'
import { getDimensionSetList, getDimensionSetById } from '../config/dimensionSets.js'
import { getLogicParadigmList } from '../config/logicParadigms.js'
import { REFERENCE_TYPES } from '../config/references.js'
import { getParadigmList } from '../config/paradigms.js'
const appStore = useAppStore()
const { analysisText, isAnalyzing } = storeToRefs(appStore)
// 数据库 Store
const dbStore = useDatabaseStore()
const { references: dbReferences, isInitialized: dbInitialized } = storeToRefs(dbStore)
const { isInitialized: dbInitialized } = storeToRefs(dbStore)
// 选中的范式
const selectedParadigm = ref(null)
@@ -434,54 +297,10 @@ const editForm = reactive({
description: '',
tagsInput: '',
tagClass: 'bg-blue-900/30 text-blue-300',
constraintsInput: '',
// 新架构:维度配置
dimensionSetId: null,
dimensions: [],
// 素材库配置
autoMatchRefs: true,
selectedRefs: []
// ⭐ 核心字段:完整 Prompt
specializedPrompt: ''
})
// 维度编辑器状态
const showDimensionEditor = ref(false)
// 素材编辑器状态
const showReferenceEditor = ref(false)
const selectedRefType = ref('all')
// 使用数据库中的素材数据
const allReferences = computed(() => {
return dbReferences.value.map(ref => ({
id: ref.id,
type: ref.type,
title: ref.title,
source: ref.source,
tags: ref.tags,
excerptCount: ref.excerpts?.length || 0
}))
})
const filteredReferences = computed(() => {
if (selectedRefType.value === 'all') {
return allReferences.value
}
return allReferences.value.filter(ref => ref.type === selectedRefType.value)
})
// 素材类型选项
const referenceTypes = [
{ id: 'all', name: '全部', icon: '📚' },
{ id: REFERENCE_TYPES.POLICY, name: '政策文件', icon: '📄' },
{ id: REFERENCE_TYPES.SPEECH, name: '领导讲话', icon: '🎙️' },
{ id: REFERENCE_TYPES.CASE, name: '典型案例', icon: '📌' },
{ id: REFERENCE_TYPES.QUOTE, name: '金句警句', icon: '✨' },
{ id: REFERENCE_TYPES.REGULATION, name: '党规党纪', icon: '📖' }
]
// 维度集选项
const dimensionSetOptions = getDimensionSetList()
// 图标选项
const iconOptions = ['📝', '💻', '📊', '🚀', '📚', '🏛️', '🔥', '🏢', '💡', '🎯', '📋', '✨']
@@ -495,6 +314,7 @@ const colorOptions = [
{ label: '青色', class: 'bg-cyan-900/30 text-cyan-300' }
]
// 初始化范式列表
const initParadigms = () => {
// 先加载默认范式
@@ -538,28 +358,13 @@ const openEditModal = (paradigm) => {
editForm.description = paradigm.description || ''
editForm.tagsInput = (paradigm.tags || []).join(', ')
editForm.tagClass = paradigm.tagClass || 'bg-blue-900/30 text-blue-300'
editForm.constraintsInput = (paradigm.systemConstraints || []).join('\n')
// ⭐ 核心字段:加载完整 Prompt
editForm.specializedPrompt = paradigm.specializedPrompt || ''
// 加载维度配置
editForm.dimensionSetId = paradigm.dimensionSetId || null
if (paradigm.dimensionSetId) {
const dimSet = getDimensionSetById(paradigm.dimensionSetId)
editForm.dimensions = dimSet ? JSON.parse(JSON.stringify(dimSet.dimensions)) : []
} else if (paradigm.customDimensions) {
editForm.dimensions = JSON.parse(JSON.stringify(paradigm.customDimensions))
} else {
editForm.dimensions = []
}
// 加载素材配置
editForm.autoMatchRefs = paradigm.autoMatchRefs !== false // 默认为true
editForm.selectedRefs = paradigm.selectedRefs ? [...paradigm.selectedRefs] : []
showDimensionEditor.value = false
showReferenceEditor.value = false
showEditModal.value = true
}
// 关闭弹窗
const closeEditModal = () => {
showEditModal.value = false
@@ -573,84 +378,15 @@ const resetEditForm = () => {
editForm.description = ''
editForm.tagsInput = ''
editForm.tagClass = 'bg-blue-900/30 text-blue-300'
editForm.constraintsInput = ''
editForm.dimensionSetId = null
editForm.dimensions = []
// 素材配置重置
editForm.autoMatchRefs = true
editForm.selectedRefs = []
showDimensionEditor.value = false
showReferenceEditor.value = false
// ⭐ 核心字段重置
editForm.specializedPrompt = ''
}
// 切换维度编辑器显示
const toggleDimensionEditor = () => {
showDimensionEditor.value = !showDimensionEditor.value
}
// 维度集变更时更新维度列表
const onDimensionSetChange = () => {
if (editForm.dimensionSetId) {
const dimSet = getDimensionSetById(editForm.dimensionSetId)
if (dimSet) {
// 深拷贝维度列表,允许用户编辑
editForm.dimensions = JSON.parse(JSON.stringify(dimSet.dimensions))
}
} else {
editForm.dimensions = []
}
}
// 添加自定义维度
const addDimension = () => {
editForm.dimensions.push({
id: `custom-dim-${Date.now()}`,
name: '',
focus: ''
})
}
// 删除维度
const removeDimension = (index) => {
editForm.dimensions.splice(index, 1)
}
// 切换素材编辑器显示
const toggleReferenceEditor = () => {
showReferenceEditor.value = !showReferenceEditor.value
}
// 按类型筛选素材通过修改selectedRefType触发computed重新计算
const filterReferencesByType = (typeId) => {
selectedRefType.value = typeId
}
// 切换素材选中状态
const toggleReference = (refId) => {
const index = editForm.selectedRefs.indexOf(refId)
if (index === -1) {
editForm.selectedRefs.push(refId)
} else {
editForm.selectedRefs.splice(index, 1)
}
}
// 保存范式
const saveParadigm = () => {
const tags = editForm.tagsInput.split(',').map(t => t.trim()).filter(t => t)
const constraints = editForm.constraintsInput.split('\n').map(c => c.trim()).filter(c => c)
// 处理维度配置
const dimensionConfig = {
dimensionSetId: editForm.dimensionSetId,
customDimensions: editForm.dimensions.length > 0 ? editForm.dimensions : null
}
// 处理素材配置
const referenceConfig = {
autoMatchRefs: editForm.autoMatchRefs,
selectedRefs: editForm.selectedRefs
}
if (isAddMode.value) {
// 新增自定义范式
@@ -658,20 +394,12 @@ const saveParadigm = () => {
id: `custom-${Date.now()}`,
icon: editForm.icon,
name: editForm.name,
description: editForm.description,
description: editForm.description || editForm.specializedPrompt.substring(0, 100) + '...',
tags,
tagClass: editForm.tagClass,
systemConstraints: constraints,
isCustom: true,
// 新架构:维度配置
...dimensionConfig,
// 素材配置
...referenceConfig,
logicParadigms: {
problemSection: 'progressive-problem',
analysisSection: 'deep-attribution',
remediationSection: 'remediation'
}
// ⭐ 核心字段:完整 Prompt
specializedPrompt: editForm.specializedPrompt
}
paradigms.value.push(newParadigm)
@@ -692,11 +420,8 @@ const saveParadigm = () => {
description: editForm.description,
tags,
tagClass: editForm.tagClass,
systemConstraints: constraints,
// 新架构:维度配置
...dimensionConfig,
// 素材配置
...referenceConfig
// ⭐ 核心字段:完整 Prompt
specializedPrompt: editForm.specializedPrompt
}
paradigms.value[index] = updatedParadigm
@@ -721,11 +446,7 @@ const saveParadigm = () => {
description: editForm.description,
tags,
tagClass: editForm.tagClass,
systemConstraints: constraints,
// 新架构:维度配置
...dimensionConfig,
// 素材配置
...referenceConfig
specializedPrompt: editForm.specializedPrompt
}
localStorage.setItem('paradigmCustomizations', JSON.stringify(customizations))
}
@@ -735,6 +456,7 @@ const saveParadigm = () => {
closeEditModal()
}
// 删除自定义范式
const deleteParadigm = (paradigm) => {
if (!paradigm.isCustom) return