feat: knowledge
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run

This commit is contained in:
wangdan-fit2cloud 2025-06-04 18:08:52 +08:00
parent a0291bea88
commit a84c287963
26 changed files with 990 additions and 442 deletions

View File

@ -2,6 +2,7 @@ import { Result } from '@/request/Result'
import { get, post, del, put } from '@/request/index'
import { type Ref } from 'vue'
import type { pageRequest } from '@/api/type/common'
import type { knowledgeData } from '@/api/type/knowledge'
const prefix = '/workspace'
/**
@ -99,10 +100,63 @@ const getKnowledgeDetail: (
return get(`${prefix}/${wordspace_id}/knowledge/${knowledge_id}`, undefined, loading)
}
/**
*
* @param
* {
"name": "string",
"folder_id": "string",
"desc": "string",
"embedding": "string"
}
*/
const postDataset: (
wordspace_id: string,
data: knowledgeData,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (wordspace_id, data, loading) => {
return post(`${prefix}/${wordspace_id}/knowledge/base`, data, undefined, loading, 1000 * 60 * 5)
}
/**
* Web知识库
* @param
* {
"name": "string",
"desc": "string",
"source_url": "string",
"selector": "string",
}
*/
const postWebDataset: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
data,
loading,
) => {
return post(`${prefix}/web`, data, undefined, loading)
}
/**
* Lark知识库
* @param
* {
"name": "string",
"desc": "string",
"app_id": "string",
"app_secret": "string",
"folder_token": "string",
}
*/
const postLarkDataset: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
data,
loading,
) => {
return post(`${prefix}/lark/save`, data, undefined, loading)
}
export default {
getKnowledgeByFolder,
getKnowledgeList,
putReEmbeddingDataset,
putSyncWebKnowledge,
getKnowledgeDetail,
postDataset,
}

View File

@ -1,9 +1,9 @@
interface knowledgeData {
name: String
folder_id?: String
desc: String
embedding?: String
documents?: Array<any>
type?: String
embedding_model_id?: String
}
export type { knowledgeData }

View File

