feat: document

This commit is contained in:
wangdan-fit2cloud 2025-06-05 15:09:01 +08:00
parent e63572b89b
commit 1817537269
5 changed files with 374 additions and 130 deletions

View File

@ -20,6 +20,7 @@ export default {
])
},
},
'app-document-active': {
iconReader: () => {
return h('i', [
@ -52,4 +53,88 @@ export default {
])
},
},
'app-close': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 16 16',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M7.96141 6.98572L12.4398 2.50738C12.5699 2.3772 12.781 2.3772 12.9112 2.50738L13.3826 2.97878C13.5127 3.10895 13.5127 3.32001 13.3826 3.45018L8.90422 7.92853L13.3826 12.4069C13.5127 12.537 13.5127 12.7481 13.3826 12.8783L12.9112 13.3497C12.781 13.4799 12.5699 13.4799 12.4398 13.3497L7.96141 8.87134L3.48307 13.3497C3.35289 13.4799 3.14184 13.4799 3.01166 13.3497L2.54026 12.8783C2.41008 12.7481 2.41008 12.537 2.54026 12.4069L7.0186 7.92853L2.54026 3.45018C2.41008 3.32001 2.41008 3.10895 2.54026 2.97878L3.01166 2.50738C3.14184 2.3772 3.35289 2.3772 3.48307 2.50738L7.96141 6.98572Z',
fill: 'currentColor',
}),
],
),
])
},
},
'app-document-refresh': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M512 170.666667a85.333333 85.333333 0 0 1 85.333333-85.333334h256a85.333333 85.333333 0 0 1 85.333334 85.333334v256a85.333333 85.333333 0 0 1-85.333334 85.333333h-256a85.333333 85.333333 0 0 1-85.333333-85.333333V170.666667z m85.333333 0v256h256V170.666667h-256zM85.333333 597.333333a85.333333 85.333333 0 0 1 85.333334-85.333333h256a85.333333 85.333333 0 0 1 85.333333 85.333333v256a85.333333 85.333333 0 0 1-85.333333 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-256z m85.333334 0v256h256v-256H170.666667zM128 298.666667a213.333333 213.333333 0 0 1 213.333333-213.333334h85.333334v85.333334H341.333333a128 128 0 0 0-128 128h57.514667a12.8 12.8 0 0 1 9.728 21.12l-100.181333 116.906666a12.8 12.8 0 0 1-19.456 0l-100.181334-116.906666A12.8 12.8 0 0 1 70.485333 298.666667H128zM896 725.333333a213.333333 213.333333 0 0 1-213.333333 213.333334h-85.333334v-85.333334h85.333334a128 128 0 0 0 128-128v-21.333333h-57.514667a12.8 12.8 0 0 1-9.728-21.12l100.181333-116.906667a12.8 12.8 0 0 1 19.456 0l100.181334 116.906667a12.8 12.8 0 0 1-9.728 21.12H896v21.333333z',
fill: 'currentColor',
}),
],
),
])
},
},
'app-migrate': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M896.128 113.792a42.666667 42.666667 0 0 1 42.24 36.864l0.426667 5.802667v711.509333a42.666667 42.666667 0 0 1-36.906667 42.24l-5.76 0.426667h-263.082667a21.333333 21.333333 0 0 1-20.906666-17.066667l-0.426667-4.266667v-42.666666a21.333333 21.333333 0 0 1 17.066667-20.906667l4.266666-0.426667h220.416V199.125333H281.941333l0.042667 192.170667a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333V135.125333a21.333333 21.333333 0 0 1 17.066666-20.906666l4.266667-0.426667h678.144zM424.96 485.973333c6.272 0 12.373333 2.218667 17.152 6.272l178.858667 151.338667a26.538667 26.538667 0 0 1 0 40.533333l-178.858667 151.381334a26.538667 26.538667 0 0 1-43.690667-20.266667v-103.765333H135.168a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334H398.506667l-0.042667-113.621333c0-14.677333 11.904-26.538667 26.538667-26.538667z',
fill: 'currentColor'
})
]
)
])
}
},
'app-export': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M791.04 554.24l-386.432-1.728a21.248 21.248 0 0 1-21.12-21.248L383.36 490.88c-0.064-11.776 9.408-21.376 21.12-21.44h0.192l394.112 1.728-97.664-98.112a21.44 21.44 0 0 1 0-30.208l30.08-30.144a21.12 21.12 0 0 1 29.952 0l165.12 165.952a42.88 42.88 0 0 1 0 60.288l-165.12 165.952a21.12 21.12 0 0 1-30.016 0l-30.016-30.144a21.44 21.44 0 0 1 0-30.208L791.04 554.24z m-132.672-383.552H170.24v682.624h488.128c11.712 0 21.184 9.6 21.184 21.376v42.624a21.248 21.248 0 0 1-21.248 21.376h-530.56A42.56 42.56 0 0 1 85.376 896V128c0-23.552 19.008-42.688 42.496-42.688h530.56c11.712 0 21.184 9.6 21.184 21.376v42.624a21.248 21.248 0 0 1-21.248 21.376z',
fill: 'currentColor'
})
]
)
])
}
},
}

