feat(frontend): 更新三种切分模式与PDF定位
This commit is contained in:
@@ -66,7 +66,13 @@ const locationText = computed(() => {
|
||||
})
|
||||
|
||||
function pageForItem(item: PreviewItem | null) {
|
||||
if (!item || item.sourceStart == null || item.sourceEnd == null) return null
|
||||
if (!item) return null
|
||||
if (item.sourceStart == null || item.sourceEnd == null) {
|
||||
const pageNumber = item.sourcePages?.[0]
|
||||
return pageNumber == null
|
||||
? null
|
||||
: pageRanges.value.find((page) => page.page_number === pageNumber) ?? null
|
||||
}
|
||||
return pageRanges.value.find((page) => (
|
||||
item.sourceStart! >= page.source_start && item.sourceStart! < page.source_end
|
||||
)) ?? pageRanges.value.find((page) => (
|
||||
@@ -76,7 +82,10 @@ function pageForItem(item: PreviewItem | null) {
|
||||
|
||||
function selectedTextForPage(page: DataProcessPdfPageRange) {
|
||||
const item = props.selectedItem
|
||||
if (!item || item.sourceStart == null || item.sourceEnd == null) return ''
|
||||
if (!item) return ''
|
||||
if (item.sourceStart == null || item.sourceEnd == null) {
|
||||
return item.sourcePages?.includes(page.page_number) ? item.originalContent : ''
|
||||
}
|
||||
const intersectionStart = Math.max(item.sourceStart, page.source_start)
|
||||
const intersectionEnd = Math.min(item.sourceEnd, page.source_end)
|
||||
if (intersectionEnd <= intersectionStart) return ''
|
||||
@@ -145,7 +154,10 @@ function applySelectedHighlight(page: DataProcessPdfPageRange | null) {
|
||||
for (const element of textLayer?.textDivs ?? []) {
|
||||
element.classList.remove('is-slice-highlighted')
|
||||
}
|
||||
if (!item || item.sourceStart == null || item.sourceEnd == null) {
|
||||
if (!item || (
|
||||
(item.sourceStart == null || item.sourceEnd == null)
|
||||
&& !item.sourcePages?.length
|
||||
)) {
|
||||
highlightState.value = item ? 'manual' : 'idle'
|
||||
return
|
||||
}
|
||||
@@ -248,7 +260,10 @@ async function renderCurrentPage(force = false) {
|
||||
async function locateSelectedItem() {
|
||||
if (!documentRef.value) return
|
||||
const item = props.selectedItem
|
||||
if (!item || item.sourceStart == null || item.sourceEnd == null) {
|
||||
if (!item || (
|
||||
(item.sourceStart == null || item.sourceEnd == null)
|
||||
&& !item.sourcePages?.length
|
||||
)) {
|
||||
applySelectedHighlight(null)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -118,6 +118,11 @@ function itemNumber(item: PreviewItem) {
|
||||
}
|
||||
|
||||
function lineRange(item: PreviewItem) {
|
||||
if (item.sourcePages?.length) {
|
||||
const first = item.sourcePages[0]
|
||||
const last = item.sourcePages[item.sourcePages.length - 1]
|
||||
return first === last ? `来源:第 ${first} 页` : `来源:第 ${first}–${last} 页`
|
||||
}
|
||||
if (item.sourceStartLine == null || item.sourceEndLine == null) return '手动新增,无源文件定位'
|
||||
return item.sourceStartLine === item.sourceEndLine
|
||||
? `来源:第 ${item.sourceStartLine} 行`
|
||||
|
||||
@@ -75,9 +75,6 @@ const chunkValidationMessage = computed(() => {
|
||||
) {
|
||||
return '重叠长度与最小切片长度之和不能大于切片长度'
|
||||
}
|
||||
if (props.unstructuredOptions.chunkMethod === 'custom' && !props.unstructuredOptions.customDelimiter.trim()) {
|
||||
return '请输入自定义分隔符'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
|
||||
@@ -30,15 +30,16 @@ const SMART_PREPROCESS_OPTIONS: UnstructuredPreprocessOption[] = [
|
||||
]
|
||||
|
||||
const CHUNK_METHODS: Array<{ value: ChunkMethod; label: string }> = [
|
||||
{ value: 'structure', label: '文档结构' },
|
||||
{ value: 'fixed', label: '固定 Token' },
|
||||
{ value: 'custom', label: '自定义分隔符' },
|
||||
{ value: 'layout_hybrid', label: '版面结构混合切分' },
|
||||
{ value: 'semantic', label: '语义切分' },
|
||||
{ value: 'fixed', label: '固定 Token 切分' },
|
||||
]
|
||||
|
||||
const UNSTRUCTURED_NUMBER_LIMITS = {
|
||||
chunkSize: { min: 200, max: 2000 },
|
||||
chunkOverlap: { min: 0, max: 500 },
|
||||
minChunkSize: { min: 20, max: 500 },
|
||||
semanticBreakpointPercentile: { min: 1, max: 99 },
|
||||
qaPairsPerChunk: { min: 1, max: 3 },
|
||||
} as const
|
||||
|
||||
@@ -160,7 +161,7 @@ defineExpose({ revealValidation })
|
||||
<div class="section-title-row">
|
||||
<div>
|
||||
<h3>切分选项</h3>
|
||||
<p>按文档结构、固定 Token 或自定义分隔符拆分长文档</p>
|
||||
<p>优先保留文档版面结构,也可按主题变化或固定 Token 切分</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -179,7 +180,9 @@ defineExpose({ revealValidation })
|
||||
:value="method.value"
|
||||
/>
|
||||
</el-select>
|
||||
<small>推荐使用文档结构,按标题、段落和句子边界保留完整内容块</small>
|
||||
<small v-if="options.chunkMethod === 'layout_hybrid'">默认推荐,按标题、段落、列表和表格结构切分,并合并过短内容块</small>
|
||||
<small v-else-if="options.chunkMethod === 'semantic'">根据相邻内容的语义变化寻找主题边界,适合长文章和知识材料</small>
|
||||
<small v-else>按句子边界控制固定 Token 长度,速度快且结果稳定</small>
|
||||
</label>
|
||||
|
||||
<label class="config-field">
|
||||
@@ -235,17 +238,22 @@ defineExpose({ revealValidation })
|
||||
<small>过短的尾部内容会尽量并入前一个切片</small>
|
||||
</label>
|
||||
|
||||
<label v-if="options.chunkMethod === 'custom'" class="config-field">
|
||||
<span class="config-field-label">自定义分隔符</span>
|
||||
<el-input
|
||||
:model-value="options.customDelimiter"
|
||||
maxlength="40"
|
||||
show-word-limit
|
||||
placeholder="例如:--- 或 ###"
|
||||
aria-label="自定义分隔符"
|
||||
@update:model-value="updateField('customDelimiter', $event)"
|
||||
/>
|
||||
<small>系统会优先在分隔符位置结束当前切片</small>
|
||||
<label v-if="options.chunkMethod === 'semantic'" class="config-field">
|
||||
<span class="config-field-label">语义断点百分位</span>
|
||||
<span class="unit-input">
|
||||
<el-input-number
|
||||
:model-value="options.semanticBreakpointPercentile"
|
||||
:min="1"
|
||||
:max="99"
|
||||
:step="1"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
aria-label="语义断点百分位"
|
||||
@update:model-value="updateUnstructuredNumber('semanticBreakpointPercentile', $event)"
|
||||
/>
|
||||
<span>%</span>
|
||||
</span>
|
||||
<small>百分位越低切片越多,推荐保持 95%</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -30,11 +30,11 @@ export function createDefaultUnstructuredOptions(): UnstructuredProcessOptions {
|
||||
'deduplicate_content',
|
||||
'preserve_context',
|
||||
],
|
||||
chunkMethod: 'structure',
|
||||
chunkMethod: 'layout_hybrid',
|
||||
chunkSize: 800,
|
||||
chunkOverlap: 100,
|
||||
minChunkSize: 100,
|
||||
customDelimiter: '',
|
||||
semanticBreakpointPercentile: 95,
|
||||
preserveTables: true,
|
||||
preserveCodeBlocks: true,
|
||||
preserveLists: true,
|
||||
|
||||
@@ -46,7 +46,7 @@ export type UnstructuredPreprocessOption =
|
||||
| 'desensitize'
|
||||
| 'preserve_context'
|
||||
|
||||
export type ChunkMethod = 'structure' | 'fixed' | 'custom'
|
||||
export type ChunkMethod = 'layout_hybrid' | 'semantic' | 'fixed'
|
||||
|
||||
export interface UnstructuredProcessOptions extends GenerationControlOptions {
|
||||
preprocessOptions: UnstructuredPreprocessOption[]
|
||||
@@ -54,7 +54,7 @@ export interface UnstructuredProcessOptions extends GenerationControlOptions {
|
||||
chunkSize: number
|
||||
chunkOverlap: number
|
||||
minChunkSize: number
|
||||
customDelimiter: string
|
||||
semanticBreakpointPercentile: number
|
||||
preserveTables: boolean
|
||||
preserveCodeBlocks: boolean
|
||||
preserveLists: boolean
|
||||
@@ -110,6 +110,7 @@ export interface PreviewItem {
|
||||
sourceEnd: number | null
|
||||
sourceStartLine: number | null
|
||||
sourceEndLine: number | null
|
||||
sourcePages?: number[]
|
||||
tokenCount: number
|
||||
status: 'original' | 'modified' | 'manual' | 'invalid'
|
||||
qualityScore?: number
|
||||
|
||||
Reference in New Issue
Block a user