@ -0,0 +1,17 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.4383 2.66666H18.5646H18.6762L18.79 2.66723L18.9059 2.66784L19.0239 2.66868L19.144 2.66975L19.2662 2.67107L19.3906 2.67264L19.4746 2.67384L19.5595 2.67515L19.6886 2.67735L19.8196 2.67984L19.9527 2.68262L20.0426 2.68464L20.1789 2.68793L20.3172 2.69154L20.4575 2.69547L20.552 2.69829L20.6954 2.70279L20.8407 2.70765L20.9386 2.71108L21.0373 2.71469L21.1369 2.71845L21.2372 2.72239L21.3383 2.7265L21.4403 2.73078L21.5947 2.73753L21.6986 2.74226L21.8559 2.74971L21.9618 2.7549L22.0684 2.76029L22.2298 2.76873L22.3383 2.7746L22.4476 2.78068L22.5576 2.78695L22.6683 2.79344L22.7798 2.80013L22.892 2.80703L23.0049 2.81415L23.1185 2.82148L23.2329 2.82903L23.3479 2.83681C27.4788 3.11911 28.5728 6.09394 28.6892 6.44991L28.6954 6.46902L28.7001 6.48468L30.4934 6.58232C30.5891 6.58232 30.6667 6.6599 30.6667 6.7556C30.6667 6.82419 30.6268 6.88347 30.569 6.91155C28.7283 7.90759 28.1659 9.9315 28.4965 11.2499C28.6031 11.6747 28.7686 12.04 28.9529 12.426L29.0694 12.669C29.496 13.5614 29.975 14.6418 30.0532 16.8164C30.2283 21.6841 25.9495 26.059 20.8865 26.059L20.8311 26.059L20.7129 26.0592L20.5169 26.0598L20.3738 26.0604L20.1406 26.0617L19.9727 26.0628L19.7949 26.064L19.5096 26.0662L19.2019 26.0688L18.8719 26.0717L18.3971 26.0763L18.0149 26.0802L17.3284 26.0876L16.5798 26.0961L15.7692 26.1058L15.253 26.112L14.5301 26.1211L13.5705 26.1333L12.5489 26.1467L11.6869 26.1582L10.3196 26.1767L9.35831 26.19L7.58059 26.2149L5.68125 26.2419L3.95645 26.2668L3.06055 26.2799L13.8876 13.9678L13.9857 13.856L14.0835 13.7447L14.1811 13.6339L14.6638 13.087L14.7591 12.9787L14.8541 12.8708L14.9485 12.7632C14.9642 12.7453 14.9799 12.7274 14.9956 12.7095L15.0893 12.6023C15.1048 12.5844 15.1204 12.5666 15.1359 12.5487L15.2289 12.4418L15.3212 12.3351C16.3955 11.0906 17.3518 9.8759 18.0313 8.48283C18.7856 6.35249 18.0178 4.76283 17.2339 3.79963C17.1734 3.72526 17.1128 3.65463 17.0528 3.58777C16.7856 3.24574 16.9675 2.6759 17.4764 2.6759C17.5178 2.6759 17.5601 2.67526 17.6033 2.67444L17.7017 2.67245C17.7239 2.67203 17.7464 2.67164 17.769 2.67135L17.8629 2.67023L17.9592 2.66924L18.0246 2.66866L18.1926 2.66751L18.3316 2.66692L18.4383 2.66666Z" fill="#31CC79"/>
<path d="M18.4383 2.66666H18.5646H18.6762L18.79 2.66723L18.9059 2.66784L19.0239 2.66868L19.144 2.66975L19.2662 2.67107L19.3906 2.67264L19.4746 2.67384L19.5595 2.67515L19.6886 2.67735L19.8196 2.67984L19.9527 2.68262L20.0426 2.68464L20.1789 2.68793L20.3172 2.69154L20.4575 2.69547L20.552 2.69829L20.6954 2.70279L20.8407 2.70765L20.9386 2.71108L21.0373 2.71469L21.1369 2.71845L21.2372 2.72239L21.3383 2.7265L21.4403 2.73078L21.5947 2.73753L21.6986 2.74226L21.8559 2.74971L21.9618 2.7549L22.0684 2.76029L22.2298 2.76873L22.3383 2.7746L22.4476 2.78068L22.5576 2.78695L22.6683 2.79344L22.7798 2.80013L22.892 2.80703L23.0049 2.81415L23.1185 2.82148L23.2329 2.82903L23.3479 2.83681C27.4788 3.11911 28.5728 6.09394 28.6892 6.44991L28.6954 6.46902L28.7001 6.48468L30.4934 6.58232C30.5891 6.58232 30.6667 6.6599 30.6667 6.7556C30.6667 6.82419 30.6268 6.88347 30.569 6.91155C28.7283 7.90759 28.1659 9.9315 28.4965 11.2499C28.6031 11.6747 28.7686 12.04 28.9529 12.426L29.0694 12.669C29.496 13.5614 29.975 14.6418 30.0532 16.8164C30.2283 21.6841 25.9495 26.059 20.8865 26.059L20.8311 26.059L20.7129 26.0592L20.5169 26.0598L20.3738 26.0604L20.1406 26.0617L19.9727 26.0628L19.7949 26.064L19.5096 26.0662L19.2019 26.0688L18.8719 26.0717L18.3971 26.0763L18.0149 26.0802L17.3284 26.0876L16.5798 26.0961L15.7692 26.1058L15.253 26.112L14.5301 26.1211L13.5705 26.1333L12.5489 26.1467L11.6869 26.1582L10.3196 26.1767L9.35831 26.19L7.58059 26.2149L5.68125 26.2419L3.95645 26.2668L3.06055 26.2799L13.8876 13.9678L13.9857 13.856L14.0835 13.7447L14.1811 13.6339L14.6638 13.087L14.7591 12.9787L14.8541 12.8708L14.9485 12.7632C14.9642 12.7453 14.9799 12.7274 14.9956 12.7095L15.0893 12.6023C15.1048 12.5844 15.1204 12.5666 15.1359 12.5487L15.2289 12.4418L15.3212 12.3351C16.3955 11.0906 17.3518 9.8759 18.0313 8.48283C18.7856 6.35249 18.0178 4.76283 17.2339 3.79963C17.1734 3.72526 17.1128 3.65463 17.0528 3.58777C16.7856 3.24574 16.9675 2.6759 17.4764 2.6759C17.5178 2.6759 17.5601 2.67526 17.6033 2.67444L17.7017 2.67245C17.7239 2.67203 17.7464 2.67164 17.769 2.67135L17.8629 2.67023L17.9592 2.66924L18.0246 2.66866L18.1926 2.66751L18.3316 2.66692L18.4383 2.66666Z" fill="url(#paint0_radial_1602_7499)" fill-opacity="0.6"/>
<path d="M13.8876 13.9678C9.20152 19.2412 3.62434 25.6574 1.50732 28.0676C1.0995 28.5318 1.49477 28.9703 1.81067 29.0246C17.605 31.7399 22.6197 24.2969 23.7368 20.8945C25.2651 16.2393 23.1057 13.9678 21.8836 13.2271C17.7395 10.7152 14.6648 13.0932 13.8876 13.9678Z" fill="#93E65C"/>
<path style="mix-blend-mode:overlay" d="M13.8876 13.9678C9.20152 19.2412 3.62434 25.6574 1.50732 28.0676C1.0995 28.5318 1.49477 28.9703 1.81067 29.0246C17.605 31.7399 22.6197 24.2969 23.7368 20.8945C25.2651 16.2393 23.1057 13.9678 21.8836 13.2271C17.7395 10.7152 14.6648 13.0932 13.8876 13.9678Z" fill="url(#paint1_linear_1602_7499)" fill-opacity="0.75"/>
<defs>
<radialGradient id="paint0_radial_1602_7499" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(1882.01 643.886) rotate(-50.7377) scale(2502.16 2099.64)">
<stop stop-color="#178F67"/>
<stop offset="0.546848" stop-color="#31CC79" stop-opacity="0.55"/>
<stop offset="1" stop-color="#53E68D" stop-opacity="0.8"/>
</radialGradient>
<linearGradient id="paint1_linear_1602_7499" x1="1869.47" y1="1012.95" x2="-107.565" y2="1646.39" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0"/>
<stop offset="1" stop-color="#D6F056"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -12,6 +12,8 @@ import CommonList from './common-list/index.vue'
import BackButton from './back-button/index.vue'
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'
export default {
install(app: App) {
app.component('LogoFull', LogoFull)
@ -27,5 +29,7 @@ export default {
app.component('BackButton', BackButton)
app.component('AppTable', AppTable)
app.component('CodemirrorEditor', CodemirrorEditor)
app.component('InfiniteScroll', InfiniteScroll)
app.component('ModelSelect', ModelSelect)
},
}

View File

@ -0,0 +1,73 @@
<template>
<div v-infinite-scroll="loadDataset" :infinite-scroll-disabled="disabledScroll">
<slot />
</div>
<div style="padding: 16px 10px">
<el-divider v-if="size > 0 && loading">
<el-text type="info"> {{ $t('components.loading') }}...</el-text>
</el-divider>
<el-divider v-if="noMore">
<el-text type="info"> {{ $t('components.noMore') }}</el-text>
</el-divider>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
defineOptions({ name: 'InfiniteScroll' })
const props = defineProps({
/**
* 对象数量
*/
size: {
type: Number,
default: 0
},
/**
* 总数
*/
total: {
type: Number,
default: 0
},
/**
* 总数
*/
page_size: {
type: Number,
default: 0
},
current_page: {
type: Number,
default: 0
},
loading: Boolean
})
const emit = defineEmits(['update:current_page', 'load'])
const current = ref(props.current_page)
watch(
() => props.current_page,
(val) => {
if (val === 1) {
current.value = 1
}
}
)
const noMore = computed(
() =>
props.size > 0 && props.size === props.total && props.total > props.page_size && !props.loading
)
const disabledScroll = computed(() => props.size > 0 && (props.loading || noMore.value))
function loadDataset() {
if (props.total > props.page_size) {
current.value += 1
emit('update:current_page', current.value)
emit('load')
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,161 @@
<template>
<div class="w-full">
<el-select v-model="modelValue" popper-class="select-model" :clearable="true" v-bind="$attrs">
<el-option-group
v-for="(value, label) in options"
:key="value"
:label="relatedObject(providerOptions, label, 'provider')?.name"
>
<el-option
v-for="item in value.filter((v: any) => v.status === 'SUCCESS')"
:key="item.id"
:label="item.name"
:value="item.id"
class="flex-between"
>
<div class="flex">
<span
v-html="relatedObject(providerOptions, label, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
<el-tag v-if="item.permission_type === 'PUBLIC'" type="info" class="info-tag ml-8 mt-4">
{{ $t('common.public') }}
</el-tag>
</div>
<el-icon class="check-icon" v-if="item.id === modelValue">
<Check />
</el-icon>
</el-option>
<!-- 不可用 -->
<el-option
v-for="item in value.filter((v: any) => v.status !== 'SUCCESS')"
:key="item.id"
:label="item.name"
:value="item.id"
class="flex-between"
disabled
>
<div class="flex">
<span
v-html="relatedObject(providerOptions, label, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
<span class="danger">{{ $t('common.unavailable') }}</span>
</div>
<el-icon class="check-icon" v-if="item.id === modelValue">
<Check />
</el-icon>
</el-option>
</el-option-group>
<template #footer v-if="showFooter">
<slot name="footer">
<div class="w-full text-left cursor" @click="openCreateModel(undefined, props.modelType)">
<el-button type="primary" link>
<el-icon class="mr-4">
<Plus />
</el-icon>
{{ $t('views.application.buttons.addModel') }}
</el-button>
</div>
</slot>
</template>
</el-select>
<!-- 添加模版 -->
<CreateModelDialog
v-if="showFooter"
ref="createModelRef"
@submit="submitModel"
@change="openCreateModel($event)"
></CreateModelDialog>
<SelectProviderDialog
v-if="showFooter"
ref="selectProviderRef"
@change="(provider, modelType) => openCreateModel(provider, modelType)"
/>
</div>
</template>
<script setup lang="ts">
import { computed, ref, onMounted } from 'vue'
import type { Provider } from '@/api/type/model'
import { relatedObject } from '@/utils/common'
import CreateModelDialog from '@/views/model/component/CreateModelDialog.vue'
import SelectProviderDialog from '@/views/model/component/SelectProviderDialog.vue'
import { t } from '@/locales'
import useStore from '@/stores'
defineOptions({ name: 'ModelSelect' })
const props = defineProps<{
modelValue: any
options: any
showFooter?: false
modelType?: ''
}>()
const emit = defineEmits(['update:modelValue', 'change', 'submitModel'])
const modelValue = computed({
set: (item) => {
emit('change', item)
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const { model } = useStore()
const createModelRef = ref<InstanceType<typeof CreateModelDialog>>()
const selectProviderRef = ref<InstanceType<typeof SelectProviderDialog>>()
const providerOptions = ref<Array<Provider>>([])
const loading = ref(false)
function getProvider() {
loading.value = true
model
.asyncGetProvider()
.then((res: any) => {
providerOptions.value = res?.data
loading.value = false
})
.catch(() => {
loading.value = false
})
}
const openCreateModel = (provider?: Provider, model_type?: string) => {
if (provider && provider.provider) {
createModelRef.value?.open(provider, model_type)
} else {
selectProviderRef.value?.open(model_type)
}
}
function submitModel() {
emit('submitModel')
}
onMounted(() => {
getProvider()
})
</script>
<style lang="scss" scoped>
// AIhover
.select-model {
.el-select-dropdown__footer {
&:hover {
background-color: var(--el-fill-color-light);
}
}
.model-icon {
width: 18px;
}
.check-icon {
position: absolute;
right: 10px;
}
}
</style>

View File

@ -8,5 +8,8 @@ export default {
title: 'Select Segments',
error: 'Process only the failed segments',
all: 'All Segments'
},
folder: {
addFolder:'Add Folder'
}
}

View File

@ -1,86 +0,0 @@
export default {
title: 'Knowledge',
createDataset: 'Create Knowledge',
general: 'General',
web: 'Web Site',
relatedApplications: 'Linked App',
document_count: 'docs',
relatedApp_count: 'linked apps',
searchBar: {
placeholder: 'Search by name'
},
setting: {
vectorization: 'Vectorization',
sync: 'Sync'
},
tip: {
professionalMessage:
'The community edition supports up to 50 knowledge. For more knowledge, please upgrade to the professional edition.',
syncSuccess: 'Sync task sent successfully',
updateModeMessage:
'After modifying the knowledge vector model, you need to vectorize the knowledge. Do you want to continue saving?'
},
delete: {
confirmTitle: 'Confirm deletion of knowledge:',
confirmMessage1: 'This knowledge is related with',
confirmMessage2: 'APP. Deleting it will be irreversible, please proceed with caution.'
},
datasetForm: {
title: {
info: 'Knowledge Settings'
},
form: {
datasetName: {
label: 'Name',
placeholder: 'Please enter the knowledge name',
requiredMessage: 'Please enter the knowledge name'
},
datasetDescription: {
label: 'Description',
placeholder:
'Describe the content of the knowledge. A detailed description will help AI understand the content better, improving the accuracy of content retrieval and hit rate.',
requiredMessage: 'Please enter the knowledge description'
},
EmbeddingModel: {
label: 'Embedding Model',
placeholder: 'Please select a embedding model',
requiredMessage: 'Please select the embedding model'
},
datasetType: {
label: 'Type',
generalInfo: 'Upload local documents',
webInfo: 'Sync text data from a web site'
},
source_url: {
label: 'Web Root URL',
placeholder: 'Please enter the web root URL',
requiredMessage: 'Please enter the web root URL'
},
selector: {
label: 'Selector',
placeholder: 'Default is body, can input .classname/#idname/tagname'
}
}
},
ResultSuccess: {
title: 'Knowledge Created Successfully',
paragraph: 'Segments',
paragraph_count: 'Segments',
documentList: 'Document List',
loading: 'Importing',
buttons: {
toDataset: 'Return to Knowledge List',
toDocument: 'Go to Document'
}
},
syncWeb: {
title: 'Sync Knowledge',
syncMethod: 'Sync Method',
replace: 'Replace Sync',
replaceText: 'Re-fetch Web site documents, replacing the documents in the local knowledge',
complete: 'Full Sync',
completeText:
'Delete all documents in the local knowledge and re-fetch web site documents',
tip: 'Note: All syncs will delete existing data and re-fetch new data. Please proceed with caution.'
}
}

View File

@ -1,7 +1,7 @@
import notFound from './404'
import application from './application'
import applicationOverview from './application-overview'
import dataset from './dataset'
import knowledge from './knowledge'
import system from './system'
import tool from './tool'
import userManage from './user-manage'
@ -25,7 +25,7 @@ export default {
resourceAuthorization,
team,
model,
dataset,
knowledge,
applicationWorkflow,
document,
paragraph,

View File

@ -0,0 +1,89 @@
export default {
title: 'Knowledge',
relatedApplications: 'Linked App',
document_count: 'docs',
relatedApp_count: 'linked apps',
searchBar: {
placeholder: 'Search by name',
},
setting: {
vectorization: 'Vectorization',
sync: 'Sync',
},
tip: {
professionalMessage:
'The community edition supports up to 50 knowledge. For more knowledge, please upgrade to the professional edition.',
syncSuccess: 'Sync task sent successfully',
updateModeMessage:
'After modifying the knowledge vector model, you need to vectorize the knowledge. Do you want to continue saving?',
},
delete: {
confirmTitle: 'Confirm deletion of knowledge:',
confirmMessage1: 'This knowledge is related with',
confirmMessage2: 'APP. Deleting it will be irreversible, please proceed with caution.',
},
knowledgeType: {
label: 'Type',
generalKnowledge: 'General Knowledge',
webKnowledge: 'Web Knowledge',
larkKnowledge: 'Lark Knowledge',
yuqueKnowledge: 'Yuque Knowledge',
generalInfo: 'Upload local documents',
webInfo: 'Sync text data from a web site',
larkInfo: 'Build knowledge through Lark documents',
yuqueInfo: 'Build knowledge through Yuque documents',
createGeneralKnowledge: 'Create General Knowledge',
createWebKnowledge: 'Create Web Knowledge',
createLarkKnowledge: 'Create Lark Knowledge',
createYuqueKnowledge: 'Create Yuque Knowledge',
},
form: {
knowledgeName: {
label: 'Name',
placeholder: 'Please enter the knowledge name',
requiredMessage: 'Please enter the knowledge name',
},
knowledgeDescription: {
label: 'Description',
placeholder:
'Describe the content of the knowledge. A detailed description will help AI understand the content better, improving the accuracy of content retrieval and hit rate.',
requiredMessage: 'Please enter the knowledge description',
},
EmbeddingModel: {
label: 'Embedding Model',
placeholder: 'Please select a embedding model',
requiredMessage: 'Please select the embedding model',
},
source_url: {
label: 'Web Root URL',
placeholder: 'Please enter the web root URL',
requiredMessage: 'Please enter the web root URL',
},
selector: {
label: 'Selector',
placeholder: 'Default is body, can input .classname/#idname/tagname',
},
},
ResultSuccess: {
title: 'Knowledge Created Successfully',
paragraph: 'Segments',
paragraph_count: 'Segments',
documentList: 'Document List',
loading: 'Importing',
buttons: {
toKnowledge: 'Return to Knowledge List',
toDocument: 'Go to Document',
},
},
syncWeb: {
title: 'Sync Knowledge',
syncMethod: 'Sync Method',
replace: 'Replace Sync',
replaceText: 'Re-fetch Web site documents, replacing the documents in the local knowledge',
complete: 'Full Sync',
completeText: 'Delete all documents in the local knowledge and re-fetch web site documents',
tip: 'Note: All syncs will delete existing data and re-fetch new data. Please proceed with caution.',
},
}

View File

@ -8,5 +8,8 @@ export default {
title: '选择分段',
error: '仅执行未成功分段',
all: '全部分段'
},
folder: {
addFolder:'添加文件夹'
}
}

View File

@ -6,4 +6,84 @@ export default {
vectorization: '向量化',
sync: '同步',
},
tip: {
professionalMessage: '社区版最多支持 50 个知识库,如需拥有更多知识库,请升级为专业版。',
syncSuccess: '同步任务发送成功',
updateModeMessage: '修改知识库向量模型后,需要对知识库向量化,是否继续保存?',
},
delete: {
confirmTitle: '是否删除知识库:',
confirmMessage1: '此知识库关联',
confirmMessage2: '个应用,删除后无法恢复,请谨慎操作。',
},
knowledgeType: {
label: '知识库类型',
generalKnowledge: '通用知识库',
webKnowledge: 'web知识库',
larkKnowledge: '飞书知识库',
yuqueKnowledge: '语雀知识库',
generalInfo: '通过上传文件或手动录入构建知识库',
webInfo: '通过网站链接构建知识库',
larkInfo: '通过飞书文档构建知识库',
yuqueInfo: '通过语雀文档构建知识库',
createGeneralKnowledge: '创建通用知识库',
createWebKnowledge: '创建 web 知识库',
createLarkKnowledge: '创建飞书知识库',
createYuqueKnowledge: '创建语雀知识库',
},
form: {
knowledgeName: {
label: '知识库名称',
placeholder: '请输入知识库名称',
requiredMessage: '请输入知识库名称',
},
knowledgeDescription: {
label: '知识库描述',
placeholder:
'描述知识库的内容详尽的描述将帮助AI能深入理解该知识库的内容能更准确的检索到内容提高该知识库的命中率。',
requiredMessage: '请输入知识库描述',
},
EmbeddingModel: {
label: '向量模型',
placeholder: '请选择向量模型',
requiredMessage: '请输入Embedding模型',
},
source_url: {
label: 'Web 根地址',
placeholder: '请输入 Web 根地址',
requiredMessage: ' 请输入 Web 根地址',
},
user_id: {
requiredMessage: '请输入User ID',
},
token: {
requiredMessage: '请输入Token',
},
selector: {
label: '选择器',
placeholder: '默认为 body可输入 .classname/#idname/tagname',
},
},
ResultSuccess: {
title: '知识库创建成功',
paragraph: '分段',
paragraph_count: '个分段',
documentList: '文档列表',
loading: '导入中',
buttons: {
toKnowledge: '返回知识库列表',
toDocument: '前往文档',
},
},
syncWeb: {
title: '同步知识库',
syncMethod: '同步方式',
replace: '替换同步',
replaceText: '重新获取 Web 站点文档,覆盖替换本地知识库中的文档',
complete: '整体同步',
completeText: '先删除本地知识库所有文档,重新获取 Web 站点文档',
tip: '注意:所有同步都会删除已有数据重新获取新数据,请谨慎操作。',
},
}

View File

@ -8,5 +8,8 @@ export default {
title: '選擇分段',
error: '僅執行未成功分段',
all: '全部分段'
},
folder: {
addFolder:'添加文件夾'
}
}

View File

@ -1,83 +0,0 @@
export default {
title: '知識庫',
createDataset: '建立知識庫',
general: '通用型',
web: 'Web 站點',
relatedApplications: '關聯應用',
document_count: '文檔數',
relatedApp_count: '關聯應用',
searchBar: {
placeholder: '按名稱搜尋'
},
setting: {
vectorization: '向量化',
sync: '同步'
},
tip: {
professionalMessage: '社群版最多支援 50 個知識庫,如需擁有更多知識庫,請升級為專業版。',
syncSuccess: '同步任務發送成功',
updateModeMessage: '修改知識庫向量模型後,需要對知識庫向量化,是否繼續保存?'
},
delete: {
confirmTitle: '是否刪除知識庫:',
confirmMessage1: '此知識庫關聯',
confirmMessage2: '個應用,刪除後無法恢復,請謹慎操作。'
},
datasetForm: {
title: {
info: '基本資訊'
},
form: {
datasetName: {
label: '知識庫名稱',
placeholder: '請輸入知識庫名稱',
requiredMessage: '請輸入應用名稱'
},
datasetDescription: {
label: '知識庫描述',
placeholder:
'描述知識庫的內容詳盡的描述將幫助AI能深入理解該知識庫的內容能更準確的檢索到內容提高該知識庫的命中率。',
requiredMessage: '請輸入知識庫描述'
},
EmbeddingModel: {
label: '向量模型',
placeholder: '請選擇向量模型',
requiredMessage: '請輸入Embedding模型'
},
datasetType: {
label: '知識庫類型',
generalInfo: '上傳本地檔案',
webInfo: '同步Web網站文字資料'
},
source_url: {
label: 'Web 根位址',
placeholder: '請輸入 Web 根位址',
requiredMessage: '請輸入 Web 根位址'
},
selector: {
label: '選擇器',
placeholder: '預設為 body可輸入 .classname/#idname/tagname'
}
}
},
ResultSuccess: {
title: '知識庫建立成功',
paragraph: '段落',
paragraph_count: '個段落',
documentList: '文件列表',
loading: '正在導入',
buttons: {
toDataset: '返回知識庫列表',
toDocument: '前往文件'
}
},
syncWeb: {
title: '同步知識庫',
syncMethod: '同步方式',
replace: '替換同步',
replaceText: '重新獲取 Web 站點文件,覆蓋替換本地知識庫中的文件',
complete: '完整同步',
completeText: '先刪除本地知識庫所有文件,重新獲取 Web 站點文件',
tip: '注意:所有同步都會刪除現有數據並重新獲取新數據,請謹慎操作。'
}
}

View File

@ -1,7 +1,7 @@
import notFound from './404'
import application from './application'
import applicationOverview from './application-overview'
import dataset from './dataset'
import knowledge from './knowledge'
import system from './system'
import tool from './tool'
import userManage from './user-manage'
@ -25,7 +25,7 @@ export default {
resourceAuthorization,
team,
model,
dataset,
knowledge,
applicationWorkflow,
document,
paragraph,

View File

@ -0,0 +1,83 @@
export default {
title: '知識庫',
relatedApplications: '關聯應用',
document_count: '文檔數',
relatedApp_count: '關聯應用',
searchBar: {
placeholder: '按名稱搜尋',
},
setting: {
vectorization: '向量化',
sync: '同步',
},
tip: {
professionalMessage: '社群版最多支援 50 個知識庫,如需擁有更多知識庫,請升級為專業版。',
syncSuccess: '同步任務發送成功',
updateModeMessage: '修改知識庫向量模型後,需要對知識庫向量化,是否繼續保存?',
},
delete: {
confirmTitle: '是否刪除知識庫:',
confirmMessage1: '此知識庫關聯',
confirmMessage2: '個應用,刪除後無法恢復,請謹慎操作。',
},
knowledgeType: {
label: '知識庫類型',
generalKnowledge: '通用知識庫',
webKnowledge: 'Web 知識庫',
larkKnowledge: '飛書知識庫',
yuqueKnowledge: '語雀知識庫',
generalInfo: '上傳本地檔案',
webInfo: '同步Web網站文字資料',
larkInfo: '通過飛書文檔構建知識庫',
yuqueInfo: '通過語雀文檔構建知識庫',
},
form: {
knowledgeName: {
label: '知識庫名稱',
placeholder: '請輸入知識庫名稱',
requiredMessage: '請輸入應用名稱',
},
knowledgeDescription: {
label: '知識庫描述',
placeholder:
'描述知識庫的內容詳盡的描述將幫助AI能深入理解該知識庫的內容能更準確的檢索到內容提高該知識庫的命中率。',
requiredMessage: '請輸入知識庫描述',
},
EmbeddingModel: {
label: '向量模型',
placeholder: '請選擇向量模型',
requiredMessage: '請輸入Embedding模型',
},
source_url: {
label: 'Web 根位址',
placeholder: '請輸入 Web 根位址',
requiredMessage: '請輸入 Web 根位址',
},
selector: {
label: '選擇器',
placeholder: '預設為 body可輸入 .classname/#idname/tagname',
},
},
ResultSuccess: {
title: '知識庫建立成功',
paragraph: '段落',
paragraph_count: '個段落',
documentList: '文件列表',
loading: '正在導入',
buttons: {
toKnowledge: '返回知識庫列表',
toDocument: '前往文件',
},
},
syncWeb: {
title: '同步知識庫',
syncMethod: '同步方式',
replace: '替換同步',
replaceText: '重新獲取 Web 站點文件,覆蓋替換本地知識庫中的文件',
complete: '完整同步',
completeText: '先刪除本地知識庫所有文件,重新獲取 Web 站點文件',
tip: '注意:所有同步都會刪除現有數據並重新獲取新數據,請謹慎操作。',
},
}

View File

@ -4,6 +4,7 @@ import useUserStore from './modules/user'
import useFolderStore from './modules/folder'
import useThemeStore from './modules/theme'
import useKnowledgeStore from './modules/knowledge'
import useModelStore from './modules/model'
const useStore = () => ({
common: useCommonStore(),
@ -12,6 +13,7 @@ const useStore = () => ({
folder: useFolderStore(),
theme: useThemeStore(),
knowledge: useKnowledgeStore(),
model: useModelStore(),
})
export default useStore

View File

@ -0,0 +1,34 @@
import { defineStore } from 'pinia'
import { type Ref } from 'vue'
import ModelApi from '@/api/model/model'
import ProviderApi from '@/api/model/provider'
import type { ListModelRequest } from '@/api/type/model'
const useModelStore = defineStore('model', {
state: () => ({}),
actions: {
async asyncGetModel(wordspace_id: string, data?: ListModelRequest, loading?: Ref<boolean>) {
return new Promise((resolve, reject) => {
ModelApi.getModel(wordspace_id, data, loading)
.then((res) => {
resolve(res)
})
.catch((error) => {
reject(error)
})
})
},
async asyncGetProvider(loading?: Ref<boolean>) {
return new Promise((resolve, reject) => {
ProviderApi.getProvider(loading)
.then((res) => {
resolve(res)
})
.catch((error) => {
reject(error)
})
})
},
},
})
export default useModelStore

View File

@ -195,3 +195,16 @@
background: var(--el-table-current-row-bg-color);
}
}
// dialog
.el-dialog {
--el-dialog-padding-primary: 24px;
--el-dialog-border-radius: 8px;
}
.el-dialog__headerbtn {
top: 8px;
right: 8px;
.el-dialog__close {
font-size: 20px;
}
}

View File

@ -1,3 +1,11 @@
/*
*/
export function relatedObject(list: any, val: any, attr: string) {
const filterData: any = list.filter((item: any) => item[attr] === val)?.[0]
return filterData || null
}
// 排序
export function arraySort(list: Array<any>, property: any, desc?: boolean) {
return list.sort((a: any, b: any) => {

View File

@ -7,23 +7,20 @@
require-asterisk-position="right"
v-loading="loading"
>
<el-form-item :label="$t('views.dataset.datasetForm.form.datasetName.label')" prop="name">
<el-form-item :label="$t('views.knowledge.form.knowledgeName.label')" prop="name">
<el-input
v-model="form.name"
:placeholder="$t('views.dataset.datasetForm.form.datasetName.placeholder')"
:placeholder="$t('views.knowledge.form.knowledgeName.placeholder')"
maxlength="64"
show-word-limit
@blur="form.name = form.name.trim()"
/>
</el-form-item>
<el-form-item
:label="$t('views.dataset.datasetForm.form.datasetDescription.label')"
prop="desc"
>
<el-form-item :label="$t('views.knowledge.form.knowledgeDescription.label')" prop="desc">
<el-input
v-model="form.desc"
type="textarea"
:placeholder="$t('views.dataset.datasetForm.form.datasetDescription.placeholder')"
:placeholder="$t('views.knowledge.form.knowledgeDescription.placeholder')"
maxlength="256"
show-word-limit
:autosize="{ minRows: 3 }"
@ -31,12 +28,12 @@
/>
</el-form-item>
<el-form-item
:label="$t('views.dataset.datasetForm.form.EmbeddingModel.label')"
:label="$t('views.knowledge.form.EmbeddingModel.label')"
prop="embedding_model_id"
>
<ModelSelect
v-model="form.embedding_model_id"
:placeholder="$t('views.dataset.datasetForm.form.EmbeddingModel.placeholder')"
v-model="form.embedding"
:placeholder="$t('views.knowledge.form.EmbeddingModel.placeholder')"
:options="modelOptions"
:model-type="'EMBEDDING'"
showFooter
@ -48,43 +45,43 @@
import { ref, reactive, onMounted, onUnmounted, computed, watch } from 'vue'
import { groupBy } from 'lodash'
import useStore from '@/stores'
import type { datasetData } from '@/api/type/knowledge'
import type { knowledgeData } from '@/api/type/knowledge'
import { t } from '@/locales'
const props = defineProps({
data: {
type: Object,
default: () => {}
}
default: () => {},
},
})
const { model } = useStore()
const form = ref<datasetData>({
const form = ref<knowledgeData>({
name: '',
desc: '',
embedding_model_id: ''
embedding: '',
})
const rules = reactive({
name: [
{
required: true,
message: t('views.dataset.datasetForm.form.datasetName.requiredMessage'),
trigger: 'blur'
}
message: t('views.knowledge.form.knowledgeName.requiredMessage'),
trigger: 'blur',
},
],
desc: [
{
required: true,
message: t('views.dataset.datasetForm.form.datasetDescription.requiredMessage'),
trigger: 'blur'
}
message: t('views.knowledge.form.knowledgeDescription.requiredMessage'),
trigger: 'blur',
},
],
embedding_model_id: [
embedding: [
{
required: true,
message: t('views.dataset.datasetForm.form.EmbeddingModel.requiredMessage'),
trigger: 'change'
}
]
message: t('views.knowledge.form.EmbeddingModel.requiredMessage'),
trigger: 'change',
},
],
})
const FormRef = ref()
@ -97,12 +94,12 @@ watch(
if (value && JSON.stringify(value) !== '{}') {
form.value.name = value.name
form.value.desc = value.desc
form.value.embedding_model_id = value.embedding_model_id
form.value.embedding = value.embedding
}
},
{
immediate: true
}
immediate: true,
},
)
/*
表单校验
@ -117,7 +114,7 @@ function validate() {
function getModel() {
loading.value = true
model
.asyncGetModel({ model_type: 'EMBEDDING' })
.asyncGetModel('default', { model_type: 'EMBEDDING' })
.then((res: any) => {
modelOptions.value = groupBy(res?.data, 'provider')
loading.value = false
@ -134,14 +131,14 @@ onUnmounted(() => {
form.value = {
name: '',
desc: '',
embedding_model_id: ''
embedding: '',
}
FormRef.value?.clearValidate()
})
defineExpose({
validate,
form
form,
})
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,25 @@
<template>
<el-avatar v-if="type === '1'" class="avatar-purple" shape="square" :size="32">
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
</el-avatar>
<el-avatar
v-else-if="type === '2'"
class="avatar-purple"
shape="square"
:size="32"
style="background: none"
>
<img src="@/assets/knowledge/logo_lark.svg" style="width: 100%" alt="" />
</el-avatar>
<el-avatar v-else class="avatar-blue" shape="square" :size="32">
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
</el-avatar>
</template>
<script setup lang="ts">
const props = defineProps({
type: {
type: String,
default: '',
},
})
</script>

View File

@ -1,6 +1,6 @@
<template>
<el-dialog
:title="$t('views.dataset.createDataset')"
:title="$t('views.knowledge.knowledgeType.createGeneralKnowledge')"
v-model="dialogVisible"
width="720"
append-to-body
@ -16,114 +16,7 @@
label-position="top"
require-asterisk-position="right"
>
<el-form-item :label="$t('views.dataset.datasetForm.form.datasetType.label')" required>
<el-radio-group v-model="datasetForm.type" class="card__radio" @change="radioChange">
<el-row :gutter="20">
<el-col :span="12">
<el-card
shadow="never"
class="mb-16"
:class="datasetForm.type === '0' ? 'active' : ''"
@click="datasetForm.type = '0'"
>
<div class="flex-between">
<div class="flex align-center">
<AppAvatar class="mr-8 avatar-blue" shape="square" :size="32">
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
</AppAvatar>
<div>
<p>
<el-text>{{ $t('views.dataset.general') }}</el-text>
</p>
<el-text type="info">{{
$t('views.dataset.datasetForm.form.datasetType.generalInfo')
}}</el-text>
</div>
</div>
<el-radio value="0" size="large" style="width: 16px"></el-radio>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card
shadow="never"
class="mb-16"
:class="datasetForm.type === '1' ? 'active' : ''"
@click="datasetForm.type = '1'"
>
<div class="flex-between">
<div class="flex align-center">
<AppAvatar class="mr-8 avatar-purple" shape="square" :size="32">
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
</AppAvatar>
<div>
<p>
<el-text>{{ $t('views.dataset.web') }}</el-text>
</p>
<el-text type="info">{{
$t('views.dataset.datasetForm.form.datasetType.webInfo')
}}</el-text>
</div>
</div>
<el-radio value="1" size="large" style="width: 16px"></el-radio>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" v-hasPermission="new ComplexPermission([], ['x-pack'], 'OR')">
<el-col :span="12">
<el-card
shadow="never"
class="mb-16"
:class="datasetForm.type === '2' ? 'active' : ''"
@click="datasetForm.type = '2'"
>
<div class="flex-between">
<div class="flex align-center">
<AppAvatar shape="square" :size="32" style="background: none">
<img src="@/assets/logo_lark.svg" style="width: 100%" alt="" />
</AppAvatar>
<div>
<p>
<el-text>{{ $t('views.dataset.lark') }}</el-text>
</p>
<el-text type="info">{{
$t('views.dataset.datasetForm.form.datasetType.larkInfo')
}}</el-text>
</div>
</div>
<el-radio value="2" size="large" style="width: 16px"></el-radio>
</div>
</el-card>
</el-col>
<el-col :span="12">
<!-- <el-card-->
<!-- shadow="never"-->
<!-- class="mb-16"-->
<!-- :class="datasetForm.type === '3' ? 'active' : ''"-->
<!-- @click="datasetForm.type = '3'"-->
<!-- >-->
<!-- <div class="flex-between">-->
<!-- <div class="flex align-center">-->
<!-- <AppAvatar class="mr-8" :size="32">-->
<!-- <img src="@/assets/knowledge/icon_web.svg" style="width: 100%" alt="" />-->
<!-- </AppAvatar>-->
<!-- <div>-->
<!-- <p>-->
<!-- <el-text>{{ $t('views.dataset.yuque') }}</el-text>-->
<!-- </p>-->
<!-- <el-text type="info">{{-->
<!-- $t('views.dataset.datasetForm.form.datasetType.yuqueInfo')-->
<!-- }}</el-text>-->
<!-- </div>-->
<!-- </div>-->
<!-- <el-radio value="3" size="large" style="width: 16px"></el-radio>-->
<!-- </div>-->
<!-- </el-card>-->
</el-col>
</el-row>
</el-radio-group>
</el-form-item>
<el-form-item
:label="$t('views.dataset.datasetForm.form.source_url.label')"
prop="source_url"
@ -199,8 +92,8 @@
<script setup lang="ts">
import { ref, watch, reactive } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import BaseForm from './BaseForm.vue'
import datasetApi from '@/api/dataset'
import BaseForm from '@/views/knowledge/component/BaseForm.vue'
import KnowledgeApi from '@/api/knowledge/knowledge'
import { MsgSuccess, MsgAlert } from '@/utils/message'
import { t } from '@/locales'
import { ComplexPermission } from '@/utils/permission/type'

View File

@ -0,0 +1,69 @@
<template>
<el-dialog
:title="$t('views.knowledge.knowledgeType.createGeneralKnowledge')"
v-model="dialogVisible"
width="720"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<!-- 基本信息 -->
<BaseForm ref="BaseFormRef" v-if="dialogVisible" />
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false" :loading="loading">
{{ $t('common.cancel') }}
</el-button>
<el-button type="primary" @click="submitHandle" :loading="loading">
{{ $t('common.create') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch, reactive } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import BaseForm from '@/views/knowledge/component/BaseForm.vue'
import KnowledgeApi from '@/api/knowledge/knowledge'
import { MsgSuccess, MsgAlert } from '@/utils/message'
import { t } from '@/locales'
const emit = defineEmits(['refresh'])
const router = useRouter()
const BaseFormRef = ref()
const loading = ref(false)
const dialogVisible = ref<boolean>(false)
const currentFolder = ref<string>('')
watch(dialogVisible, (bool) => {
if (!bool) {
}
})
const open = (folder: string) => {
currentFolder.value = folder
dialogVisible.value = true
}
const submitHandle = async () => {
if (await BaseFormRef.value?.validate()) {
const obj = {
folder_id: currentFolder.value.id,
...BaseFormRef.value.form,
}
KnowledgeApi.postDataset('default', obj, loading).then((res) => {
MsgSuccess(t('common.createSuccess'))
// router.push({ path: `/knowledge/${res.data.id}/document` })
emit('refresh')
})
} else {
return false
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@ -41,112 +41,187 @@
<el-option v-for="u in user_options" :key="u.id" :value="u.id" :label="u.username" />
</el-select>
</div>
<el-button class="ml-16" type="primary"> {{ $t('common.create') }}</el-button>
</div>
</template>
<div>
<el-row v-if="datasetList.length > 0" :gutter="15">
<template v-for="(item, index) in datasetList" :key="index">
<el-col
v-if="item.resource_type === 'folder'"
:xs="24"
:sm="12"
:md="12"
:lg="8"
:xl="6"
class="mb-16"
>
<CardBox
:title="item.name"
:description="item.desc || $t('common.noData')"
class="cursor"
>
<template #icon>
<el-avatar shape="square" :size="32" style="background: none">
<AppIcon iconName="app-folder" style="font-size: 32px"></AppIcon>
</el-avatar>
</template>
<template #subTitle>
<el-text class="color-secondary lighter" size="small">
{{ $t('common.creator') }}: {{ item.username }}
</el-text>
</template>
</CardBox>
</el-col>
<el-col v-else :xs="24" :sm="12" :md="12" :lg="8" :xl="6" class="mb-16">
<CardBox
:title="item.name"
:description="item.desc"
class="cursor"
@click="router.push({ path: `/knowledge/${item.id}/document` })"
>
<template #icon>
<el-avatar
v-if="item.type === '1'"
class="avatar-purple"
shape="square"
:size="32"
>
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
</el-avatar>
<el-avatar
v-else-if="item.type === '2'"
class="avatar-purple"
shape="square"
:size="32"
style="background: none"
>
<img src="@/assets/knowledge/logo_lark.svg" style="width: 100%" alt="" />
</el-avatar>
<el-avatar v-else class="avatar-blue" shape="square" :size="32">
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
</el-avatar>
</template>
<template #subTitle>
<el-text class="color-secondary" size="small">
<auto-tooltip :content="item.username">
{{ $t('common.creator') }}: {{ item.username }}
</auto-tooltip>
</el-text>
</template>
<template #footer>
<div class="footer-content flex-between">
<div>
<span class="bold mr-4">{{ item?.document_count || 0 }}</span>
<span class="color-secondary">{{
$t('views.knowledge.document_count')
}}</span>
<el-divider direction="vertical" />
<span class="bold mr-4">{{ numberFormat(item?.char_length) || 0 }}</span>
<span class="color-secondary">{{ $t('common.character') }}</span
><el-divider direction="vertical" />
<span class="bold mr-4">{{ item?.application_mapping_count || 0 }}</span>
<span class="color-secondary">{{
$t('views.knowledge.relatedApp_count')
}}</span>
<el-dropdown trigger="click">
<el-button type="primary" class="ml-8">
{{ $t('common.create') }}<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu class="create-dropdown">
<el-dropdown-item @click="openCreateDialog(CreateKnowledgeDialog)">
<div class="flex">
<el-avatar class="avatar-blue mt-4" shape="square" :size="32">
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
</el-avatar>
<div class="pre-wrap ml-8">
<div class="lighter">
{{ $t('views.knowledge.knowledgeType.generalKnowledge') }}
</div>
<el-text type="info" size="small">{{
$t('views.knowledge.knowledgeType.generalInfo')
}}</el-text>
</div>
</div>
</template>
<template #mouseEnter>
<div @click.stop>
<el-dropdown trigger="click">
<el-button text @click.stop>
<el-icon><MoreFilled /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
icon="Refresh"
@click.stop="syncDataset(item)"
v-if="item.type === 1"
>{{ $t('views.knowledge.setting.sync') }}</el-dropdown-item
>
<el-dropdown-item @click.stop="reEmbeddingDataset(item)">
<AppIcon iconName="app-vectorization"></AppIcon>
{{ $t('views.knowledge.setting.vectorization') }}
</el-dropdown-item>
<!--
</el-dropdown-item>
<el-dropdown-item @click="openCreateDialog">
<div class="flex">
<el-avatar class="avatar-purple mt-4" shape="square" :size="32">
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
</el-avatar>
<div class="pre-wrap ml-8">
<div class="lighter">
{{ $t('views.knowledge.knowledgeType.webKnowledge') }}
</div>
<el-text type="info" size="small">{{
$t('views.knowledge.knowledgeType.webInfo')
}}</el-text>
</div>
</div>
</el-dropdown-item>
<el-dropdown-item @click="openCreateDialog">
<div class="flex">
<el-avatar
class="avatar-purple mt-4"
shape="square"
:size="32"
style="background: none"
>
<img src="@/assets/knowledge/logo_lark.svg" alt="" />
</el-avatar>
<div class="pre-wrap ml-8">
<div class="lighter">
{{ $t('views.knowledge.knowledgeType.larkKnowledge') }}
</div>
<el-text type="info" size="small">{{
$t('views.knowledge.knowledgeType.larkInfo')
}}</el-text>
</div>
</div>
</el-dropdown-item>
<el-dropdown-item @click="openCreateDialog">
<div class="flex">
<el-avatar
class="avatar-purple mt-4"
shape="square"
:size="32"
style="background: none"
>
<img src="@/assets/knowledge/logo_yuque.svg" alt="" />
</el-avatar>
<div class="pre-wrap ml-8">
<div class="lighter">
{{ $t('views.knowledge.knowledgeType.yuqueKnowledge') }}
</div>
<el-text type="info" size="small">{{
$t('views.knowledge.knowledgeType.yuqueInfo')
}}</el-text>
</div>
</div>
</el-dropdown-item>
<el-dropdown-item @click="openCreateDialog" divided>
<div class="flex align-center">
<AppIcon iconName="app-folder" style="font-size: 32px"></AppIcon>
<div class="pre-wrap ml-4">
<div class="lighter">
{{ $t('components.folder.addFolder') }}
</div>
</div>
</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<div v-loading.fullscreen.lock="paginationConfig.current_page === 1 && loading">
<InfiniteScroll
:size="knowledgeList.length"
:total="paginationConfig.total"
:page_size="paginationConfig.page_size"
v-model:current_page="paginationConfig.current_page"
@load="getList"
:loading="loading"
>
<el-row v-if="knowledgeList.length > 0" :gutter="15">
<template v-for="(item, index) in knowledgeList" :key="index">
<el-col
v-if="item.resource_type === 'folder'"
:xs="24"
:sm="12"
:md="12"
:lg="8"
:xl="6"
class="mb-16"
>
<CardBox
:title="item.name"
:description="item.desc || $t('common.noData')"
class="cursor"
>
<template #icon>
<el-avatar shape="square" :size="32" style="background: none">
<AppIcon iconName="app-folder" style="font-size: 32px"></AppIcon>
</el-avatar>
</template>
<template #subTitle>
<el-text class="color-secondary lighter" size="small">
{{ $t('common.creator') }}: {{ item.username }}
</el-text>
</template>
</CardBox>
</el-col>
<el-col v-else :xs="24" :sm="12" :md="12" :lg="8" :xl="6" class="mb-16">
<CardBox
:title="item.name"
:description="item.desc"
class="cursor"
@click="router.push({ path: `/knowledge/${item.id}/document` })"
>
<template #icon>
<KnowledgeIcon :type="item.type" />
</template>
<template #subTitle>
<el-text class="color-secondary" size="small">
{{ $t('common.creator') }}: {{ item.username }}
</el-text>
</template>
<template #footer>
<div class="footer-content flex-between">
<div>
<span class="bold mr-4">{{ item?.document_count || 0 }}</span>
<span class="color-secondary">{{
$t('views.knowledge.document_count')
}}</span>
<el-divider direction="vertical" />
<span class="bold mr-4">{{ numberFormat(item?.char_length) || 0 }}</span>
<span class="color-secondary">{{ $t('common.character') }}</span
><el-divider direction="vertical" />
<span class="bold mr-4">{{ item?.application_mapping_count || 0 }}</span>
<span class="color-secondary">{{
$t('views.knowledge.relatedApp_count')
}}</span>
</div>
</div>
</template>
<template #mouseEnter>
<div @click.stop>
<el-dropdown trigger="click">
<el-button text @click.stop>
<el-icon><MoreFilled /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
icon="Refresh"
@click.stop="syncDataset(item)"
v-if="item.type === 1"
>{{ $t('views.knowledge.setting.sync') }}</el-dropdown-item
>
<el-dropdown-item @click.stop="reEmbeddingDataset(item)">
<AppIcon iconName="app-vectorization"></AppIcon>
{{ $t('views.knowledge.setting.vectorization') }}
</el-dropdown-item>
<!--
<el-dropdown-item
icon="Connection"
@ -170,23 +245,28 @@
<el-dropdown-item icon="Delete" @click.stop="deleteDataset(item)">{{
$t('common.delete')
}}</el-dropdown-item> -->
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</CardBox>
</el-col>
</template>
</el-row>
<el-empty :description="$t('common.noData')" v-else />
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</CardBox>
</el-col>
</template>
</el-row>
<el-empty :description="$t('common.noData')" v-else />
</InfiniteScroll>
</div>
</ContentContainer>
<component :is="currentCreateDialog" ref="CreateKnowledgeDialogRef" />
</LayoutContainer>
</template>
<script lang="ts" setup>
import { onMounted, ref, reactive, computed } from 'vue'
import { onMounted, ref, reactive, shallowRef, nextTick } from 'vue'
import KnowledgeIcon from '@/views/knowledge/component/KnowledgeIcon.vue'
import CreateKnowledgeDialog from './create-component/CreateKnowledgeDialog.vue'
import KnowledgeApi from '@/api/knowledge/knowledge'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import useStore from '@/stores'
@ -217,10 +297,34 @@ const paginationConfig = reactive({
})
const folderList = ref<any[]>([])
const datasetList = ref<any[]>([])
const datasetFolderList = ref<any[]>([])
const knowledgeList = ref<any[]>([])
const currentFolder = ref<any>({})
const CreateKnowledgeDialogRef = ref()
const currentCreateDialog = shallowRef<any>(null)
function openCreateDialog(data: any) {
currentCreateDialog.value = data
nextTick(() => {
CreateKnowledgeDialogRef.value.open(currentFolder.value)
})
// common.asyncGetValid(ValidType.Dataset, ValidCount.Dataset, loading).then(async (res: any) => {
// if (res?.data) {
// CreateDatasetDialogRef.value.open()
// } else if (res?.code === 400) {
// MsgConfirm(t('common.tip'), t('views.dataset.tip.professionalMessage'), {
// cancelButtonText: t('common.confirm'),
// confirmButtonText: t('common.professional'),
// })
// .then(() => {
// window.open('https://maxkb.cn/pricing.html', '_blank')
// })
// .catch(() => {})
// }
// })
}
function reEmbeddingDataset(row: any) {
KnowledgeApi.putReEmbeddingDataset('default', row.id).then(() => {
MsgSuccess(t('common.submitSuccess'))
@ -242,7 +346,7 @@ function getList() {
}
KnowledgeApi.getKnowledgeList('default', paginationConfig, params, loading).then((res) => {
paginationConfig.total = res.data.total
datasetList.value = [...datasetList.value, ...res.data.records]
knowledgeList.value = [...knowledgeList.value, ...res.data.records]
})
}
@ -257,7 +361,7 @@ function getFolder() {
function folderClickHandel(row: any) {
currentFolder.value = row
datasetList.value = []
knowledgeList.value = []
getList()
}

View File

@ -116,7 +116,6 @@
<script lang="ts" setup>
import { onMounted, ref, computed } from 'vue'
import ModelApi from '@/api/model/model'
import ProviderApi from '@/api/model/provider'
import type { Provider, Model } from '@/api/type/model'
import ModelCard from '@/views/model/component/ModelCard.vue'
@ -125,8 +124,11 @@ import { splitArray } from '@/utils/common'
import { modelTypeList, allObj } from '@/views/model/component/data'
import CreateModelDialog from '@/views/model/component/CreateModelDialog.vue'
import SelectProviderDialog from '@/views/model/component/SelectProviderDialog.vue'
import useStore from '@/stores'
import { t } from '@/locales'
const { model } = useStore()
const commonList1 = ref()
const commonList2 = ref()
const loading = ref<boolean>(false)
@ -182,15 +184,15 @@ const openCreateModel = (provider?: Provider, model_type?: string) => {
const list_model = () => {
const params = active_provider.value?.provider ? { provider: active_provider.value.provider } : {}
ModelApi.getModel('default', { ...model_search_form.value, ...params }, list_model_loading).then(
(ok) => {
model
.asyncGetModel('default', { ...model_search_form.value, ...params }, list_model_loading)
.then((ok: any) => {
model_list.value = ok.data
const v = model_list.value.map((m) => ({ id: m.user_id, username: m.username }))
if (user_options.value.length === 0) {
user_options.value = Array.from(new Map(v.map((item) => [item.id, item])).values())
}
},
)
})
}
const search_type_change = () => {