mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-24 01:37:50 +08:00
feat: enhance storage engine configuration and user notifications
- Updated the summary length requirement for document summarization to 100-300 words. - Added new localization strings for missing storage engine notifications in English, Korean, Russian, and Chinese. - Implemented UI warnings for users regarding storage engine configuration when files exist, including confirmation dialogs for storage engine changes. - Enhanced backend validation to ensure a storage engine is configured before allowing content uploads. These changes improve user experience by providing clearer guidance on storage engine requirements and enhancing the overall functionality of the knowledge base management system.
This commit is contained in:
+1
-1
@@ -141,7 +141,7 @@ conversation:
|
||||
You are a precise document summarization expert. Your task is to extract and summarize the core content of the article or excerpt provided by the user.
|
||||
|
||||
## Core Requirements
|
||||
- Summary length should be 50-100 words, adjusted flexibly based on content complexity
|
||||
- Summary length should be 100-300 words, adjusted flexibly based on content complexity
|
||||
- Generate the summary entirely based on the provided content, without adding any information not present in the article
|
||||
- Ensure the summary captures key information points and main conclusions
|
||||
- Even for complex or specialized content, you must attempt to extract core points for summarization
|
||||
|
||||
@@ -151,6 +151,9 @@ export default {
|
||||
confirmDeleteQuestion: 'Are you sure you want to delete this question? The corresponding vector index will also be removed.',
|
||||
legacyQuestionCannotDelete: 'Legacy format questions cannot be deleted. Please regenerate questions.',
|
||||
notInitialized: 'Knowledge base is not initialized. Please configure models in settings before uploading files',
|
||||
missingStorageEngine: 'This knowledge base has no storage engine selected. Please configure a storage engine in settings before uploading content.',
|
||||
missingStorageEngineUpload: 'Please configure a storage engine before uploading content',
|
||||
goToStorageSettings: 'Go to Settings',
|
||||
getInfoFailed: 'Failed to get knowledge base information, file upload is not possible',
|
||||
missingId: 'Knowledge base ID is missing',
|
||||
deleteFailed: 'Delete failed. Please try again later!',
|
||||
@@ -1442,7 +1445,8 @@ export default {
|
||||
missingId: 'Knowledge base ID is missing',
|
||||
buildDataFailed: 'Failed to construct submission data',
|
||||
updateSuccess: 'Configuration saved successfully',
|
||||
indexModeRequired: 'Please select an indexing mode for FAQ knowledge bases'
|
||||
indexModeRequired: 'Please select an indexing mode for FAQ knowledge bases',
|
||||
storageChangeConfirm: 'This knowledge base already has files. Changing the storage engine may make old files inaccessible. Do you want to proceed?'
|
||||
},
|
||||
document: {
|
||||
title: 'Document Management',
|
||||
@@ -3039,6 +3043,7 @@ export default {
|
||||
notConfigured: 'Not Configured',
|
||||
unavailable: 'Unavailable',
|
||||
lockedHint: 'This knowledge base already has files. Cannot switch storage engine. To change, please clear all files first.',
|
||||
changeWarning: 'Changing the storage engine only affects newly uploaded files. Existing files will still be read from the original storage engine, but some old files may become inaccessible if their paths cannot be automatically recognized.',
|
||||
goGlobalSettings: 'Go to Global Settings',
|
||||
engineLocal: 'Local Storage',
|
||||
engineLocalDesc: 'For single-node deployment, simple and lightweight',
|
||||
|
||||
@@ -160,6 +160,10 @@ export default {
|
||||
docActionUnsupported: "현재 지식베이스 유형은 이 작업을 지원하지 않습니다",
|
||||
notInitialized:
|
||||
"이 지식베이스는 아직 초기화되지 않았습니다. 설정 페이지에서 모델 정보를 먼저 구성한 후 파일을 업로드하세요",
|
||||
missingStorageEngine:
|
||||
"이 지식베이스에 스토리지 엔진이 선택되지 않았습니다. 콘텐츠를 업로드하기 전에 설정 페이지에서 스토리지 엔진을 구성하세요.",
|
||||
missingStorageEngineUpload: "콘텐츠를 업로드하기 전에 스토리지 엔진을 구성하세요",
|
||||
goToStorageSettings: "설정으로 이동",
|
||||
getInfoFailed: "지식베이스 정보를 가져오는 데 실패하여 파일을 업로드할 수 없습니다",
|
||||
missingId: "지식베이스 ID가 없습니다",
|
||||
deleteFailed: "삭제 실패, 나중에 다시 시도하세요!",
|
||||
@@ -1928,6 +1932,7 @@ export default {
|
||||
buildDataFailed: "데이터 구축 실패",
|
||||
updateSuccess: "설정 저장 성공",
|
||||
indexModeRequired: "FAQ 인덱스 방식을 선택해주세요",
|
||||
storageChangeConfirm: "지식베이스에 파일이 있습니다. 스토리지 엔진을 변경하면 이전 파일에 접근할 수 없게 될 수 있습니다. 계속하시겠습니까?",
|
||||
},
|
||||
document: {
|
||||
title: "문서",
|
||||
@@ -3107,6 +3112,7 @@ export default {
|
||||
notConfigured: '미구성',
|
||||
unavailable: '사용 불가',
|
||||
lockedHint: '지식베이스에 파일이 있어 스토리지 엔진을 변경할 수 없습니다. 변경하려면 모든 파일을 먼저 삭제하세요.',
|
||||
changeWarning: '스토리지 엔진을 변경하면 새로 업로드된 파일에만 영향을 미칩니다. 기존 파일은 여전히 원래 스토리지 엔진에서 읽히지만, 일부 이전 파일의 경로를 자동으로 인식할 수 없어 접근이 불가능할 수 있습니다.',
|
||||
goGlobalSettings: '전역 설정으로 이동',
|
||||
engineLocal: 'Local(로컬 스토리지)',
|
||||
engineLocalDesc: '단일 노드 배포에 적합, 간단하고 가벼움',
|
||||
|
||||
@@ -129,6 +129,9 @@ export default {
|
||||
confirmDeleteQuestion: 'Вы уверены, что хотите удалить этот вопрос? Соответствующий векторный индекс также будет удален.',
|
||||
legacyQuestionCannotDelete: 'Вопросы в устаревшем формате нельзя удалить. Пожалуйста, сгенерируйте вопросы заново.',
|
||||
notInitialized: 'База знаний не инициализирована. Пожалуйста, настройте модели в разделе настроек перед загрузкой файлов',
|
||||
missingStorageEngine: 'Для этой базы знаний не выбрано хранилище. Пожалуйста, настройте хранилище в параметрах перед загрузкой содержимого.',
|
||||
missingStorageEngineUpload: 'Пожалуйста, настройте хранилище перед загрузкой содержимого',
|
||||
goToStorageSettings: 'Перейти к настройкам',
|
||||
getInfoFailed: 'Не удалось получить информацию о базе знаний, загрузка файла невозможна',
|
||||
missingId: 'Отсутствует ID базы знаний',
|
||||
deleteFailed: 'Не удалось удалить. Пожалуйста, попробуйте позже!',
|
||||
@@ -1607,7 +1610,8 @@ export default {
|
||||
missingId: 'Отсутствует ID базы знаний',
|
||||
buildDataFailed: 'Не удалось сформировать данные для отправки',
|
||||
updateSuccess: 'Настройки сохранены',
|
||||
indexModeRequired: 'Выберите режим индексации для FAQ базы знаний'
|
||||
indexModeRequired: 'Выберите режим индексации для FAQ базы знаний',
|
||||
storageChangeConfirm: 'В базе знаний уже есть файлы. Смена хранилища может сделать старые файлы недоступными. Продолжить?'
|
||||
},
|
||||
document: {
|
||||
title: 'Управление документами',
|
||||
@@ -2638,6 +2642,7 @@ export default {
|
||||
notConfigured: 'Не настроено',
|
||||
unavailable: 'Недоступно',
|
||||
lockedHint: 'В базе знаний уже есть файлы. Невозможно сменить хранилище. Для смены сначала удалите все файлы.',
|
||||
changeWarning: 'Смена хранилища влияет только на новые загружаемые файлы. Существующие файлы по-прежнему будут читаться из прежнего хранилища, но некоторые старые файлы могут стать недоступными.',
|
||||
goGlobalSettings: 'Перейти в глобальные настройки',
|
||||
engineLocal: 'Локальное хранилище',
|
||||
engineLocalDesc: 'Для однонодового развёртывания, простое и лёгкое',
|
||||
|
||||
@@ -152,6 +152,10 @@ export default {
|
||||
docActionUnsupported: "当前知识库类型不支持该操作",
|
||||
notInitialized:
|
||||
"该知识库尚未完成初始化配置,请先前往设置页面配置模型信息后再上传文件",
|
||||
missingStorageEngine:
|
||||
"该知识库尚未选择存储引擎,请先前往设置页面配置存储引擎后再上传内容。",
|
||||
missingStorageEngineUpload: "请先配置存储引擎后再上传内容",
|
||||
goToStorageSettings: "前往配置",
|
||||
getInfoFailed: "获取知识库信息失败,无法上传文件",
|
||||
missingId: "缺少知识库ID",
|
||||
deleteFailed: "删除失败,请稍后再试!",
|
||||
@@ -1897,6 +1901,7 @@ export default {
|
||||
buildDataFailed: "数据构建失败",
|
||||
updateSuccess: "配置保存成功",
|
||||
indexModeRequired: "请选择 FAQ 的索引方式",
|
||||
storageChangeConfirm: "知识库中已有文件,更改存储引擎后旧文件可能无法正常访问。是否确认更改?",
|
||||
},
|
||||
document: {
|
||||
title: "文档",
|
||||
@@ -3036,6 +3041,7 @@ export default {
|
||||
notConfigured: "未配置",
|
||||
unavailable: "不可用",
|
||||
lockedHint: "知识库中已有文件,无法切换存储引擎。如需更换,请先清空知识库中的所有文件。",
|
||||
changeWarning: "更改存储引擎仅影响新上传的文件。已有文件仍使用原存储引擎读取,但部分旧文件可能无法自动识别而导致访问失败。",
|
||||
goGlobalSettings: "去全局设置中配置",
|
||||
engineLocal: "Local(本地存储)",
|
||||
engineLocalDesc: "仅适合单机部署,简单轻量",
|
||||
|
||||
@@ -43,6 +43,11 @@ const folderUploadInputRef = ref<HTMLInputElement | null>(null);
|
||||
const uploading = ref(false);
|
||||
const kbLoading = ref(false);
|
||||
const isFAQ = computed(() => (kbInfo.value?.type || '') === 'faq');
|
||||
const missingStorageEngine = computed(() => {
|
||||
if (!kbInfo.value || isFAQ.value) return false
|
||||
const spc = kbInfo.value.storage_provider_config
|
||||
return !spc || !spc.provider
|
||||
})
|
||||
const parserEngines = ref<ParserEngineInfo[]>([]);
|
||||
|
||||
const supportedFileTypes = computed<Set<string>>(() => {
|
||||
@@ -947,6 +952,10 @@ const ensureDocumentKbReady = () => {
|
||||
MessagePlugin.warning(t('knowledgeBase.notInitialized'));
|
||||
return false;
|
||||
}
|
||||
if (missingStorageEngine.value) {
|
||||
MessagePlugin.warning(t('knowledgeBase.missingStorageEngineUpload'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1459,6 +1468,11 @@ async function createNewSession(value: string): Promise<void> {
|
||||
<span>{{ $t('knowledgeBase.unsupportedTypesHint', { types: unsupportedFileTypes.map(t => '.' + t).join('、') }) }}</span>
|
||||
<span class="parser-hint-link">{{ $t('knowledgeBase.goToParserSettings') }} →</span>
|
||||
</p>
|
||||
<div v-if="missingStorageEngine" class="storage-engine-warning" @click="handleOpenKBSettings">
|
||||
<t-icon name="error-circle" class="warning-icon" />
|
||||
<span>{{ $t('knowledgeBase.missingStorageEngine') }}</span>
|
||||
<span class="warning-link">{{ $t('knowledgeBase.goToStorageSettings') }} →</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2714,6 +2728,41 @@ async function createNewSession(value: string): Promise<void> {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.storage-engine-warning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin: 8px 0 0;
|
||||
padding: 8px 12px;
|
||||
background: var(--td-warning-color-1, #fff8e6);
|
||||
border: 1px solid var(--td-warning-color-3, #ffcc33);
|
||||
border-radius: 6px;
|
||||
color: var(--td-warning-color);
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--td-warning-color-2, #fff1c9);
|
||||
|
||||
.warning-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.warning-link {
|
||||
color: var(--td-brand-color);
|
||||
margin-left: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { MessagePlugin } from 'tdesign-vue-next'
|
||||
import { MessagePlugin, DialogPlugin } from 'tdesign-vue-next'
|
||||
import { createKnowledgeBase, getKnowledgeBaseById, listKnowledgeFiles, updateKnowledgeBase } from '@/api/knowledge-base'
|
||||
import { updateKBConfig, type KBModelConfigRequest } from '@/api/initialization'
|
||||
import { listModels } from '@/api/model'
|
||||
@@ -229,6 +229,7 @@ const saving = ref(false)
|
||||
const loading = ref(false)
|
||||
const allModels = ref<any[]>([])
|
||||
const hasFiles = ref(false)
|
||||
const initialStorageProvider = ref<string>('')
|
||||
|
||||
const navItems = computed(() => {
|
||||
const items = [
|
||||
@@ -401,6 +402,7 @@ const loadKBData = async () => {
|
||||
questionCount: kb.question_generation_config?.question_count || 3
|
||||
},
|
||||
}
|
||||
initialStorageProvider.value = formData.value.storageProvider
|
||||
} catch (error) {
|
||||
console.error('Failed to load knowledge base data:', error)
|
||||
MessagePlugin.error(t('knowledgeEditor.messages.loadDataFailed'))
|
||||
@@ -568,6 +570,34 @@ const handleSubmit = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑模式下,若已有文件且存储引擎发生了变化,弹窗确认
|
||||
if (
|
||||
props.mode === 'edit' &&
|
||||
hasFiles.value &&
|
||||
formData.value &&
|
||||
initialStorageProvider.value &&
|
||||
formData.value.storageProvider !== initialStorageProvider.value
|
||||
) {
|
||||
const dialog = DialogPlugin.confirm({
|
||||
header: t('common.confirm'),
|
||||
body: t('knowledgeEditor.messages.storageChangeConfirm'),
|
||||
confirmBtn: t('common.confirm'),
|
||||
cancelBtn: t('common.cancel'),
|
||||
onConfirm: () => {
|
||||
dialog.destroy()
|
||||
doSubmit()
|
||||
},
|
||||
onCancel: () => {
|
||||
dialog.destroy()
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
doSubmit()
|
||||
}
|
||||
|
||||
const doSubmit = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
const data = buildSubmitData()
|
||||
@@ -653,6 +683,7 @@ const resetState = () => {
|
||||
currentSection.value = 'basic'
|
||||
formData.value = null
|
||||
hasFiles.value = false
|
||||
initialStorageProvider.value = ''
|
||||
saving.value = false
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
size="medium"
|
||||
:placeholder="$t('kbSettings.storage.selectPlaceholder')"
|
||||
style="width: 100%; min-width: 220px;"
|
||||
:disabled="props.hasFiles"
|
||||
@change="handleChange"
|
||||
>
|
||||
<t-option
|
||||
@@ -41,7 +40,7 @@
|
||||
</span>
|
||||
</t-option>
|
||||
</t-select>
|
||||
<p v-if="props.hasFiles" class="option-hint locked-hint">{{ $t('kbSettings.storage.lockedHint') }}</p>
|
||||
<p v-if="props.hasFiles" class="option-hint change-warning">{{ $t('kbSettings.storage.changeWarning') }}</p>
|
||||
<p v-else-if="selectedOption?.desc" class="option-hint">{{ selectedOption.desc }}</p>
|
||||
<a v-if="showGoSettings" href="javascript:void(0)" class="go-settings" @click.prevent="goToStorageSettings">{{ $t('kbSettings.storage.goGlobalSettings') }}</a>
|
||||
</div>
|
||||
@@ -253,6 +252,10 @@ onMounted(load)
|
||||
&.locked-hint {
|
||||
color: var(--td-warning-color);
|
||||
}
|
||||
|
||||
&.change-warning {
|
||||
color: var(--td-warning-color);
|
||||
}
|
||||
}
|
||||
|
||||
.go-settings {
|
||||
|
||||
@@ -162,6 +162,22 @@ func (s *knowledgeService) isKnowledgeDeleting(ctx context.Context, tenantID uin
|
||||
return knowledge.ParseStatus == types.ParseStatusDeleting
|
||||
}
|
||||
|
||||
// checkStorageEngineConfigured verifies that the knowledge base has a storage engine configured
|
||||
// (either at the KB level or via the tenant default). Returns an error if no storage engine is found.
|
||||
func checkStorageEngineConfigured(ctx context.Context, kb *types.KnowledgeBase) error {
|
||||
provider := kb.GetStorageProvider()
|
||||
if provider == "" {
|
||||
tenant, _ := ctx.Value(types.TenantInfoContextKey).(*types.Tenant)
|
||||
if tenant != nil && tenant.StorageEngineConfig != nil {
|
||||
provider = strings.ToLower(strings.TrimSpace(tenant.StorageEngineConfig.DefaultProvider))
|
||||
}
|
||||
}
|
||||
if provider == "" {
|
||||
return werrors.NewBadRequestError("请先为知识库选择存储引擎,再上传内容。请前往知识库设置页面进行配置。")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateKnowledgeFromFile creates a knowledge entry from an uploaded file
|
||||
func (s *knowledgeService) CreateKnowledgeFromFile(ctx context.Context,
|
||||
kbID string, file *multipart.FileHeader, metadata map[string]string, enableMultimodel *bool, customFileName string, tagID string,
|
||||
@@ -185,6 +201,10 @@ func (s *knowledgeService) CreateKnowledgeFromFile(ctx context.Context,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := checkStorageEngineConfigured(ctx, kb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查多模态配置完整性 - 只在图片文件时校验
|
||||
if !IsImageType(getFileType(fileName)) {
|
||||
logger.Info(ctx, "Non-image file with multimodal enabled, skipping COS/VLM validation")
|
||||
@@ -434,6 +454,10 @@ func (s *knowledgeService) CreateKnowledgeFromURL(ctx context.Context,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := checkStorageEngineConfigured(ctx, kb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate URL format and security
|
||||
logger.Info(ctx, "Validating URL")
|
||||
if !isValidURL(url) || !secutils.IsValidURL(url) {
|
||||
@@ -615,6 +639,10 @@ func (s *knowledgeService) createKnowledgeFromFileURL(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := checkStorageEngineConfigured(ctx, kb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate URL format and security (static check only, no HEAD request)
|
||||
if !isValidURL(fileURL) || !secutils.IsValidURL(fileURL) {
|
||||
logger.Error(ctx, "Invalid or unsafe file URL format")
|
||||
@@ -811,6 +839,10 @@ func (s *knowledgeService) CreateKnowledgeFromManual(ctx context.Context,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := checkStorageEngineConfigured(ctx, kb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tenantID := ctx.Value(types.TenantIDContextKey).(uint64)
|
||||
now := time.Now()
|
||||
title := safeTitle
|
||||
|
||||
@@ -320,9 +320,7 @@ func (h *InitializationHandler) UpdateKBConfig(c *gin.Context) {
|
||||
knowledgeList, err := h.knowledgeService.ListPagedKnowledgeByKnowledgeBaseID(ctx,
|
||||
kbIdStr, &types.Pagination{Page: 1, PageSize: 1}, "", "", "")
|
||||
if err == nil && knowledgeList != nil && knowledgeList.Total > 0 {
|
||||
logger.Error(ctx, "Cannot change storage engine when files exist")
|
||||
c.Error(errors.NewBadRequestError("知识库中已有文件,无法切换存储引擎"))
|
||||
return
|
||||
logger.Warn(ctx, "Storage engine changed with existing files, old files may become inaccessible")
|
||||
}
|
||||
}
|
||||
kb.SetStorageProvider(provider)
|
||||
|
||||
Reference in New Issue
Block a user