View File

@ -14,6 +14,7 @@ import AppTable from './app-table/index.vue'
import CodemirrorEditor from './codemirror-editor/index.vue'
import InfiniteScroll from './infinite-scroll/index.vue'
import ModelSelect from './model-select/index.vue'
import ReadWrite from './read-write/index.vue'
export default {
install(app: App) {
app.component('LogoFull', LogoFull)
@ -31,5 +32,6 @@ export default {
app.component('CodemirrorEditor', CodemirrorEditor)
app.component('InfiniteScroll', InfiniteScroll)
app.component('ModelSelect', ModelSelect)
app.component('ReadWrite', ReadWrite)
},
}

View File

@ -0,0 +1,126 @@
<template>
<div class="cursor w-full">
<slot name="read">
<div class="flex align-center" v-if="!isEdit" @dblclick="dblclick">
<auto-tooltip :content="data">
{{ data }}
</auto-tooltip>
<el-button
v-if="trigger === 'default' && showEditIcon"
class="ml-4"
@click.stop="editNameHandle"
text
>
<el-icon><EditPen /></el-icon>
</el-button>
</div>
</slot>
<slot>
<div class="flex align-center" @click.stop v-if="isEdit">
<div class="w-full">
<el-input
ref="inputRef"
v-model="writeValue"
:placeholder="$t('common.inputPlaceholder')"
autofocus
:maxlength="maxlength || '-'"
:show-word-limit="maxlength ? true : false"
@blur="isEdit = false"
@keyup.enter="submit"
clearable
></el-input>
</div>
<span class="ml-4">
<el-button type="primary" text @mousedown="submit" :disabled="loading">
<el-icon><Select /></el-icon>
</el-button>
</span>
<span>
<el-button text @click.stop="isEdit = false" :disabled="loading">
<el-icon><CloseBold /></el-icon>
</el-button>
</span>
</div>
</slot>
</div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, nextTick } from 'vue'
defineOptions({ name: 'ReadWrite' })
const props = defineProps({
data: {
type: String,
default: ''
},
showEditIcon: {
type: Boolean,
default: false
},
maxlength: {
type: Number,
default: () => 0
},
trigger: {
type: String,
default: 'default',
validator: (value: string) => ['default', 'dblclick', 'manual'].includes(value)
},
write: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['change', 'close'])
const inputRef = ref()
const isEdit = ref(false)
const writeValue = ref('')
const loading = ref(false)
watch(isEdit, (bool) => {
if (!bool) {
writeValue.value = ''
emit('close')
} else {
setTimeout(() => {
nextTick(() => {
inputRef.value?.focus()
})
}, 200)
}
})
watch(
() => props.write,
(bool) => {
if (bool && props.trigger === 'manual') {
editNameHandle()
} else {
isEdit.value = false
}
}
)
function dblclick() {
if (props.trigger === 'dblclick') {
editNameHandle()
}
}
function submit() {
loading.value = true
emit('change', writeValue.value)
setTimeout(() => {
isEdit.value = false
loading.value = false
}, 200)
}
function editNameHandle() {
writeValue.value = props.data
isEdit.value = true
}
onMounted(() => {})
</script>
<style lang="scss" scoped></style>

4
ui/src/enums/document.ts Normal file
View File

@ -0,0 +1,4 @@
export enum hitHandlingMethod {
optimization = 'views.document.hitHandlingMethod.optimization',
directly_return = 'views.document.hitHandlingMethod.directly_return'
}

View File

@ -9,50 +9,70 @@
<el-button
v-if="datasetDetail.type === 0"
type="primary"
@click="router.push({ path: '/dataset/upload', query: { id: id } })"
@click="router.push({ path: '/knowledge/upload', query: { id: id } })"
>{{ $t('views.document.uploadDocument') }}
</el-button>
<el-button v-if="datasetDetail.type === 1" type="primary" @click="importDoc"
>{{ $t('views.document.importDocument') }}
</el-button>
<el-button
@click="syncMulDocument"
:disabled="multipleSelection.length === 0"
v-if="datasetDetail.type === 1"
>{{ $t('views.document.syncDocument') }}
</el-button>
<el-button
v-if="datasetDetail.type === 2"
type="primary"
@click="
router.push({
path: '/dataset/import',
query: { id: id, folder_token: datasetDetail.meta.folder_token },
})
"
>{{ $t('views.document.importDocument') }}
</el-button>
<el-button
@click="syncLarkMulDocument"
:disabled="multipleSelection.length === 0"
v-if="datasetDetail.type === 2"
>{{ $t('views.document.syncDocument') }}
</el-button>
<el-button @click="openDatasetDialog()" :disabled="multipleSelection.length === 0">
{{ $t('views.document.setting.migration') }}
</el-button>
<el-button @click="batchRefresh" :disabled="multipleSelection.length === 0">
{{ $t('views.knowledge.setting.vectorization') }}
</el-button>
<el-button @click="openGenerateDialog()" :disabled="multipleSelection.length === 0">
{{ $t('views.document.generateQuestion.title') }}
</el-button>
<el-button @click="openBatchEditDocument" :disabled="multipleSelection.length === 0">
{{ $t('common.setting') }}
<el-button @click="openDatasetDialog()" :disabled="multipleSelection.length === 0">
{{ $t('views.document.setting.migration') }}
</el-button>
<el-button @click="deleteMulDocument" :disabled="multipleSelection.length === 0">
{{ $t('common.delete') }}
<el-dropdown>
<el-button class="ml-12 mr-12">
<el-icon><MoreFilled /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@click="openBatchEditDocument"
:disabled="multipleSelection.length === 0"
>
{{ $t('common.setting') }}</el-dropdown-item
>
<el-dropdown-item
divided
@click="syncMulDocument"
:disabled="multipleSelection.length === 0"
v-if="datasetDetail.type === 1"
>{{ $t('views.document.syncDocument') }}</el-dropdown-item
>
<el-dropdown-item
divided
v-if="datasetDetail.type === 2"
type="primary"
@click="
router.push({
path: '/knowledge/import',
query: { id: id, folder_token: datasetDetail.meta.folder_token },
})
"
>{{ $t('views.document.importDocument') }}</el-dropdown-item
>
<el-dropdown-item
divided
@click="syncLarkMulDocument"
:disabled="multipleSelection.length === 0"
v-if="datasetDetail.type === 2"
>{{ $t('views.document.syncDocument') }}</el-dropdown-item
>
<el-dropdown-item
divided
@click="deleteMulDocument"
:disabled="multipleSelection.length === 0"
>{{ $t('common.delete') }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<el-input
@ -92,24 +112,6 @@
/>
</template>
</el-table-column>
<el-table-column
prop="char_length"
:label="$t('views.document.table.char_length')"
align="right"
min-width="90"
sortable
>
<template #default="{ row }">
{{ numberFormat(row.char_length) }}
</template>
</el-table-column>
<el-table-column
prop="paragraph_count"
:label="$t('views.document.table.paragraph')"
align="right"
min-width="90"
sortable
/>
<el-table-column
prop="status"
:label="$t('views.document.fileStatus.label')"
@ -187,6 +189,25 @@
<StatusValue :status="row.status" :status-meta="row.status_meta"></StatusValue>
</template>
</el-table-column>
<el-table-column
prop="char_length"
:label="$t('views.document.table.char_length')"
align="right"
min-width="90"
sortable
>
<template #default="{ row }">
{{ numberFormat(row.char_length) }}
</template>
</el-table-column>
<el-table-column
prop="paragraph_count"
:label="$t('views.document.table.paragraph')"
align="right"
min-width="90"
sortable
/>
<el-table-column width="130">
<template #header>
<div>
@ -227,13 +248,19 @@
</div>
</template>
<template #default="{ row }">
<div @click.stop>
<el-switch
:loading="loading"
size="small"
v-model="row.is_active"
:before-change="() => changeState(row)"
/>
<div v-if="row.is_active" class="flex align-center">
<el-icon class="color-success mr-8" style="font-size: 16px"
><SuccessFilled
/></el-icon>
<span class="color-secondary">
{{ $t('common.status.enabled') }}
</span>
</div>
<div v-else class="flex align-center">
<AppIcon iconName="app-disabled" class="color-secondary mr-8"></AppIcon>
<span class="color-secondary">
{{ $t('common.status.disabled') }}
</span>
</div>
</template>
</el-table-column>
@ -300,43 +327,50 @@
</el-table-column>
<el-table-column :label="$t('common.operation')" align="left" width="110" fixed="right">
<template #default="{ row }">
<span @click.stop>
<el-switch
:loading="loading"
size="small"
v-model="row.is_active"
:before-change="() => changeState(row)"
/>
</span>
<el-divider direction="vertical" />
<div v-if="datasetDetail.type === 0">
<span class="mr-4">
<el-tooltip
effect="dark"
<el-button
v-if="
([State.STARTED, State.PENDING] as Array<string>).includes(
getTaskState(row.status, TaskType.EMBEDDING),
)
"
:content="$t('views.document.setting.cancelVectorization')"
placement="top"
>
<el-button
type="primary"
text
@click.stop="cancelTask(row, TaskType.EMBEDDING)"
:title="$t('views.document.setting.cancelVectorization')"
>
<AppIcon iconName="app-close" style="font-size: 16px"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip
v-else
effect="dark"
:content="$t('views.dataset.setting.vectorization')"
placement="top"
>
<el-button type="primary" text @click.stop="refreshDocument(row)">
<AppIcon iconName="app-document-refresh" style="font-size: 16px"></AppIcon>
</el-button>
</el-tooltip>
</span>
<span class="mr-4">
<el-tooltip effect="dark" :content="$t('common.setting')" placement="top">
<el-button type="primary" text @click.stop="settingDoc(row)">
<el-button
type="primary"
text
@click.stop="refreshDocument(row)"
:title="$t('views.knowledge.setting.vectorization')"
>
<AppIcon iconName="app-document-refresh" style="font-size: 16px"></AppIcon>
</el-button>
</span>
<span class="mr-4">
<el-button
type="primary"
text
@click.stop="settingDoc(row)"
:title="$t('common.setting')"
>
<el-icon><Setting /></el-icon>
</el-button>
</el-tooltip>
</span>
<span @click.stop>
<el-dropdown trigger="click">
@ -382,46 +416,39 @@
</div>
<div v-if="datasetDetail.type === 1 || datasetDetail.type === 2">
<span class="mr-4">
<el-tooltip
effect="dark"
:content="$t('views.dataset.setting.sync')"
placement="top"
<el-button
type="primary"
text
@click.stop="syncDocument(row)"
:title="$t('views.knowledge.setting.sync')"
>
<el-button type="primary" text @click.stop="syncDocument(row)">
<el-icon><Refresh /></el-icon>
</el-button>
</el-tooltip>
</span>
<span class="mr-4">
<el-tooltip
effect="dark"
<el-button
v-if="
([State.STARTED, State.PENDING] as Array<string>).includes(
getTaskState(row.status, TaskType.EMBEDDING),
)
"
:content="$t('views.document.setting.cancelVectorization')"
placement="top"
>
<el-button
type="primary"
text
@click.stop="cancelTask(row, TaskType.EMBEDDING)"
:title="$t('views.document.setting.cancelVectorization')"
>
<AppIcon iconName="app-close" style="font-size: 16px"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip
effect="dark"
<el-button
v-else
:content="$t('views.dataset.setting.vectorization')"
placement="top"
type="primary"
text
@click.stop="refreshDocument(row)"
:title="$t('views.knowledge.setting.vectorization')"
>
<el-button type="primary" text @click.stop="refreshDocument(row)">
<AppIcon iconName="app-document-refresh" style="font-size: 16px"></AppIcon>
</el-button>
</el-tooltip>
</span>
<span @click.stop>
@ -480,6 +507,7 @@
<SelectDatasetDialog ref="SelectDatasetDialogRef" @refresh="refreshMigrate" />
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" @refresh="getList" />
</div>
</el-card>
<div class="mul-operation w-full flex" v-if="multipleSelection.length !== 0">
<el-button :disabled="multipleSelection.length === 0" @click="cancelTaskHandle(1)">
{{ $t('views.document.setting.cancelVectorization') }}
@ -496,7 +524,6 @@
</el-button>
</div>
<EmbeddingContentDialog ref="embeddingContentDialogRef"></EmbeddingContentDialog>
</el-card>
</div>
</template>
<script setup lang="ts">
@ -508,8 +535,8 @@ import ImportDocumentDialog from './component/ImportDocumentDialog.vue'
import SyncWebDialog from '@/views/knowledge/component/SyncWebDialog.vue'
import SelectDatasetDialog from './component/SelectDatasetDialog.vue'
import { numberFormat } from '@/utils/common'
// import { datetimeFormat } from '@/utils/time'
// import { hitHandlingMethod } from '@/enums/document'
import { datetimeFormat } from '@/utils/time'
import { hitHandlingMethod } from '@/enums/document'
import { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'
import useStore from '@/stores'
import StatusValue from '@/views/document/component/Status.vue'