mirror of
https://github.com/Tencent/WeKnora.git
synced 2025-11-25 03:15:00 +08:00
Feature/add multilingual support (#384)
* feat: add multilingual support (English and Russian) - Add i18n infrastructure with vue-i18n - Implement language switcher component - Add Russian (ru-RU) and English (en-US) translations - Configure TDesign locale for proper UI component translation - Replace hardcoded Chinese strings with i18n keys - Support dynamic language switching in all components - Add translations for all UI elements including: - Menu items and navigation - Knowledge base management - Chat interface - Settings and initialization - Authentication pages - Separator options in document splitting This enables users to use the application in Chinese, English, or Russian. * chore: add vue-i18n dependency and fix Input-field i18n integration - Add vue-i18n package to frontend dependencies - Fix Input-field component i18n integration for multilingual support * chore: add PROGRESS_RU.md to .gitignore - Exclude personal progress tracking file from git * rearrange the order of the multilingual languages: Chinese, English, Russian * Delete docker-compose.yml * Replaced hardcoded messages with the t() function in the following files: all error messages, 14 console.error ,messages session creation messages , login/registration errors * fix: restore docker-compose.yml and update .gitignore * restore docker-compose.yml latest * add multilingual support
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -24,6 +24,9 @@ node_modules/
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# Docker compose файл (локальные настройки)
|
||||
# docker-compose.yml
|
||||
|
||||
WeKnora
|
||||
/models/
|
||||
services/docreader/src/proto/__pycache__
|
||||
@@ -36,3 +39,4 @@ data/files/
|
||||
### macOS
|
||||
# General
|
||||
.DS_Store
|
||||
PROGRESS_RU.md
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"tdesign-icons-vue-next": "^0.4.1",
|
||||
"tdesign-vue-next": "^1.11.5",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^9.9.0",
|
||||
"vue-router": "^4.5.0",
|
||||
"webpack": "^5.94.0"
|
||||
},
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ConfigProvider } from 'tdesign-vue-next'
|
||||
import enUS from 'tdesign-vue-next/es/locale/en_US'
|
||||
import zhCN from 'tdesign-vue-next/es/locale/zh_CN'
|
||||
import ruRU from 'tdesign-vue-next/es/locale/ru_RU'
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
const tdesignLocale = computed(() => {
|
||||
switch (locale.value) {
|
||||
case 'en-US':
|
||||
return enUS
|
||||
case 'ru-RU':
|
||||
return ruRU
|
||||
case 'zh-CN':
|
||||
default:
|
||||
return zhCN
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div id="app">
|
||||
<RouterView />
|
||||
</div>
|
||||
<ConfigProvider :global-config="tdesignLocale">
|
||||
<div id="app">
|
||||
<RouterView />
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
</template>
|
||||
<style>
|
||||
body,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, defineEmits, onMounted, defineProps, defineExpose } from "vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import useKnowledgeBase from '@/hooks/useKnowledgeBase';
|
||||
import { onBeforeRouteUpdate } from 'vue-router';
|
||||
import { MessagePlugin } from "tdesign-vue-next";
|
||||
|
||||
const { t } = useI18n();
|
||||
let { cardList, total, getKnowled } = useKnowledgeBase()
|
||||
let query = ref("");
|
||||
const props = defineProps({
|
||||
@@ -17,15 +20,15 @@ onMounted(() => {
|
||||
const emit = defineEmits(['send-msg']);
|
||||
const createSession = (val: string) => {
|
||||
if (!val.trim()) {
|
||||
MessagePlugin.info("请先输入内容!");
|
||||
MessagePlugin.info(t('chat.pleaseEnterContent'));
|
||||
return
|
||||
}
|
||||
if (!query.value && cardList.value.length == 0) {
|
||||
MessagePlugin.info("请先上传知识库!");
|
||||
MessagePlugin.info(t('chat.pleaseUploadKnowledgeBase'));
|
||||
return;
|
||||
}
|
||||
if (props.isReplying) {
|
||||
return MessagePlugin.error("正在回复中,请稍后再试!");
|
||||
return MessagePlugin.error(t('chat.replyingPleaseWait'));
|
||||
}
|
||||
emit('send-msg', val);
|
||||
clearvalue();
|
||||
@@ -50,9 +53,9 @@ onBeforeRouteUpdate((to, from, next) => {
|
||||
</script>
|
||||
<template>
|
||||
<div class="answers-input">
|
||||
<t-textarea v-model="query" placeholder="基于知识库提问" name="description" :autosize="true" @keydown="onKeydown" />
|
||||
<t-textarea v-model="query" :placeholder="t('chat.askKnowledgeBase')" name="description" :autosize="true" @keydown="onKeydown" />
|
||||
<div class="answers-input-source">
|
||||
<span>{{ total }}个来源</span>
|
||||
<span>{{ t('chat.sourcesCount', { count: total }) }}</span>
|
||||
</div>
|
||||
<div @click="createSession(query)" class="answers-input-send"
|
||||
:class="[query.length && total ? '' : 'grey-out']">
|
||||
|
||||
67
frontend/src/components/LanguageSwitcher.vue
Normal file
67
frontend/src/components/LanguageSwitcher.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="language-switcher">
|
||||
<t-select
|
||||
v-model="selectedLanguage"
|
||||
:options="languageOptions"
|
||||
@change="handleLanguageChange"
|
||||
:popup-props="{ overlayClassName: 'language-select-popup' }"
|
||||
size="small"
|
||||
>
|
||||
<template #prefixIcon>
|
||||
<t-icon name="translate" />
|
||||
</template>
|
||||
</t-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
const languageOptions = [
|
||||
{ label: '中文', value: 'zh-CN' },
|
||||
{ label: 'English', value: 'en-US' },
|
||||
{ label: 'Русский', value: 'ru-RU' }
|
||||
]
|
||||
|
||||
const selectedLanguage = ref(localStorage.getItem('locale') || 'zh-CN')
|
||||
|
||||
const handleLanguageChange = (value: string) => {
|
||||
console.log('Язык изменен на:', value)
|
||||
if (value && ['ru-RU', 'en-US', 'zh-CN'].includes(value)) {
|
||||
locale.value = value
|
||||
localStorage.setItem('locale', value)
|
||||
// Перезагрузка страницы для применения нового языка
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
// Синхронизация с i18n при инициализации
|
||||
watch(() => locale.value, (newLocale) => {
|
||||
if (selectedLanguage.value !== newLocale) {
|
||||
selectedLanguage.value = newLocale
|
||||
}
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.language-switcher {
|
||||
.t-button {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
color: #333;
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
.t-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
<template>
|
||||
<div class="empty">
|
||||
<img class="empty-img" src="@/assets/img/upload.svg" alt="">
|
||||
<span class="empty-txt">知识为空,拖放上传</span>
|
||||
<span class="empty-type-txt">pdf、doc 格式文件,不超过10M</span>
|
||||
<span class="empty-type-txt">text、markdown格式文件,不超过200K</span>
|
||||
<span class="empty-txt">{{ t('knowledgeBase.emptyKnowledgeDragDrop') }}</span>
|
||||
<span class="empty-type-txt">{{ t('knowledgeBase.pdfDocFormat') }}</span>
|
||||
<span class="empty-type-txt">{{ t('knowledgeBase.textMarkdownFormat') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="less">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="menu_icon">
|
||||
<img class="icon" :src="getImgSrc(item.icon == 'zhishiku' ? knowledgeIcon : item.icon == 'logout' ? logoutIcon : item.icon == 'tenant' ? tenantIcon : prefixIcon)" alt="">
|
||||
</div>
|
||||
<span class="menu_title" :title="item.path === 'knowledge-bases' && kbMenuItem ? kbMenuItem.title : item.title">{{ item.path === 'knowledge-bases' && kbMenuItem ? kbMenuItem.title : item.title }}</span>
|
||||
<span class="menu_title" :title="item.path === 'knowledge-bases' && kbMenuItem?.title ? kbMenuItem.title : t(item.titleKey)">{{ item.path === 'knowledge-bases' && kbMenuItem?.title ? kbMenuItem.title : t(item.titleKey) }}</span>
|
||||
<!-- 知识库切换下拉箭头 -->
|
||||
<div v-if="item.path === 'knowledge-bases' && isInKnowledgeBase"
|
||||
class="kb-dropdown-icon"
|
||||
@@ -39,7 +39,7 @@
|
||||
{{ kb.name }}
|
||||
</div>
|
||||
</div>
|
||||
<t-popup overlayInnerClassName="upload-popup" class="placement top center" content="上传知识"
|
||||
<t-popup overlayInnerClassName="upload-popup" class="placement top center" :content="t('menu.uploadKnowledge')"
|
||||
placement="top" show-arrow destroy-on-close>
|
||||
<div class="upload-file-wrap" @click.stop="uploadFile" variant="outline"
|
||||
v-if="item.path === 'knowledge-bases' && $route.name === 'knowledgeBaseDetail'">
|
||||
@@ -66,7 +66,7 @@
|
||||
<t-icon name="ellipsis" class="menu-more" />
|
||||
</div>
|
||||
<template #content>
|
||||
<span class="del_submenu">删除记录</span>
|
||||
<span class="del_submenu">{{ t('menu.deleteRecord') }}</span>
|
||||
</template>
|
||||
</t-popup>
|
||||
</div>
|
||||
@@ -74,13 +74,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 下半部分:账户信息、系统设置、退出登录 -->
|
||||
<div class="menu_bottom">
|
||||
<div class="menu_box" v-for="(item, index) in bottomMenuItems" :key="'bottom-' + index">
|
||||
<div v-if="item.path === 'logout'">
|
||||
<t-popconfirm
|
||||
content="确定要退出登录吗?"
|
||||
<t-popconfirm
|
||||
:content="t('menu.confirmLogout')"
|
||||
@confirm="handleLogout"
|
||||
placement="top"
|
||||
:show-arrow="true"
|
||||
@@ -91,7 +91,7 @@
|
||||
<div class="menu_icon">
|
||||
<img class="icon" :src="getImgSrc(logoutIcon)" alt="">
|
||||
</div>
|
||||
<span class="menu_title">{{ item.title }}</span>
|
||||
<span class="menu_title">{{ t(item.titleKey) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</t-popconfirm>
|
||||
@@ -103,7 +103,7 @@
|
||||
<div class="menu_icon">
|
||||
<img class="icon" :src="getImgSrc(item.icon == 'zhishiku' ? knowledgeIcon : item.icon == 'tenant' ? tenantIcon : prefixIcon)" alt="">
|
||||
</div>
|
||||
<span class="menu_title">{{ item.path === 'knowledge-bases' && kbMenuItem ? kbMenuItem.title : item.title }}</span>
|
||||
<span class="menu_title">{{ item.path === 'knowledge-bases' && kbMenuItem?.title ? kbMenuItem.title : t(item.titleKey) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,12 +118,14 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onMounted, watch, computed, ref, reactive, nextTick } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { getSessionsList, delSession } from "@/api/chat/index";
|
||||
import { getKnowledgeBaseById, listKnowledgeBases, uploadKnowledgeFile } from '@/api/knowledge-base';
|
||||
import { kbFileTypeVerification } from '@/utils/index';
|
||||
import { useMenuStore } from '@/stores/menu';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { MessagePlugin } from "tdesign-vue-next";
|
||||
const { t } = useI18n();
|
||||
let uploadInput = ref();
|
||||
const usemenuStore = useMenuStore();
|
||||
const authStore = useAuthStore();
|
||||
@@ -234,14 +236,14 @@ const uploadFile = async () => {
|
||||
const kb = kbResponse.data;
|
||||
|
||||
// 检查知识库是否已初始化(有 EmbeddingModelID 和 SummaryModelID)
|
||||
if (!kb.embedding_model_id || kb.embedding_model_id === '' ||
|
||||
if (!kb.embedding_model_id || kb.embedding_model_id === '' ||
|
||||
!kb.summary_model_id || kb.summary_model_id === '') {
|
||||
MessagePlugin.warning("该知识库尚未完成初始化配置,请先前往设置页面配置模型信息后再上传文件");
|
||||
MessagePlugin.warning(t('knowledgeBase.notInitialized'));
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取知识库信息失败:', error);
|
||||
MessagePlugin.error("获取知识库信息失败,无法上传文件");
|
||||
MessagePlugin.error(t('knowledgeBase.getInfoFailed'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -260,7 +262,7 @@ const upload = async (e: any) => {
|
||||
// 获取当前知识库ID
|
||||
const currentKbId = (route.params as any)?.kbId as string;
|
||||
if (!currentKbId) {
|
||||
MessagePlugin.error("缺少知识库ID");
|
||||
MessagePlugin.error(t('knowledgeBase.missingId'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -280,24 +282,24 @@ const upload = async (e: any) => {
|
||||
const isSuccess = responseData.success || responseData.code === 200 || responseData.status === 'success' || (!responseData.error && responseData);
|
||||
|
||||
if (isSuccess) {
|
||||
MessagePlugin.info("上传成功!");
|
||||
MessagePlugin.info(t('file.uploadSuccess'));
|
||||
} else {
|
||||
// 改进错误信息提取逻辑
|
||||
let errorMessage = "上传失败!";
|
||||
let errorMessage = t('file.uploadFailed');
|
||||
if (responseData.error && responseData.error.message) {
|
||||
errorMessage = responseData.error.message;
|
||||
} else if (responseData.message) {
|
||||
errorMessage = responseData.message;
|
||||
}
|
||||
if (responseData.code === 'duplicate_file' || (responseData.error && responseData.error.code === 'duplicate_file')) {
|
||||
errorMessage = "文件已存在";
|
||||
errorMessage = t('file.fileExists');
|
||||
}
|
||||
MessagePlugin.error(errorMessage);
|
||||
}
|
||||
} catch (err: any) {
|
||||
let errorMessage = "上传失败!";
|
||||
let errorMessage = t('file.uploadFailed');
|
||||
if (err.code === 'duplicate_file') {
|
||||
errorMessage = "文件已存在";
|
||||
errorMessage = t('file.fileExists');
|
||||
} else if (err.error && err.error.message) {
|
||||
errorMessage = err.error.message;
|
||||
} else if (err.message) {
|
||||
@@ -331,7 +333,7 @@ const delCard = (index: number, item: any) => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MessagePlugin.error("删除失败,请稍后再试!");
|
||||
MessagePlugin.error(t('knowledgeBase.deleteFailed'));
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -392,7 +394,7 @@ const getMessageList = async () => {
|
||||
// 过滤出当前知识库的会话
|
||||
const filtered = res.data.filter((s: any) => s.knowledge_base_id === kbId)
|
||||
filtered.forEach((item: any) => {
|
||||
let obj = { title: item.title ? item.title : "新会话", path: `chat/${kbId}/${item.id}`, id: item.id, isMore: false, isNoTitle: item.title ? false : true }
|
||||
let obj = { title: item.title ? item.title : t('menu.newSession'), path: `chat/${kbId}/${item.id}`, id: item.id, isMore: false, isNoTitle: item.title ? false : true }
|
||||
usemenuStore.updatemenuArr(obj)
|
||||
});
|
||||
loading.value = false;
|
||||
|
||||
24
frontend/src/i18n/index.ts
Normal file
24
frontend/src/i18n/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import zhCN from './locales/zh-CN.ts'
|
||||
import ruRU from './locales/ru-RU.ts'
|
||||
import enUS from './locales/en-US.ts'
|
||||
|
||||
const messages = {
|
||||
'zh-CN': zhCN,
|
||||
'en-US': enUS,
|
||||
'ru-RU': ruRU
|
||||
}
|
||||
|
||||
// Получаем сохраненный язык из localStorage или используем китайский по умолчанию
|
||||
const savedLocale = localStorage.getItem('locale') || 'zh-CN'
|
||||
console.log('i18n инициализация с языком:', savedLocale)
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: savedLocale,
|
||||
fallbackLocale: 'zh-CN',
|
||||
globalInjection: true,
|
||||
messages
|
||||
})
|
||||
|
||||
export default i18n
|
||||
553
frontend/src/i18n/locales/en-US.ts
Normal file
553
frontend/src/i18n/locales/en-US.ts
Normal file
@@ -0,0 +1,553 @@
|
||||
export default {
|
||||
menu: {
|
||||
knowledgeBase: 'Knowledge Base',
|
||||
chat: 'Chat',
|
||||
createChat: 'Create Chat',
|
||||
tenant: 'Account Info',
|
||||
settings: 'System Settings',
|
||||
logout: 'Logout',
|
||||
uploadKnowledge: 'Upload Knowledge',
|
||||
deleteRecord: 'Delete Record',
|
||||
newSession: 'New Chat',
|
||||
confirmLogout: 'Are you sure you want to logout?',
|
||||
systemInfo: 'System Information'
|
||||
},
|
||||
knowledgeBase: {
|
||||
title: 'Knowledge Base',
|
||||
list: 'Knowledge Base List',
|
||||
detail: 'Knowledge Base Details',
|
||||
create: 'Create Knowledge Base',
|
||||
edit: 'Edit Knowledge Base',
|
||||
delete: 'Delete Knowledge Base',
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
files: 'Files',
|
||||
settings: 'Settings',
|
||||
upload: 'Upload File',
|
||||
uploadSuccess: 'File uploaded successfully!',
|
||||
uploadFailed: 'File upload failed!',
|
||||
fileExists: 'File already exists',
|
||||
notInitialized: 'Knowledge base is not initialized. Please configure models in settings before uploading files',
|
||||
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!',
|
||||
createKnowledgeBase: 'Create Knowledge Base',
|
||||
knowledgeBaseName: 'Knowledge Base Name',
|
||||
enterName: 'Enter knowledge base name',
|
||||
embeddingModel: 'Embedding Model',
|
||||
selectEmbeddingModel: 'Select embedding model',
|
||||
summaryModel: 'Summary Model',
|
||||
selectSummaryModel: 'Select summary model',
|
||||
rerankModel: 'Rerank Model',
|
||||
selectRerankModel: 'Select rerank model (optional)',
|
||||
createSuccess: 'Knowledge base created successfully',
|
||||
createFailed: 'Failed to create knowledge base',
|
||||
updateSuccess: 'Knowledge base updated successfully',
|
||||
updateFailed: 'Failed to update knowledge base',
|
||||
deleteSuccess: 'Knowledge base deleted successfully',
|
||||
deleteConfirm: 'Are you sure you want to delete this knowledge base?',
|
||||
fileName: 'File Name',
|
||||
fileSize: 'File Size',
|
||||
uploadTime: 'Upload Time',
|
||||
status: 'Status',
|
||||
actions: 'Actions',
|
||||
processing: 'Processing',
|
||||
completed: 'Completed',
|
||||
failed: 'Failed',
|
||||
noFiles: 'No files',
|
||||
dragFilesHere: 'Drag files here or',
|
||||
clickToUpload: 'click to upload',
|
||||
supportedFormats: 'Supported formats',
|
||||
maxFileSize: 'Max file size',
|
||||
viewDetails: 'View Details',
|
||||
downloadFile: 'Download File',
|
||||
deleteFile: 'Delete File',
|
||||
confirmDeleteFile: 'Are you sure you want to delete this file?',
|
||||
totalFiles: 'Total files',
|
||||
totalSize: 'Total size',
|
||||
// Additional translations for KnowledgeBase.vue
|
||||
newSession: 'New Chat',
|
||||
deleteDocument: 'Delete Document',
|
||||
parsingFailed: 'Parsing failed',
|
||||
parsingInProgress: 'Parsing...',
|
||||
deleteConfirmation: 'Delete Confirmation',
|
||||
confirmDeleteDocument: 'Confirm deletion of document "{fileName}", recovery will be impossible after deletion',
|
||||
cancel: 'Cancel',
|
||||
confirmDelete: 'Confirm Delete',
|
||||
selectKnowledgeBaseFirst: 'Please select a knowledge base first',
|
||||
sessionCreationFailed: 'Failed to create chat session',
|
||||
sessionCreationError: 'Chat session creation error',
|
||||
settingsParsingFailed: 'Failed to parse settings',
|
||||
fileUploadEventReceived: 'File upload event received, uploaded knowledge base ID: {uploadedKbId}, current knowledge base ID: {currentKbId}',
|
||||
matchingKnowledgeBase: 'Matching knowledge base, starting file list update',
|
||||
routeParamChange: 'Route parameter change, re-fetching knowledge base content',
|
||||
fileUploadEventListening: 'Listening for file upload events',
|
||||
apiCallKnowledgeFiles: 'Direct API call to get knowledge base file list',
|
||||
responseInterceptorData: 'Since the response interceptor has already returned data, result is part of the response data',
|
||||
hookProcessing: 'Processing according to useKnowledgeBase hook method',
|
||||
errorHandling: 'Error handling',
|
||||
priorityCurrentPageKbId: 'Priority to use knowledge base ID of current page',
|
||||
fallbackLocalStorageKbId: 'If current page has no knowledge base ID, attempt to get knowledge base ID from settings in localStorage',
|
||||
// Additional translations for KnowledgeBaseList.vue
|
||||
createNewKnowledgeBase: 'Create Knowledge Base',
|
||||
uninitializedWarning: 'Some knowledge bases are not initialized, you need to configure model information in settings first to add knowledge documents',
|
||||
initializedStatus: 'Initialized',
|
||||
notInitializedStatus: 'Not Initialized',
|
||||
needSettingsFirst: 'You need to configure model information in settings first to add knowledge',
|
||||
documents: 'Documents',
|
||||
configureModelsFirst: 'Please configure model information in settings first',
|
||||
confirmDeleteKnowledgeBase: 'Confirm deletion of this knowledge base?',
|
||||
createKnowledgeBaseDialog: 'Create Knowledge Base',
|
||||
enterNameKb: 'Enter name',
|
||||
enterDescriptionKb: 'Enter description',
|
||||
createKb: 'Create',
|
||||
deleted: 'Deleted',
|
||||
deleteFailedKb: 'Delete failed',
|
||||
noDescription: 'No description',
|
||||
emptyKnowledgeDragDrop: 'Knowledge is empty, drag and drop to upload',
|
||||
pdfDocFormat: 'pdf, doc format files, max 10M',
|
||||
textMarkdownFormat: 'text, markdown format files, max 200K',
|
||||
dragFileNotText: 'Please drag files instead of text or links'
|
||||
},
|
||||
chat: {
|
||||
title: 'Chat',
|
||||
newChat: 'New Chat',
|
||||
inputPlaceholder: 'Enter your message...',
|
||||
send: 'Send',
|
||||
thinking: 'Thinking...',
|
||||
regenerate: 'Regenerate',
|
||||
copy: 'Copy',
|
||||
delete: 'Delete',
|
||||
reference: 'Reference',
|
||||
noMessages: 'No messages',
|
||||
// Additional translations for chat components
|
||||
waitingForAnswer: 'Waiting for answer...',
|
||||
cannotAnswer: 'Sorry, I cannot answer this question.',
|
||||
summarizingAnswer: 'Summarizing answer...',
|
||||
loading: 'Loading...',
|
||||
enterDescription: 'Enter description',
|
||||
referencedContent: '{count} related materials used',
|
||||
deepThinking: 'Deep thinking completed',
|
||||
knowledgeBaseQandA: 'Knowledge Base Q&A',
|
||||
askKnowledgeBase: 'Ask the knowledge base',
|
||||
sourcesCount: '{count} sources',
|
||||
pleaseEnterContent: 'Please enter content!',
|
||||
pleaseUploadKnowledgeBase: 'Please upload knowledge base first!',
|
||||
replyingPleaseWait: 'Replying, please try again later!',
|
||||
createSessionFailed: 'Failed to create session',
|
||||
createSessionError: 'Session creation error',
|
||||
unableToGetKnowledgeBaseId: 'Unable to get knowledge base ID'
|
||||
},
|
||||
settings: {
|
||||
title: 'Settings',
|
||||
system: 'System Settings',
|
||||
systemConfig: 'System Configuration',
|
||||
knowledgeBaseSettings: 'Knowledge Base Settings',
|
||||
configureKbModels: 'Configure models and document splitting parameters for this knowledge base',
|
||||
manageSystemModels: 'Manage and update system models and service configurations',
|
||||
basicInfo: 'Basic Information',
|
||||
documentSplitting: 'Document Splitting',
|
||||
apiEndpoint: 'API Endpoint',
|
||||
enterApiEndpoint: 'Enter API endpoint, e.g.: http://localhost',
|
||||
enterApiKey: 'Enter API key',
|
||||
enterKnowledgeBaseId: 'Enter knowledge base ID',
|
||||
saveConfig: 'Save Configuration',
|
||||
reset: 'Reset',
|
||||
configSaved: 'Configuration saved successfully',
|
||||
enterApiEndpointRequired: 'Enter API endpoint',
|
||||
enterApiKeyRequired: 'Enter API key',
|
||||
enterKnowledgeBaseIdRequired: 'Enter knowledge base ID',
|
||||
name: 'Name',
|
||||
enterName: 'Enter name',
|
||||
description: 'Description',
|
||||
chunkSize: 'Chunk Size',
|
||||
chunkOverlap: 'Chunk Overlap',
|
||||
save: 'Save',
|
||||
saving: 'Saving...',
|
||||
saveSuccess: 'Saved successfully',
|
||||
saveFailed: 'Failed to save',
|
||||
model: 'Model',
|
||||
llmModel: 'LLM Model',
|
||||
embeddingModel: 'Embedding Model',
|
||||
rerankModel: 'Rerank Model',
|
||||
vlmModel: 'Multimodal Model',
|
||||
modelName: 'Model Name',
|
||||
modelUrl: 'Model URL',
|
||||
apiKey: 'API Key',
|
||||
cancel: 'Cancel',
|
||||
saveFailedSettings: 'Failed to save settings',
|
||||
enterNameRequired: 'Enter name'
|
||||
},
|
||||
initialization: {
|
||||
title: 'Initialization',
|
||||
welcome: 'Welcome to WeKnora',
|
||||
description: 'Please configure the system before starting',
|
||||
step1: 'Step 1: Configure LLM Model',
|
||||
step2: 'Step 2: Configure Embedding Model',
|
||||
step3: 'Step 3: Configure Additional Models',
|
||||
complete: 'Complete Initialization',
|
||||
skip: 'Skip',
|
||||
next: 'Next',
|
||||
previous: 'Previous',
|
||||
// Ollama service
|
||||
ollamaServiceStatus: 'Ollama Service Status',
|
||||
refreshStatus: 'Refresh Status',
|
||||
ollamaServiceAddress: 'Ollama Service Address',
|
||||
notConfigured: 'Not Configured',
|
||||
notRunning: 'Not Running',
|
||||
normal: 'Normal',
|
||||
installedModels: 'Installed Models',
|
||||
none: 'None temporarily',
|
||||
// Knowledge base
|
||||
knowledgeBaseInfo: 'Knowledge Base Information',
|
||||
knowledgeBaseName: 'Knowledge Base Name',
|
||||
knowledgeBaseNamePlaceholder: 'Enter knowledge base name',
|
||||
knowledgeBaseDescription: 'Knowledge Base Description',
|
||||
knowledgeBaseDescriptionPlaceholder: 'Enter knowledge base description',
|
||||
// LLM model
|
||||
llmModelConfig: 'LLM Large Language Model Configuration',
|
||||
modelSource: 'Model Source',
|
||||
local: 'Ollama (Local)',
|
||||
remote: 'Remote API (Remote)',
|
||||
modelName: 'Model Name',
|
||||
modelNamePlaceholder: 'E.g.: qwen3:0.6b',
|
||||
baseUrl: 'Base URL',
|
||||
baseUrlPlaceholder: 'E.g.: https://api.openai.com/v1, remove /chat/completions from the end of URL',
|
||||
apiKey: 'API Key (Optional)',
|
||||
apiKeyPlaceholder: 'Enter API Key (Optional)',
|
||||
downloadModel: 'Download Model',
|
||||
installed: 'Installed',
|
||||
notInstalled: 'Not Installed',
|
||||
notChecked: 'Not Checked',
|
||||
checkConnection: 'Check Connection',
|
||||
connectionNormal: 'Connection Normal',
|
||||
connectionFailed: 'Connection Failed',
|
||||
checkingConnection: 'Checking Connection',
|
||||
// Embedding model
|
||||
embeddingModelConfig: 'Embedding Model Configuration',
|
||||
embeddingWarning: 'Knowledge base already has files, cannot change embedding model configuration',
|
||||
dimension: 'Dimension',
|
||||
dimensionPlaceholder: 'Enter vector dimension',
|
||||
detectDimension: 'Detect Dimension',
|
||||
// Rerank model
|
||||
rerankModelConfig: 'Rerank Model Configuration',
|
||||
enableRerank: 'Enable Rerank Model',
|
||||
// Multimodal settings
|
||||
multimodalConfig: 'Multimodal Configuration',
|
||||
enableMultimodal: 'Enable image information extraction',
|
||||
visualLanguageModelConfig: 'Visual Language Model Configuration',
|
||||
interfaceType: 'Interface Type',
|
||||
openaiCompatible: 'OpenAI Compatible Interface',
|
||||
// Storage settings
|
||||
storageServiceConfig: 'Storage Service Configuration',
|
||||
storageType: 'Storage Type',
|
||||
bucketName: 'Bucket Name',
|
||||
bucketNamePlaceholder: 'Enter Bucket name',
|
||||
pathPrefix: 'Path Prefix',
|
||||
pathPrefixPlaceholder: 'E.g.: images',
|
||||
secretId: 'Secret ID',
|
||||
secretIdPlaceholder: 'Enter COS Secret ID',
|
||||
secretKey: 'Secret Key',
|
||||
secretKeyPlaceholder: 'Enter COS Secret Key',
|
||||
region: 'Region',
|
||||
regionPlaceholder: 'E.g.: ap-beijing',
|
||||
appId: 'App ID',
|
||||
appIdPlaceholder: 'Enter App ID',
|
||||
// Multimodal function testing
|
||||
functionTest: 'Function Test',
|
||||
testDescription: 'Upload an image to test the model\'s image description and text recognition functions',
|
||||
selectImage: 'Select Image',
|
||||
startTest: 'Start Test',
|
||||
testResult: 'Test Result',
|
||||
imageDescription: 'Image Description:',
|
||||
textRecognition: 'Text Recognition:',
|
||||
processingTime: 'Processing Time:',
|
||||
testFailed: 'Test Failed',
|
||||
multimodalProcessingFailed: 'Multimodal processing failed',
|
||||
// Document splitting
|
||||
documentSplittingConfig: 'Document Splitting Configuration',
|
||||
splittingStrategy: 'Splitting Strategy',
|
||||
balancedMode: 'Balanced Mode',
|
||||
balancedModeDesc: 'Chunk size: 1000 / Overlap: 200',
|
||||
precisionMode: 'Precision Mode',
|
||||
precisionModeDesc: 'Chunk size: 512 / Overlap: 100',
|
||||
contextMode: 'Context Mode',
|
||||
contextModeDesc: 'Chunk size: 2048 / Overlap: 400',
|
||||
custom: 'Custom',
|
||||
customDesc: 'Configure parameters manually',
|
||||
chunkSize: 'Chunk Size',
|
||||
chunkOverlap: 'Chunk Overlap',
|
||||
separatorSettings: 'Separator Settings',
|
||||
selectOrCustomSeparators: 'Select or customize separators',
|
||||
characters: 'characters',
|
||||
separatorParagraph: 'Paragraph separator (\\n\\n)',
|
||||
separatorNewline: 'Newline (\\n)',
|
||||
separatorPeriod: 'Period (。)',
|
||||
separatorExclamation: 'Exclamation mark (!)',
|
||||
separatorQuestion: 'Question mark (?)',
|
||||
separatorSemicolon: 'Semicolon (;)',
|
||||
separatorChineseSemicolon: 'Chinese semicolon (;)',
|
||||
separatorComma: 'Comma (,)',
|
||||
separatorChineseComma: 'Chinese comma (,)',
|
||||
// Entity and relation extraction
|
||||
entityRelationExtraction: 'Entity and Relation Extraction',
|
||||
enableEntityRelationExtraction: 'Enable entity and relation extraction',
|
||||
relationTypeConfig: 'Relation Type Configuration',
|
||||
relationType: 'Relation Type',
|
||||
generateRandomTags: 'Generate Random Tags',
|
||||
completeModelConfig: 'Please complete model configuration',
|
||||
systemWillExtract: 'The system will extract corresponding entities and relations from the text according to the selected relation types',
|
||||
extractionExample: 'Extraction Example',
|
||||
sampleText: 'Sample Text',
|
||||
sampleTextPlaceholder: 'Enter text for analysis, e.g.: "Red Mansion", also known as "Dream of the Red Chamber", is one of the four great classical novels of Chinese literature, written by Cao Xueqin during the Qing Dynasty...',
|
||||
generateRandomText: 'Generate Random Text',
|
||||
entityList: 'Entity List',
|
||||
nodeName: 'Node Name',
|
||||
nodeNamePlaceholder: 'Node name',
|
||||
addAttribute: 'Add Attribute',
|
||||
attributeValue: 'Attribute Value',
|
||||
attributeValuePlaceholder: 'Attribute value',
|
||||
addEntity: 'Add Entity',
|
||||
completeEntityInfo: 'Please complete entity information',
|
||||
relationConnection: 'Relation Connection',
|
||||
selectEntity: 'Select Entity',
|
||||
addRelation: 'Add Relation',
|
||||
completeRelationInfo: 'Please complete relation information',
|
||||
startExtraction: 'Start Extraction',
|
||||
extracting: 'Extracting...',
|
||||
defaultExample: 'Default Example',
|
||||
clearExample: 'Clear Example',
|
||||
// Buttons and messages
|
||||
updateKnowledgeBaseSettings: 'Update Knowledge Base Settings',
|
||||
updateConfigInfo: 'Update Configuration Information',
|
||||
completeConfig: 'Complete Configuration',
|
||||
waitForDownloads: 'Please wait for all Ollama models to finish downloading before updating configuration',
|
||||
completeModelConfigInfo: 'Please complete model configuration information',
|
||||
knowledgeBaseIdMissing: 'Knowledge base ID is missing',
|
||||
knowledgeBaseSettingsUpdateSuccess: 'Knowledge base settings updated successfully',
|
||||
configUpdateSuccess: 'Configuration updated successfully',
|
||||
systemInitComplete: 'System initialization completed',
|
||||
operationFailed: 'Operation failed',
|
||||
updateKnowledgeBaseInfoFailed: 'Failed to update knowledge base basic information',
|
||||
knowledgeBaseIdMissingCannotSave: 'Knowledge base ID is missing, cannot save configuration',
|
||||
operationFailedCheckNetwork: 'Operation failed, please check network connection',
|
||||
imageUploadSuccess: 'Image uploaded successfully, testing can begin',
|
||||
multimodalConfigIncomplete: 'Multimodal configuration incomplete, please complete multimodal configuration before uploading images',
|
||||
pleaseSelectImage: 'Please select an image',
|
||||
multimodalTestSuccess: 'Multimodal test successful',
|
||||
multimodalTestFailed: 'Multimodal test failed',
|
||||
pleaseEnterSampleText: 'Please enter sample text',
|
||||
pleaseEnterRelationType: 'Please enter relation type',
|
||||
pleaseEnterLLMModelConfig: 'Please enter LLM large language model configuration',
|
||||
noValidNodesExtracted: 'No valid nodes extracted',
|
||||
noValidRelationsExtracted: 'No valid relations extracted',
|
||||
extractionFailedCheckNetwork: 'Extraction failed, please check network or text format',
|
||||
generateFailedRetry: 'Generation failed, please try again',
|
||||
pleaseCheckForm: 'Please check form correctness',
|
||||
detectionSuccessful: 'Detection successful, dimension automatically filled as',
|
||||
detectionFailed: 'Detection failed',
|
||||
detectionFailedCheckConfig: 'Detection failed, please check configuration',
|
||||
modelDownloadSuccess: 'Model downloaded successfully',
|
||||
modelDownloadFailed: 'Model download failed',
|
||||
downloadStartFailed: 'Download start failed',
|
||||
queryProgressFailed: 'Progress query failed',
|
||||
checkOllamaStatusFailed: 'Ollama status check failed',
|
||||
getKnowledgeBaseInfoFailed: 'Failed to get knowledge base information',
|
||||
textRelationExtractionFailed: 'Text relation extraction failed',
|
||||
// Validation
|
||||
pleaseEnterKnowledgeBaseName: 'Please enter knowledge base name',
|
||||
knowledgeBaseNameLength: 'Knowledge base name length must be 1-50 characters',
|
||||
knowledgeBaseDescriptionLength: 'Knowledge base description cannot exceed 200 characters',
|
||||
pleaseEnterLLMModelName: 'Please enter LLM model name',
|
||||
pleaseEnterBaseURL: 'Please enter BaseURL',
|
||||
pleaseEnterEmbeddingModelName: 'Please enter embedding model name',
|
||||
pleaseEnterEmbeddingDimension: 'Please enter embedding dimension',
|
||||
dimensionMustBeInteger: 'Dimension must be a valid integer, usually 768, 1024, 1536, 3584, etc.',
|
||||
pleaseEnterTextContent: 'Please enter text content',
|
||||
textContentMinLength: 'Text content must contain at least 10 characters',
|
||||
pleaseEnterValidTag: 'Please enter a valid tag',
|
||||
tagAlreadyExists: 'This tag already exists',
|
||||
// Additional translations for InitializationContent.vue
|
||||
checkFailed: 'Check failed',
|
||||
startingDownload: 'Starting download...',
|
||||
downloadStarted: 'Download started',
|
||||
model: 'Model',
|
||||
startModelDownloadFailed: 'Failed to start model download',
|
||||
downloadCompleted: 'Download completed',
|
||||
downloadFailed: 'Download failed',
|
||||
knowledgeBaseSettingsModeMissingId: 'Knowledge base settings mode missing ID',
|
||||
completeEmbeddingConfig: 'Please complete embedding configuration first',
|
||||
detectionSuccess: 'Detection successful,',
|
||||
dimensionAutoFilled: 'dimension automatically filled:',
|
||||
checkFormCorrectness: 'Please check form correctness',
|
||||
systemInitializationCompleted: 'System initialization completed',
|
||||
generationFailedRetry: 'Generation failed, please try again',
|
||||
chunkSizeDesc: 'Size of each text chunk. Larger chunks preserve more context but may reduce search accuracy.',
|
||||
chunkOverlapDesc: 'Number of characters overlapping between adjacent chunks. Helps maintain context at chunk boundaries.',
|
||||
selectRelationType: 'Select relation type'
|
||||
},
|
||||
auth: {
|
||||
login: 'Login',
|
||||
logout: 'Logout',
|
||||
username: 'Username',
|
||||
email: 'Email',
|
||||
password: 'Password',
|
||||
confirmPassword: 'Confirm Password',
|
||||
rememberMe: 'Remember Me',
|
||||
forgotPassword: 'Forgot Password?',
|
||||
loginSuccess: 'Login successful!',
|
||||
loginFailed: 'Login failed',
|
||||
loggingIn: 'Logging in...',
|
||||
register: 'Register',
|
||||
registering: 'Registering...',
|
||||
createAccount: 'Create Account',
|
||||
haveAccount: 'Already have an account?',
|
||||
noAccount: 'Don\'t have an account?',
|
||||
backToLogin: 'Back to Login',
|
||||
registerNow: 'Register Now',
|
||||
registerSuccess: 'Registration successful! The system has created an exclusive tenant for you, please login',
|
||||
registerFailed: 'Registration failed',
|
||||
subtitle: 'Document understanding and semantic search framework based on large models',
|
||||
registerSubtitle: 'The system will create an exclusive tenant for you after registration',
|
||||
emailPlaceholder: 'Enter email address',
|
||||
passwordPlaceholder: 'Enter password (8-32 characters, including letters and numbers)',
|
||||
confirmPasswordPlaceholder: 'Enter password again',
|
||||
usernamePlaceholder: 'Enter username',
|
||||
emailRequired: 'Enter email address',
|
||||
emailInvalid: 'Enter correct email format',
|
||||
passwordRequired: 'Enter password',
|
||||
passwordMinLength: 'Password must be at least 8 characters',
|
||||
passwordMaxLength: 'Password cannot exceed 32 characters',
|
||||
passwordMustContainLetter: 'Password must contain letters',
|
||||
passwordMustContainNumber: 'Password must contain numbers',
|
||||
usernameRequired: 'Enter username',
|
||||
usernameMinLength: 'Username must be at least 2 characters',
|
||||
usernameMaxLength: 'Username cannot exceed 20 characters',
|
||||
usernameInvalid: 'Username can only contain letters, numbers, underscores and Chinese characters',
|
||||
confirmPasswordRequired: 'Confirm password',
|
||||
passwordMismatch: 'Entered passwords do not match',
|
||||
loginError: 'Login error, please check email or password',
|
||||
loginErrorRetry: 'Login error, please try again later',
|
||||
registerError: 'Registration error, please try again later',
|
||||
forgotPasswordNotAvailable: 'Password recovery function is temporarily unavailable, please contact administrator'
|
||||
},
|
||||
common: {
|
||||
confirm: 'Confirm',
|
||||
cancel: 'Cancel',
|
||||
save: 'Save',
|
||||
delete: 'Delete',
|
||||
edit: 'Edit',
|
||||
create: 'Create',
|
||||
search: 'Search',
|
||||
filter: 'Filter',
|
||||
export: 'Export',
|
||||
import: 'Import',
|
||||
upload: 'Upload',
|
||||
download: 'Download',
|
||||
refresh: 'Refresh',
|
||||
loading: 'Loading...',
|
||||
noData: 'No data',
|
||||
error: 'Error',
|
||||
success: 'Success',
|
||||
warning: 'Warning',
|
||||
info: 'Information',
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
ok: 'OK',
|
||||
close: 'Close',
|
||||
back: 'Back',
|
||||
next: 'Next',
|
||||
finish: 'Finish',
|
||||
all: 'All',
|
||||
reset: 'Reset',
|
||||
clear: 'Clear'
|
||||
},
|
||||
file: {
|
||||
upload: 'Upload File',
|
||||
uploadSuccess: 'File uploaded successfully',
|
||||
uploadFailed: 'File upload failed',
|
||||
delete: 'Delete File',
|
||||
deleteSuccess: 'File deleted successfully',
|
||||
deleteFailed: 'File deletion failed',
|
||||
download: 'Download File',
|
||||
preview: 'Preview',
|
||||
unsupportedFormat: 'Unsupported file format',
|
||||
maxSizeExceeded: 'Maximum file size exceeded',
|
||||
selectFile: 'Select File'
|
||||
},
|
||||
tenant: {
|
||||
title: 'Tenant Information',
|
||||
name: 'Tenant Name',
|
||||
id: 'Tenant ID',
|
||||
createdAt: 'Created At',
|
||||
updatedAt: 'Updated At',
|
||||
status: 'Status',
|
||||
active: 'Active',
|
||||
inactive: 'Inactive',
|
||||
// Additional translations for TenantInfo.vue
|
||||
systemInfo: 'System Information',
|
||||
viewSystemInfo: 'View system version and user account configuration information',
|
||||
version: 'Version',
|
||||
buildTime: 'Build Time',
|
||||
goVersion: 'Go Version',
|
||||
userInfo: 'User Information',
|
||||
userId: 'User ID',
|
||||
username: 'Username',
|
||||
email: 'Email',
|
||||
tenantInfo: 'Tenant Information',
|
||||
tenantId: 'Tenant ID',
|
||||
tenantName: 'Tenant Name',
|
||||
description: 'Description',
|
||||
business: 'Business',
|
||||
noDescription: 'No description',
|
||||
noBusiness: 'None',
|
||||
statusActive: 'Active',
|
||||
statusInactive: 'Not activated',
|
||||
statusSuspended: 'Suspended',
|
||||
statusUnknown: 'Unknown',
|
||||
apiKey: 'API Key',
|
||||
keepApiKeySafe: 'Please keep your API Key safe, do not disclose it in public places or code repositories',
|
||||
storageInfo: 'Storage Information',
|
||||
storageQuota: 'Storage Quota',
|
||||
used: 'Used',
|
||||
usage: 'Usage',
|
||||
apiDevDocs: 'API Developer Documentation',
|
||||
useApiKey: 'Use your API Key to start development, view complete API documentation and code examples.',
|
||||
viewApiDoc: 'View API Documentation',
|
||||
loadingAccountInfo: 'Loading account information...',
|
||||
loadFailed: 'Load failed',
|
||||
retry: 'Retry',
|
||||
apiKeyCopied: 'API Key copied to clipboard',
|
||||
unknown: 'Unknown',
|
||||
formatError: 'Format error'
|
||||
},
|
||||
error: {
|
||||
network: 'Network error',
|
||||
server: 'Server error',
|
||||
notFound: 'Not found',
|
||||
unauthorized: 'Unauthorized',
|
||||
forbidden: 'Access forbidden',
|
||||
unknown: 'Unknown error',
|
||||
tryAgain: 'Please try again'
|
||||
},
|
||||
model: {
|
||||
llmModel: 'LLM Model',
|
||||
embeddingModel: 'Embedding Model',
|
||||
rerankModel: 'Rerank Model',
|
||||
vlmModel: 'Multimodal Model',
|
||||
modelName: 'Model Name',
|
||||
modelProvider: 'Model Provider',
|
||||
modelUrl: 'Model URL',
|
||||
apiKey: 'API Key',
|
||||
testConnection: 'Test Connection',
|
||||
connectionSuccess: 'Connection successful',
|
||||
connectionFailed: 'Connection failed',
|
||||
dimension: 'Dimension',
|
||||
maxTokens: 'Max Tokens',
|
||||
temperature: 'Temperature',
|
||||
topP: 'Top P',
|
||||
selectModel: 'Select Model',
|
||||
customModel: 'Custom Model',
|
||||
builtinModel: 'Built-in Model'
|
||||
}
|
||||
}
|
||||
553
frontend/src/i18n/locales/ru-RU.ts
Normal file
553
frontend/src/i18n/locales/ru-RU.ts
Normal file
@@ -0,0 +1,553 @@
|
||||
export default {
|
||||
menu: {
|
||||
knowledgeBase: 'База знаний',
|
||||
chat: 'Диалог',
|
||||
createChat: 'Создать диалог',
|
||||
tenant: 'Информация об аккаунте',
|
||||
settings: 'Настройки системы',
|
||||
logout: 'Выход',
|
||||
uploadKnowledge: 'Загрузить знания',
|
||||
deleteRecord: 'Удалить запись',
|
||||
newSession: 'Новый диалог',
|
||||
confirmLogout: 'Вы уверены, что хотите выйти?',
|
||||
systemInfo: 'Информация о системе'
|
||||
},
|
||||
knowledgeBase: {
|
||||
title: 'База знаний',
|
||||
list: 'Список баз знаний',
|
||||
detail: 'Детали базы знаний',
|
||||
create: 'Создать базу знаний',
|
||||
edit: 'Редактировать базу знаний',
|
||||
delete: 'Удалить базу знаний',
|
||||
name: 'Название',
|
||||
description: 'Описание',
|
||||
files: 'Файлы',
|
||||
settings: 'Настройки',
|
||||
upload: 'Загрузить файл',
|
||||
uploadSuccess: 'Файл успешно загружен!',
|
||||
uploadFailed: 'Ошибка загрузки файла!',
|
||||
fileExists: 'Файл уже существует',
|
||||
notInitialized: 'База знаний не инициализирована. Пожалуйста, настройте модели в разделе настроек перед загрузкой файлов',
|
||||
getInfoFailed: 'Не удалось получить информацию о базе знаний, загрузка файла невозможна',
|
||||
missingId: 'Отсутствует ID базы знаний',
|
||||
deleteFailed: 'Не удалось удалить. Пожалуйста, попробуйте позже!',
|
||||
createKnowledgeBase: 'Создать базу знаний',
|
||||
knowledgeBaseName: 'Название базы знаний',
|
||||
enterName: 'Введите название базы знаний',
|
||||
embeddingModel: 'Модель встраивания',
|
||||
selectEmbeddingModel: 'Выберите модель встраивания',
|
||||
summaryModel: 'Модель суммаризации',
|
||||
selectSummaryModel: 'Выберите модель суммаризации',
|
||||
rerankModel: 'Модель ранжирования',
|
||||
selectRerankModel: 'Выберите модель ранжирования (опционально)',
|
||||
createSuccess: 'База знаний успешно создана',
|
||||
createFailed: 'Не удалось создать базу знаний',
|
||||
updateSuccess: 'База знаний успешно обновлена',
|
||||
updateFailed: 'Не удалось обновить базу знаний',
|
||||
deleteSuccess: 'База знаний успешно удалена',
|
||||
deleteConfirm: 'Вы уверены, что хотите удалить эту базу знаний?',
|
||||
fileName: 'Имя файла',
|
||||
fileSize: 'Размер файла',
|
||||
uploadTime: 'Время загрузки',
|
||||
status: 'Статус',
|
||||
actions: 'Действия',
|
||||
processing: 'Обработка',
|
||||
completed: 'Завершено',
|
||||
failed: 'Ошибка',
|
||||
noFiles: 'Нет файлов',
|
||||
dragFilesHere: 'Перетащите файлы сюда или',
|
||||
clickToUpload: 'нажмите для загрузки',
|
||||
supportedFormats: 'Поддерживаемые форматы',
|
||||
maxFileSize: 'Макс. размер файла',
|
||||
viewDetails: 'Просмотр деталей',
|
||||
downloadFile: 'Скачать файл',
|
||||
deleteFile: 'Удалить файл',
|
||||
confirmDeleteFile: 'Вы уверены, что хотите удалить этот файл?',
|
||||
totalFiles: 'Всего файлов',
|
||||
totalSize: 'Общий размер',
|
||||
// Дополнительные переводы для KnowledgeBase.vue
|
||||
newSession: 'Новый диалог',
|
||||
deleteDocument: 'Удалить документ',
|
||||
parsingFailed: 'Парсинг не удался',
|
||||
parsingInProgress: 'Парсинг...',
|
||||
deleteConfirmation: 'Подтверждение удаления',
|
||||
confirmDeleteDocument: 'Подтвердить удаление документа "{fileName}", после удаления восстановление невозможно',
|
||||
cancel: 'Отмена',
|
||||
confirmDelete: 'Подтвердить удаление',
|
||||
selectKnowledgeBaseFirst: 'Пожалуйста, сначала выберите базу знаний',
|
||||
sessionCreationFailed: 'Не удалось создать диалог',
|
||||
sessionCreationError: 'Ошибка создания диалога',
|
||||
settingsParsingFailed: 'Не удалось разобрать настройки',
|
||||
fileUploadEventReceived: 'Получено событие загрузки файла, загруженный ID базы знаний: {uploadedKbId}, текущий ID базы знаний: {currentKbId}',
|
||||
matchingKnowledgeBase: 'Совпадающая база знаний, начинаем обновление списка файлов',
|
||||
routeParamChange: 'Изменение параметров маршрута, повторное получение содержимого базы знаний',
|
||||
fileUploadEventListening: 'Прослушивание события загрузки файла',
|
||||
apiCallKnowledgeFiles: 'Прямой вызов API для получения списка файлов базы знаний',
|
||||
responseInterceptorData: 'Поскольку перехватчик ответа уже вернул data, result является частью данных ответа',
|
||||
hookProcessing: 'Обработка в соответствии со способом useKnowledgeBase hook',
|
||||
errorHandling: 'Обработка ошибок',
|
||||
priorityCurrentPageKbId: 'Приоритет использования ID базы знаний текущей страницы',
|
||||
fallbackLocalStorageKbId: 'Если на текущей странице нет ID базы знаний, попытка получить ID базы знаний из настроек в localStorage',
|
||||
// Дополнительные переводы для KnowledgeBaseList.vue
|
||||
createNewKnowledgeBase: 'Создать базу знаний',
|
||||
uninitializedWarning: 'Некоторые базы знаний не инициализированы, необходимо сначала настроить информацию о моделях в настройках, чтобы добавить документы знаний',
|
||||
initializedStatus: 'Инициализирована',
|
||||
notInitializedStatus: 'Не инициализирована',
|
||||
needSettingsFirst: 'Необходимо сначала настроить информацию о моделях в настройках, чтобы добавить знания',
|
||||
documents: 'Документы',
|
||||
configureModelsFirst: 'Пожалуйста, сначала настройте информацию о моделях в настройках',
|
||||
confirmDeleteKnowledgeBase: 'Подтвердить удаление этой базы знаний?',
|
||||
createKnowledgeBaseDialog: 'Создать базу знаний',
|
||||
enterNameKb: 'Введите название',
|
||||
enterDescriptionKb: 'Введите описание',
|
||||
createKb: 'Создать',
|
||||
deleted: 'Удалено',
|
||||
deleteFailedKb: 'Не удалось удалить',
|
||||
noDescription: 'Нет описания',
|
||||
emptyKnowledgeDragDrop: 'База знаний пуста, перетащите файлы для загрузки',
|
||||
pdfDocFormat: 'Файлы pdf, doc формата, не более 10 МБ',
|
||||
textMarkdownFormat: 'Файлы text, markdown формата, не более 200 КБ',
|
||||
dragFileNotText: 'Пожалуйста, перетащите файлы, а не текст или ссылки'
|
||||
},
|
||||
chat: {
|
||||
title: 'Диалог',
|
||||
newChat: 'Новый чат',
|
||||
inputPlaceholder: 'Введите ваше сообщение...',
|
||||
send: 'Отправить',
|
||||
thinking: 'Думаю...',
|
||||
regenerate: 'Сгенерировать заново',
|
||||
copy: 'Копировать',
|
||||
delete: 'Удалить',
|
||||
reference: 'Ссылка',
|
||||
noMessages: 'Нет сообщений',
|
||||
// Дополнительные переводы для компонентов чата
|
||||
waitingForAnswer: 'Ожидание ответа...',
|
||||
cannotAnswer: 'Извините, я не могу ответить на этот вопрос.',
|
||||
summarizingAnswer: 'Подведение итогов ответа...',
|
||||
loading: 'Загрузка...',
|
||||
enterDescription: 'Введите описание',
|
||||
referencedContent: 'Использовано {count} связанных материалов',
|
||||
deepThinking: 'Глубокое мышление завершено',
|
||||
knowledgeBaseQandA: 'Вопросы и ответы на основе базы знаний',
|
||||
askKnowledgeBase: 'Задайте вопрос базе знаний',
|
||||
sourcesCount: '{count} источников',
|
||||
pleaseEnterContent: 'Пожалуйста, введите содержимое!',
|
||||
pleaseUploadKnowledgeBase: 'Пожалуйста, сначала загрузите базу знаний!',
|
||||
replyingPleaseWait: 'Идёт ответ, пожалуйста, попробуйте позже!',
|
||||
createSessionFailed: 'Не удалось создать сеанс',
|
||||
createSessionError: 'Ошибка создания сеанса',
|
||||
unableToGetKnowledgeBaseId: 'Невозможно получить ID базы знаний'
|
||||
},
|
||||
settings: {
|
||||
title: 'Настройки',
|
||||
system: 'Настройки системы',
|
||||
systemConfig: 'Системная конфигурация',
|
||||
knowledgeBaseSettings: 'Настройки базы знаний',
|
||||
configureKbModels: 'Настройка моделей и параметров разделения документов для этой базы знаний',
|
||||
manageSystemModels: 'Управление и обновление системных моделей и конфигураций сервисов',
|
||||
basicInfo: 'Основная информация',
|
||||
documentSplitting: 'Разделение документов',
|
||||
apiEndpoint: 'API конечная точка',
|
||||
enterApiEndpoint: 'Введите API конечную точку, например: http://localhost',
|
||||
enterApiKey: 'Введите API ключ',
|
||||
enterKnowledgeBaseId: 'Введите ID базы знаний',
|
||||
saveConfig: 'Сохранить конфигурацию',
|
||||
reset: 'Сбросить',
|
||||
configSaved: 'Конфигурация сохранена успешно',
|
||||
enterApiEndpointRequired: 'Введите API конечную точку',
|
||||
enterApiKeyRequired: 'Введите API ключ',
|
||||
enterKnowledgeBaseIdRequired: 'Введите ID базы знаний',
|
||||
name: 'Название',
|
||||
enterName: 'Введите название',
|
||||
description: 'Описание',
|
||||
chunkSize: 'Размер блока',
|
||||
chunkOverlap: 'Перекрытие блоков',
|
||||
save: 'Сохранить',
|
||||
saving: 'Сохранение...',
|
||||
saveSuccess: 'Сохранено успешно',
|
||||
saveFailed: 'Не удалось сохранить',
|
||||
model: 'Модель',
|
||||
llmModel: 'LLM модель',
|
||||
embeddingModel: 'Модель встраивания',
|
||||
rerankModel: 'Модель ранжирования',
|
||||
vlmModel: 'Мультимодальная модель',
|
||||
modelName: 'Название модели',
|
||||
modelUrl: 'URL модели',
|
||||
apiKey: 'API ключ',
|
||||
cancel: 'Отмена',
|
||||
saveFailedSettings: 'Не удалось сохранить настройки',
|
||||
enterNameRequired: 'Введите название'
|
||||
},
|
||||
initialization: {
|
||||
title: 'Инициализация',
|
||||
welcome: 'Добро пожаловать в WeKnora',
|
||||
description: 'Пожалуйста, настройте систему перед началом работы',
|
||||
step1: 'Шаг 1: Настройка LLM модели',
|
||||
step2: 'Шаг 2: Настройка модели встраивания',
|
||||
step3: 'Шаг 3: Настройка дополнительных моделей',
|
||||
complete: 'Завершить инициализацию',
|
||||
skip: 'Пропустить',
|
||||
next: 'Далее',
|
||||
previous: 'Назад',
|
||||
// Ollama сервис
|
||||
ollamaServiceStatus: 'Статус службы Ollama',
|
||||
refreshStatus: 'Обновить статус',
|
||||
ollamaServiceAddress: 'Адрес службы Ollama',
|
||||
notConfigured: 'Не настроено',
|
||||
notRunning: 'Не запущено',
|
||||
normal: 'Нормально',
|
||||
installedModels: 'Установленные модели',
|
||||
none: 'Временно отсутствует',
|
||||
// База знаний
|
||||
knowledgeBaseInfo: 'Информация о базе знаний',
|
||||
knowledgeBaseName: 'Название базы знаний',
|
||||
knowledgeBaseNamePlaceholder: 'Введите название базы знаний',
|
||||
knowledgeBaseDescription: 'Описание базы знаний',
|
||||
knowledgeBaseDescriptionPlaceholder: 'Введите описание базы знаний',
|
||||
// LLM модель
|
||||
llmModelConfig: 'Конфигурация LLM большой языковой модели',
|
||||
modelSource: 'Источник модели',
|
||||
local: 'Ollama (локальный)',
|
||||
remote: 'Remote API (удаленный)',
|
||||
modelName: 'Название модели',
|
||||
modelNamePlaceholder: 'Например: qwen3:0.6b',
|
||||
baseUrl: 'Base URL',
|
||||
baseUrlPlaceholder: 'Например: https://api.openai.com/v1, удалите часть /chat/completions в конце URL',
|
||||
apiKey: 'API Key (необязательно)',
|
||||
apiKeyPlaceholder: 'Введите API Key (необязательно)',
|
||||
downloadModel: 'Скачать модель',
|
||||
installed: 'Установлено',
|
||||
notInstalled: 'Не установлено',
|
||||
notChecked: 'Не проверено',
|
||||
checkConnection: 'Проверить соединение',
|
||||
connectionNormal: 'Соединение в норме',
|
||||
connectionFailed: 'Ошибка соединения',
|
||||
checkingConnection: 'Проверка соединения',
|
||||
// Embedding модель
|
||||
embeddingModelConfig: 'Конфигурация модели встраивания',
|
||||
embeddingWarning: 'В базе знаний уже есть файлы, невозможно изменить конфигурацию модели встраивания',
|
||||
dimension: 'Размерность',
|
||||
dimensionPlaceholder: 'Введите размерность вектора',
|
||||
detectDimension: 'Определить размерность',
|
||||
// Rerank модель
|
||||
rerankModelConfig: 'Конфигурация модели ранжирования',
|
||||
enableRerank: 'Включить модель ранжирования',
|
||||
// Мультимодальные настройки
|
||||
multimodalConfig: 'Мультимодальная конфигурация',
|
||||
enableMultimodal: 'Включить извлечение информации из изображений',
|
||||
visualLanguageModelConfig: 'Конфигурация визуально-языковой модели',
|
||||
interfaceType: 'Тип интерфейса',
|
||||
openaiCompatible: 'Совместимый с OpenAI интерфейс',
|
||||
// Настройки хранилища
|
||||
storageServiceConfig: 'Конфигурация службы хранения',
|
||||
storageType: 'Тип хранения',
|
||||
bucketName: 'Bucket Name',
|
||||
bucketNamePlaceholder: 'Введите имя Bucket',
|
||||
pathPrefix: 'Path Prefix',
|
||||
pathPrefixPlaceholder: 'Например: images',
|
||||
secretId: 'Secret ID',
|
||||
secretIdPlaceholder: 'Введите COS Secret ID',
|
||||
secretKey: 'Secret Key',
|
||||
secretKeyPlaceholder: 'Введите COS Secret Key',
|
||||
region: 'Region',
|
||||
regionPlaceholder: 'Например: ap-beijing',
|
||||
appId: 'App ID',
|
||||
appIdPlaceholder: 'Введите App ID',
|
||||
// Тестирование мультимодальных функций
|
||||
functionTest: 'Тест функции',
|
||||
testDescription: 'Загрузите изображение для тестирования функций описания изображений и распознавания текста модели VLM',
|
||||
selectImage: 'Выбрать изображение',
|
||||
startTest: 'Начать тест',
|
||||
testResult: 'Результат теста',
|
||||
imageDescription: 'Описание изображения:',
|
||||
textRecognition: 'Распознавание текста:',
|
||||
processingTime: 'Время обработки:',
|
||||
testFailed: 'Тест не удался',
|
||||
multimodalProcessingFailed: 'Ошибка мультимодальной обработки',
|
||||
// Разделение документов
|
||||
documentSplittingConfig: 'Конфигурация разделения документов',
|
||||
splittingStrategy: 'Стратегия разделения',
|
||||
balancedMode: 'Сбалансированный режим',
|
||||
balancedModeDesc: 'Размер блока: 1000 / Перекрытие: 200',
|
||||
precisionMode: 'Точный режим',
|
||||
precisionModeDesc: 'Размер блока: 512 / Перекрытие: 100',
|
||||
contextMode: 'Контекстный режим',
|
||||
contextModeDesc: 'Размер блока: 2048 / Перекрытие: 400',
|
||||
custom: 'Пользовательский',
|
||||
customDesc: 'Настроить параметры вручную',
|
||||
chunkSize: 'Размер блока',
|
||||
chunkOverlap: 'Перекрытие блоков',
|
||||
separatorSettings: 'Настройки разделителей',
|
||||
selectOrCustomSeparators: 'Выберите или настройте разделители',
|
||||
characters: 'символов',
|
||||
separatorParagraph: 'Разделитель абзацев (\\n\\n)',
|
||||
separatorNewline: 'Перевод строки (\\n)',
|
||||
separatorPeriod: 'Точка (。)',
|
||||
separatorExclamation: 'Восклицательный знак (!)',
|
||||
separatorQuestion: 'Вопросительный знак (?)',
|
||||
separatorSemicolon: 'Точка с запятой (;)',
|
||||
separatorChineseSemicolon: 'Китайская точка с запятой (;)',
|
||||
separatorComma: 'Запятая (,)',
|
||||
separatorChineseComma: 'Китайская запятая (,)',
|
||||
// Извлечение сущностей и отношений
|
||||
entityRelationExtraction: 'Извлечение сущностей и отношений',
|
||||
enableEntityRelationExtraction: 'Включить извлечение сущностей и отношений',
|
||||
relationTypeConfig: 'Конфигурация типов отношений',
|
||||
relationType: 'Тип отношения',
|
||||
generateRandomTags: 'Сгенерировать случайные теги',
|
||||
completeModelConfig: 'Пожалуйста, завершите конфигурацию модели',
|
||||
systemWillExtract: 'Система будет извлекать соответствующие сущности и отношения из текста в соответствии с выбранными типами отношений',
|
||||
extractionExample: 'Пример извлечения',
|
||||
sampleText: 'Пример текста',
|
||||
sampleTextPlaceholder: 'Введите текст для анализа, например: "Красный особняк", также известный как "Сон в красном тереме", является одним из четырех великих классических произведений китайской литературы, написанным Цинь Сюэцином в династии Цин...',
|
||||
generateRandomText: 'Сгенерировать случайный текст',
|
||||
entityList: 'Список сущностей',
|
||||
nodeName: 'Имя узла',
|
||||
nodeNamePlaceholder: 'Имя узла',
|
||||
addAttribute: 'Добавить атрибут',
|
||||
attributeValue: 'Значение атрибута',
|
||||
attributeValuePlaceholder: 'Значение атрибута',
|
||||
addEntity: 'Добавить сущность',
|
||||
completeEntityInfo: 'Пожалуйста, завершите информацию о сущности',
|
||||
relationConnection: 'Соединение отношений',
|
||||
selectEntity: 'Выберите сущность',
|
||||
addRelation: 'Добавить отношение',
|
||||
completeRelationInfo: 'Пожалуйста, завершите информацию об отношении',
|
||||
startExtraction: 'Начать извлечение',
|
||||
extracting: 'Извлечение...',
|
||||
defaultExample: 'Пример по умолчанию',
|
||||
clearExample: 'Очистить пример',
|
||||
// Кнопки и сообщения
|
||||
updateKnowledgeBaseSettings: 'Обновить настройки базы знаний',
|
||||
updateConfigInfo: 'Обновить информацию о конфигурации',
|
||||
completeConfig: 'Завершить конфигурацию',
|
||||
waitForDownloads: 'Пожалуйста, дождитесь завершения загрузки всех моделей Ollama перед обновлением конфигурации',
|
||||
completeModelConfigInfo: 'Пожалуйста, завершите информацию о конфигурации модели',
|
||||
knowledgeBaseIdMissing: 'Отсутствует ID базы знаний',
|
||||
knowledgeBaseSettingsUpdateSuccess: 'Настройки базы знаний успешно обновлены',
|
||||
configUpdateSuccess: 'Конфигурация успешно обновлена',
|
||||
systemInitComplete: 'Инициализация системы завершена',
|
||||
operationFailed: 'Операция не удалась',
|
||||
updateKnowledgeBaseInfoFailed: 'Не удалось обновить базовую информацию о базе знаний',
|
||||
knowledgeBaseIdMissingCannotSave: 'Отсутствует ID базы знаний, невозможно сохранить конфигурацию',
|
||||
operationFailedCheckNetwork: 'Операция не удалась, проверьте сетевое соединение',
|
||||
imageUploadSuccess: 'Изображение успешно загружено, можно начать тестирование',
|
||||
multimodalConfigIncomplete: 'Мультимодальная конфигурация неполная, пожалуйста, завершите мультимодальную конфигурацию перед загрузкой изображения',
|
||||
pleaseSelectImage: 'Пожалуйста, выберите изображение',
|
||||
multimodalTestSuccess: 'Мультимодальный тест успешен',
|
||||
multimodalTestFailed: 'Мультимодальный тест не удался',
|
||||
pleaseEnterSampleText: 'Пожалуйста, введите текст примера',
|
||||
pleaseEnterRelationType: 'Пожалуйста, введите тип отношения',
|
||||
pleaseEnterLLMModelConfig: 'Пожалуйста, введите конфигурацию LLM большой языковой модели',
|
||||
noValidNodesExtracted: 'Не извлечено допустимых узлов',
|
||||
noValidRelationsExtracted: 'Не извлечено допустимых отношений',
|
||||
extractionFailedCheckNetwork: 'Извлечение не удалось, проверьте сетевое соединение или формат текста',
|
||||
generateFailedRetry: 'Генерация не удалась, попробуйте еще раз',
|
||||
pleaseCheckForm: 'Пожалуйста, проверьте правильность заполнения формы',
|
||||
detectionSuccessful: 'Обнаружение успешно, размерность автоматически заполнена как',
|
||||
detectionFailed: 'Обнаружение не удалось',
|
||||
detectionFailedCheckConfig: 'Обнаружение не удалось, проверьте конфигурацию',
|
||||
modelDownloadSuccess: 'Модель успешно загружена',
|
||||
modelDownloadFailed: 'Не удалось загрузить модель',
|
||||
downloadStartFailed: 'Не удалось начать загрузку',
|
||||
queryProgressFailed: 'Не удалось запросить прогресс',
|
||||
checkOllamaStatusFailed: 'Не удалось проверить статус Ollama',
|
||||
getKnowledgeBaseInfoFailed: 'Не удалось получить информацию о базе знаний',
|
||||
textRelationExtractionFailed: 'Не удалось извлечь текстовые отношения',
|
||||
// Валидация
|
||||
pleaseEnterKnowledgeBaseName: 'Пожалуйста, введите название базы знаний',
|
||||
knowledgeBaseNameLength: 'Длина названия базы знаний должна быть от 1 до 50 символов',
|
||||
knowledgeBaseDescriptionLength: 'Длина описания базы знаний не может превышать 200 символов',
|
||||
pleaseEnterLLMModelName: 'Пожалуйста, введите название LLM модели',
|
||||
pleaseEnterBaseURL: 'Пожалуйста, введите BaseURL',
|
||||
pleaseEnterEmbeddingModelName: 'Пожалуйста, введите название модели встраивания',
|
||||
pleaseEnterEmbeddingDimension: 'Пожалуйста, введите размерность встраивания',
|
||||
dimensionMustBeInteger: 'Размерность должна быть допустимым целым числом, обычно 768, 1024, 1536, 3584 и т.д.',
|
||||
pleaseEnterTextContent: 'Пожалуйста, введите текстовое содержание',
|
||||
textContentMinLength: 'Текстовое содержание должно содержать не менее 10 символов',
|
||||
pleaseEnterValidTag: 'Пожалуйста, введите действительный тег',
|
||||
tagAlreadyExists: 'Этот тег уже существует',
|
||||
// Дополнительные переводы для InitializationContent.vue
|
||||
checkFailed: 'Проверка не удалась',
|
||||
startingDownload: 'Запуск загрузки...',
|
||||
downloadStarted: 'Загрузка началась',
|
||||
model: 'Модель',
|
||||
startModelDownloadFailed: 'Не удалось запустить загрузку модели',
|
||||
downloadCompleted: 'Загрузка завершена',
|
||||
downloadFailed: 'Загрузка не удалась',
|
||||
knowledgeBaseSettingsModeMissingId: 'В режиме настроек базы знаний отсутствует ID базы знаний',
|
||||
completeEmbeddingConfig: 'Пожалуйста, сначала полностью заполните конфигурацию встраивания',
|
||||
detectionSuccess: 'Обнаружение успешно,',
|
||||
dimensionAutoFilled: 'размерность автоматически заполнена:',
|
||||
checkFormCorrectness: 'Пожалуйста, проверьте правильность заполнения формы',
|
||||
systemInitializationCompleted: 'Инициализация системы завершена',
|
||||
generationFailedRetry: 'Генерация не удалась, пожалуйста, попробуйте еще раз',
|
||||
chunkSizeDesc: 'Размер каждого текстового блока. Большие блоки сохраняют больше контекста, но могут снизить точность поиска.',
|
||||
chunkOverlapDesc: 'Количество символов, перекрывающихся между соседними блоками. Помогает сохранить контекст на границах блоков.',
|
||||
selectRelationType: 'Выберите тип отношения'
|
||||
},
|
||||
auth: {
|
||||
login: 'Вход',
|
||||
logout: 'Выход',
|
||||
username: 'Имя пользователя',
|
||||
email: 'Почта Email',
|
||||
password: 'Пароль',
|
||||
confirmPassword: 'Подтвердите пароль',
|
||||
rememberMe: 'Запомнить меня',
|
||||
forgotPassword: 'Забыли пароль?',
|
||||
loginSuccess: 'Вход выполнен успешно!',
|
||||
loginFailed: 'Ошибка входа',
|
||||
loggingIn: 'Вход...',
|
||||
register: 'Регистрация',
|
||||
registering: 'Регистрация...',
|
||||
createAccount: 'Создать аккаунт',
|
||||
haveAccount: 'Уже есть аккаунт?',
|
||||
noAccount: 'Ещё нет аккаунта?',
|
||||
backToLogin: 'Вернуться ко входу',
|
||||
registerNow: 'Зарегистрироваться',
|
||||
registerSuccess: 'Регистрация успешна! Система создала для вас эксклюзивного арендатора, пожалуйста, войдите',
|
||||
registerFailed: 'Ошибка регистрации',
|
||||
subtitle: 'Фреймворк понимания документов и семантического поиска на основе больших моделей',
|
||||
registerSubtitle: 'После регистрации система создаст для вас эксклюзивного арендатора',
|
||||
emailPlaceholder: 'Введите адрес электронной почты',
|
||||
passwordPlaceholder: 'Введите пароль (8-32 символа, включая буквы и цифры)',
|
||||
confirmPasswordPlaceholder: 'Введите пароль ещё раз',
|
||||
usernamePlaceholder: 'Введите имя пользователя',
|
||||
emailRequired: 'Введите адрес электронной почты',
|
||||
emailInvalid: 'Введите правильный формат электронной почты',
|
||||
passwordRequired: 'Введите пароль',
|
||||
passwordMinLength: 'Пароль должен быть не менее 8 символов',
|
||||
passwordMaxLength: 'Пароль не может превышать 32 символа',
|
||||
passwordMustContainLetter: 'Пароль должен содержать буквы',
|
||||
passwordMustContainNumber: 'Пароль должен содержать цифры',
|
||||
usernameRequired: 'Введите имя пользователя',
|
||||
usernameMinLength: 'Имя пользователя должно быть не менее 2 символов',
|
||||
usernameMaxLength: 'Имя пользователя не может превышать 20 символов',
|
||||
usernameInvalid: 'Имя пользователя может содержать только буквы, цифры, подчёркивания и китайские иероглифы',
|
||||
confirmPasswordRequired: 'Подтвердите пароль',
|
||||
passwordMismatch: 'Введённые пароли не совпадают',
|
||||
loginError: 'Ошибка входа, пожалуйста, проверьте электронную почту или пароль',
|
||||
loginErrorRetry: 'Ошибка входа, пожалуйста, повторите попытку позже',
|
||||
registerError: 'Ошибка регистрации, пожалуйста, повторите попытку позже',
|
||||
forgotPasswordNotAvailable: 'Функция восстановления пароля временно недоступна, пожалуйста, свяжитесь с администратором'
|
||||
},
|
||||
common: {
|
||||
confirm: 'Подтвердить',
|
||||
cancel: 'Отмена',
|
||||
save: 'Сохранить',
|
||||
delete: 'Удалить',
|
||||
edit: 'Редактировать',
|
||||
create: 'Создать',
|
||||
search: 'Поиск',
|
||||
filter: 'Фильтр',
|
||||
export: 'Экспорт',
|
||||
import: 'Импорт',
|
||||
upload: 'Загрузить',
|
||||
download: 'Скачать',
|
||||
refresh: 'Обновить',
|
||||
loading: 'Загрузка...',
|
||||
noData: 'Нет данных',
|
||||
error: 'Ошибка',
|
||||
success: 'Успешно',
|
||||
warning: 'Предупреждение',
|
||||
info: 'Информация',
|
||||
yes: 'Да',
|
||||
no: 'Нет',
|
||||
ok: 'OK',
|
||||
close: 'Закрыть',
|
||||
back: 'Назад',
|
||||
next: 'Далее',
|
||||
finish: 'Завершить',
|
||||
all: 'Все',
|
||||
reset: 'Сбросить',
|
||||
clear: 'Очистить'
|
||||
},
|
||||
file: {
|
||||
upload: 'Загрузить файл',
|
||||
uploadSuccess: 'Файл успешно загружен',
|
||||
uploadFailed: 'Ошибка загрузки файла',
|
||||
delete: 'Удалить файл',
|
||||
deleteSuccess: 'Файл успешно удален',
|
||||
deleteFailed: 'Ошибка удаления файла',
|
||||
download: 'Скачать файл',
|
||||
preview: 'Предпросмотр',
|
||||
unsupportedFormat: 'Неподдерживаемый формат файла',
|
||||
maxSizeExceeded: 'Превышен максимальный размер файла',
|
||||
selectFile: 'Выберите файл'
|
||||
},
|
||||
tenant: {
|
||||
title: 'Информация об арендаторе',
|
||||
name: 'Имя арендатора',
|
||||
id: 'ID арендатора',
|
||||
createdAt: 'Дата создания',
|
||||
updatedAt: 'Дата обновления',
|
||||
status: 'Статус',
|
||||
active: 'Активен',
|
||||
inactive: 'Неактивен',
|
||||
// Дополнительные переводы для TenantInfo.vue
|
||||
systemInfo: 'Системная информация',
|
||||
viewSystemInfo: 'Просмотр информации о версии системы и конфигурации учётной записи пользователя',
|
||||
version: 'Версия',
|
||||
buildTime: 'Время сборки',
|
||||
goVersion: 'Версия Go',
|
||||
userInfo: 'Информация о пользователе',
|
||||
userId: 'ID пользователя',
|
||||
username: 'Имя пользователя',
|
||||
email: 'Электронная почта',
|
||||
tenantInfo: 'Информация об арендаторе',
|
||||
tenantId: 'ID арендатора',
|
||||
tenantName: 'Название арендатора',
|
||||
description: 'Описание',
|
||||
business: 'Бизнес',
|
||||
noDescription: 'Нет описания',
|
||||
noBusiness: 'Нет',
|
||||
statusActive: 'Активен',
|
||||
statusInactive: 'Не активирован',
|
||||
statusSuspended: 'Приостановлен',
|
||||
statusUnknown: 'Неизвестен',
|
||||
apiKey: 'API Key',
|
||||
keepApiKeySafe: 'Пожалуйста, храните ваш API Key в безопасности, не раскрывайте его в общественных местах или репозиториях кода',
|
||||
storageInfo: 'Информация о хранилище',
|
||||
storageQuota: 'Квота хранилища',
|
||||
used: 'Использовано',
|
||||
usage: 'Использование',
|
||||
apiDevDocs: 'Документация для разработчиков API',
|
||||
useApiKey: 'Используйте ваш API Key для начала разработки, просмотрите полную документацию API и примеры кода.',
|
||||
viewApiDoc: 'Просмотреть документацию API',
|
||||
loadingAccountInfo: 'Загрузка информации об учётной записи...',
|
||||
loadFailed: 'Загрузка не удалась',
|
||||
retry: 'Повторить',
|
||||
apiKeyCopied: 'API Key скопирован в буфер обмена',
|
||||
unknown: 'Неизвестно',
|
||||
formatError: 'Ошибка формата'
|
||||
},
|
||||
error: {
|
||||
network: 'Ошибка сети',
|
||||
server: 'Ошибка сервера',
|
||||
notFound: 'Не найдено',
|
||||
unauthorized: 'Не авторизован',
|
||||
forbidden: 'Доступ запрещен',
|
||||
unknown: 'Неизвестная ошибка',
|
||||
tryAgain: 'Пожалуйста, попробуйте еще раз'
|
||||
},
|
||||
model: {
|
||||
llmModel: 'LLM модель',
|
||||
embeddingModel: 'Модель встраивания',
|
||||
rerankModel: 'Модель ранжирования',
|
||||
vlmModel: 'Мультимодальная модель',
|
||||
modelName: 'Название модели',
|
||||
modelProvider: 'Поставщик модели',
|
||||
modelUrl: 'URL модели',
|
||||
apiKey: 'API ключ',
|
||||
testConnection: 'Проверить соединение',
|
||||
connectionSuccess: 'Соединение успешно',
|
||||
connectionFailed: 'Ошибка соединения',
|
||||
dimension: 'Размерность',
|
||||
maxTokens: 'Макс. токенов',
|
||||
temperature: 'Температура',
|
||||
topP: 'Top P',
|
||||
selectModel: 'Выберите модель',
|
||||
customModel: 'Пользовательская модель',
|
||||
builtinModel: 'Встроенная модель'
|
||||
}
|
||||
}
|
||||
536
frontend/src/i18n/locales/zh-CN.ts
Normal file
536
frontend/src/i18n/locales/zh-CN.ts
Normal file
@@ -0,0 +1,536 @@
|
||||
export default {
|
||||
menu: {
|
||||
knowledgeBase: '知识库',
|
||||
chat: '对话',
|
||||
createChat: '创建对话',
|
||||
tenant: '账户信息',
|
||||
settings: '系统设置',
|
||||
logout: '退出登录',
|
||||
uploadKnowledge: '上传知识',
|
||||
deleteRecord: '删除记录',
|
||||
newSession: '新会话',
|
||||
confirmLogout: '确定要退出登录吗?',
|
||||
systemInfo: '系统信息'
|
||||
},
|
||||
knowledgeBase: {
|
||||
title: '知识库',
|
||||
list: '知识库列表',
|
||||
detail: '知识库详情',
|
||||
create: '创建知识库',
|
||||
edit: '编辑知识库',
|
||||
delete: '删除知识库',
|
||||
name: '名称',
|
||||
description: '描述',
|
||||
files: '文件',
|
||||
settings: '设置',
|
||||
upload: '上传文件',
|
||||
uploadSuccess: '文件上传成功!',
|
||||
uploadFailed: '文件上传失败!',
|
||||
fileExists: '文件已存在',
|
||||
notInitialized: '该知识库尚未完成初始化配置,请先前往设置页面配置模型信息后再上传文件',
|
||||
getInfoFailed: '获取知识库信息失败,无法上传文件',
|
||||
missingId: '缺少知识库ID',
|
||||
deleteFailed: '删除失败,请稍后再试!',
|
||||
createKnowledgeBase: '创建知识库',
|
||||
knowledgeBaseName: '知识库名称',
|
||||
enterName: '输入知识库名称',
|
||||
embeddingModel: '嵌入模型',
|
||||
selectEmbeddingModel: '选择嵌入模型',
|
||||
summaryModel: '摘要模型',
|
||||
selectSummaryModel: '选择摘要模型',
|
||||
rerankModel: '重排序模型',
|
||||
selectRerankModel: '选择重排序模型(可选)',
|
||||
createSuccess: '知识库创建成功',
|
||||
createFailed: '知识库创建失败',
|
||||
updateSuccess: '知识库更新成功',
|
||||
updateFailed: '知识库更新失败',
|
||||
deleteSuccess: '知识库删除成功',
|
||||
deleteConfirm: '确定要删除此知识库吗?',
|
||||
fileName: '文件名',
|
||||
fileSize: '文件大小',
|
||||
uploadTime: '上传时间',
|
||||
status: '状态',
|
||||
actions: '操作',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
failed: '失败',
|
||||
noFiles: '暂无文件',
|
||||
dragFilesHere: '拖拽文件至此或',
|
||||
clickToUpload: '点击上传',
|
||||
supportedFormats: '支持格式',
|
||||
maxFileSize: '最大文件大小',
|
||||
viewDetails: '查看详情',
|
||||
downloadFile: '下载文件',
|
||||
deleteFile: '删除文件',
|
||||
confirmDeleteFile: '确定要删除此文件吗?',
|
||||
totalFiles: '文件总数',
|
||||
totalSize: '总大小',
|
||||
newSession: '新会话',
|
||||
deleteDocument: '删除文档',
|
||||
parsingFailed: '解析失败',
|
||||
parsingInProgress: '解析中...',
|
||||
deleteConfirmation: '删除确认',
|
||||
confirmDeleteDocument: '确认删除文档"{fileName}",删除后将无法恢复',
|
||||
cancel: '取消',
|
||||
confirmDelete: '确认删除',
|
||||
selectKnowledgeBaseFirst: '请先选择知识库',
|
||||
sessionCreationFailed: '创建会话失败',
|
||||
sessionCreationError: '会话创建错误',
|
||||
settingsParsingFailed: '设置解析失败',
|
||||
fileUploadEventReceived: '收到文件上传事件,上传的知识库ID:{uploadedKbId},当前知识库ID:{currentKbId}',
|
||||
matchingKnowledgeBase: '知识库匹配,开始更新文件列表',
|
||||
routeParamChange: '路由参数变化,重新获取知识库内容',
|
||||
fileUploadEventListening: '监听文件上传事件',
|
||||
apiCallKnowledgeFiles: '直接调用API获取知识库文件列表',
|
||||
responseInterceptorData: '由于响应拦截器已返回data,result是响应数据的一部分',
|
||||
hookProcessing: '按照useKnowledgeBase hook方法处理',
|
||||
errorHandling: '错误处理',
|
||||
priorityCurrentPageKbId: '优先使用当前页面的知识库ID',
|
||||
fallbackLocalStorageKbId: '如果当前页面没有知识库ID,尝试从localStorage的设置中获取知识库ID',
|
||||
createNewKnowledgeBase: '创建知识库',
|
||||
uninitializedWarning: '部分知识库未初始化,需要先在设置中配置模型信息才能添加知识文档',
|
||||
initializedStatus: '已初始化',
|
||||
notInitializedStatus: '未初始化',
|
||||
needSettingsFirst: '需要先在设置中配置模型信息才能添加知识',
|
||||
documents: '文档',
|
||||
configureModelsFirst: '请先在设置中配置模型信息',
|
||||
confirmDeleteKnowledgeBase: '确认删除此知识库?',
|
||||
createKnowledgeBaseDialog: '创建知识库',
|
||||
enterNameKb: '输入名称',
|
||||
enterDescriptionKb: '输入描述',
|
||||
createKb: '创建',
|
||||
deleted: '已删除',
|
||||
deleteFailedKb: '删除失败',
|
||||
noDescription: '无描述',
|
||||
emptyKnowledgeDragDrop: '知识为空,拖放上传',
|
||||
pdfDocFormat: 'pdf、doc 格式文件,不超过10M',
|
||||
textMarkdownFormat: 'text、markdown格式文件,不超过200K',
|
||||
dragFileNotText: '请拖拽文件而不是文本或链接'
|
||||
},
|
||||
chat: {
|
||||
title: '对话',
|
||||
newChat: '新对话',
|
||||
inputPlaceholder: '请输入您的消息...',
|
||||
send: '发送',
|
||||
thinking: '思考中...',
|
||||
regenerate: '重新生成',
|
||||
copy: '复制',
|
||||
delete: '删除',
|
||||
reference: '引用',
|
||||
noMessages: '暂无消息',
|
||||
waitingForAnswer: '等待回答...',
|
||||
cannotAnswer: '抱歉,我无法回答这个问题。',
|
||||
summarizingAnswer: '总结答案中...',
|
||||
loading: '加载中...',
|
||||
enterDescription: '输入描述',
|
||||
referencedContent: '引用了 {count} 个相关资料',
|
||||
deepThinking: '深度思考完成',
|
||||
knowledgeBaseQandA: '知识库问答',
|
||||
askKnowledgeBase: '向知识库提问',
|
||||
sourcesCount: '{count} 个来源',
|
||||
pleaseEnterContent: '请输入内容!',
|
||||
pleaseUploadKnowledgeBase: '请先上传知识库!',
|
||||
replyingPleaseWait: '正在回复,请稍后再试!',
|
||||
createSessionFailed: '创建会话失败',
|
||||
createSessionError: '创建会话出错',
|
||||
unableToGetKnowledgeBaseId: '无法获取知识库ID'
|
||||
},
|
||||
settings: {
|
||||
title: '设置',
|
||||
system: '系统设置',
|
||||
systemConfig: '系统配置',
|
||||
knowledgeBaseSettings: '知识库设置',
|
||||
configureKbModels: '为此知识库配置模型和文档分割参数',
|
||||
manageSystemModels: '管理和更新系统模型及服务配置',
|
||||
basicInfo: '基本信息',
|
||||
documentSplitting: '文档分割',
|
||||
apiEndpoint: 'API端点',
|
||||
enterApiEndpoint: '输入API端点,例如:http://localhost',
|
||||
enterApiKey: '输入API密钥',
|
||||
enterKnowledgeBaseId: '输入知识库ID',
|
||||
saveConfig: '保存配置',
|
||||
reset: '重置',
|
||||
configSaved: '配置保存成功',
|
||||
enterApiEndpointRequired: '请输入API端点',
|
||||
enterApiKeyRequired: '请输入API密钥',
|
||||
enterKnowledgeBaseIdRequired: '请输入知识库ID',
|
||||
name: '名称',
|
||||
enterName: '输入名称',
|
||||
description: '描述',
|
||||
chunkSize: '分块大小',
|
||||
chunkOverlap: '分块重叠',
|
||||
save: '保存',
|
||||
saving: '保存中...',
|
||||
saveSuccess: '保存成功',
|
||||
saveFailed: '保存失败',
|
||||
model: '模型',
|
||||
llmModel: 'LLM模型',
|
||||
embeddingModel: '嵌入模型',
|
||||
rerankModel: '重排序模型',
|
||||
vlmModel: '多模态模型',
|
||||
modelName: '模型名称',
|
||||
modelUrl: '模型地址',
|
||||
apiKey: 'API密钥',
|
||||
cancel: '取消',
|
||||
saveFailedSettings: '设置保存失败',
|
||||
enterNameRequired: '请输入名称'
|
||||
},
|
||||
initialization: {
|
||||
title: '初始化',
|
||||
welcome: '欢迎使用WeKnora',
|
||||
description: '请先配置系统以开始使用',
|
||||
step1: '步骤1:配置LLM模型',
|
||||
step2: '步骤2:配置嵌入模型',
|
||||
step3: '步骤3:配置其他模型',
|
||||
complete: '完成初始化',
|
||||
skip: '跳过',
|
||||
next: '下一步',
|
||||
previous: '上一步',
|
||||
ollamaServiceStatus: 'Ollama服务状态',
|
||||
refreshStatus: '刷新状态',
|
||||
ollamaServiceAddress: 'Ollama服务地址',
|
||||
notConfigured: '未配置',
|
||||
notRunning: '未运行',
|
||||
normal: '正常',
|
||||
installedModels: '已安装模型',
|
||||
none: '暂无',
|
||||
knowledgeBaseInfo: '知识库信息',
|
||||
knowledgeBaseName: '知识库名称',
|
||||
knowledgeBaseNamePlaceholder: '输入知识库名称',
|
||||
knowledgeBaseDescription: '知识库描述',
|
||||
knowledgeBaseDescriptionPlaceholder: '输入知识库描述',
|
||||
llmModelConfig: 'LLM大语言模型配置',
|
||||
modelSource: '模型来源',
|
||||
local: 'Ollama(本地)',
|
||||
remote: 'Remote API(远程)',
|
||||
modelName: '模型名称',
|
||||
modelNamePlaceholder: '例如:qwen3:0.6b',
|
||||
baseUrl: 'Base URL',
|
||||
baseUrlPlaceholder: '例如:https://api.openai.com/v1,去掉URL末尾的/chat/completions部分',
|
||||
apiKey: 'API Key(可选)',
|
||||
apiKeyPlaceholder: '输入API Key(可选)',
|
||||
downloadModel: '下载模型',
|
||||
installed: '已安装',
|
||||
notInstalled: '未安装',
|
||||
notChecked: '未检查',
|
||||
checkConnection: '检查连接',
|
||||
connectionNormal: '连接正常',
|
||||
connectionFailed: '连接失败',
|
||||
checkingConnection: '正在检查连接',
|
||||
embeddingModelConfig: '嵌入模型配置',
|
||||
embeddingWarning: '知识库已有文件,无法更改嵌入模型配置',
|
||||
dimension: '维度',
|
||||
dimensionPlaceholder: '输入向量维度',
|
||||
detectDimension: '检测维度',
|
||||
rerankModelConfig: '重排序模型配置',
|
||||
enableRerank: '启用重排序模型',
|
||||
multimodalConfig: '多模态配置',
|
||||
enableMultimodal: '启用图像信息提取',
|
||||
visualLanguageModelConfig: '视觉语言模型配置',
|
||||
interfaceType: '接口类型',
|
||||
openaiCompatible: 'OpenAI兼容接口',
|
||||
storageServiceConfig: '存储服务配置',
|
||||
storageType: '存储类型',
|
||||
bucketName: 'Bucket名称',
|
||||
bucketNamePlaceholder: '输入Bucket名称',
|
||||
pathPrefix: '路径前缀',
|
||||
pathPrefixPlaceholder: '例如:images',
|
||||
secretId: 'Secret ID',
|
||||
secretIdPlaceholder: '输入COS Secret ID',
|
||||
secretKey: 'Secret Key',
|
||||
secretKeyPlaceholder: '输入COS Secret Key',
|
||||
region: 'Region',
|
||||
regionPlaceholder: '例如:ap-beijing',
|
||||
appId: 'App ID',
|
||||
appIdPlaceholder: '输入App ID',
|
||||
functionTest: '功能测试',
|
||||
testDescription: '上传图片测试VLM模型的图像描述和文字识别功能',
|
||||
selectImage: '选择图片',
|
||||
startTest: '开始测试',
|
||||
testResult: '测试结果',
|
||||
imageDescription: '图像描述:',
|
||||
textRecognition: '文字识别:',
|
||||
processingTime: '处理时间:',
|
||||
testFailed: '测试失败',
|
||||
multimodalProcessingFailed: '多模态处理失败',
|
||||
documentSplittingConfig: '文档分割配置',
|
||||
splittingStrategy: '分割策略',
|
||||
balancedMode: '平衡模式',
|
||||
balancedModeDesc: '分块大小:1000 / 重叠:200',
|
||||
precisionMode: '精确模式',
|
||||
precisionModeDesc: '分块大小:512 / 重叠:100',
|
||||
contextMode: '上下文模式',
|
||||
contextModeDesc: '分块大小:2048 / 重叠:400',
|
||||
custom: '自定义',
|
||||
customDesc: '手动配置参数',
|
||||
chunkSize: '分块大小',
|
||||
chunkOverlap: '分块重叠',
|
||||
separatorSettings: '分隔符设置',
|
||||
selectOrCustomSeparators: '选择或自定义分隔符',
|
||||
characters: '个字符',
|
||||
separatorParagraph: '段落分隔符 (\\n\\n)',
|
||||
separatorNewline: '换行符 (\\n)',
|
||||
separatorPeriod: '句号 (。)',
|
||||
separatorExclamation: '感叹号 (!)',
|
||||
separatorQuestion: '问号 (?)',
|
||||
separatorSemicolon: '分号 (;)',
|
||||
separatorChineseSemicolon: '中文分号 (;)',
|
||||
separatorComma: '逗号 (,)',
|
||||
separatorChineseComma: '中文逗号 (,)',
|
||||
entityRelationExtraction: '实体和关系提取',
|
||||
enableEntityRelationExtraction: '启用实体和关系提取',
|
||||
relationTypeConfig: '关系类型配置',
|
||||
relationType: '关系类型',
|
||||
generateRandomTags: '生成随机标签',
|
||||
completeModelConfig: '请完成模型配置',
|
||||
systemWillExtract: '系统将根据所选关系类型从文本中提取相应的实体和关系',
|
||||
extractionExample: '提取示例',
|
||||
sampleText: '示例文本',
|
||||
sampleTextPlaceholder: '输入用于分析的文本,例如:"红楼梦",又名"石头记",是中国四大名著之一,清代曹雪芹所著...',
|
||||
generateRandomText: '生成随机文本',
|
||||
entityList: '实体列表',
|
||||
nodeName: '节点名称',
|
||||
nodeNamePlaceholder: '节点名称',
|
||||
addAttribute: '添加属性',
|
||||
attributeValue: '属性值',
|
||||
attributeValuePlaceholder: '属性值',
|
||||
addEntity: '添加实体',
|
||||
completeEntityInfo: '请完成实体信息',
|
||||
relationConnection: '关系连接',
|
||||
selectEntity: '选择实体',
|
||||
addRelation: '添加关系',
|
||||
completeRelationInfo: '请完成关系信息',
|
||||
startExtraction: '开始提取',
|
||||
extracting: '提取中...',
|
||||
defaultExample: '默认示例',
|
||||
clearExample: '清除示例',
|
||||
updateKnowledgeBaseSettings: '更新知识库设置',
|
||||
updateConfigInfo: '更新配置信息',
|
||||
completeConfig: '完成配置',
|
||||
waitForDownloads: '请等待所有Ollama模型下载完成后再更新配置',
|
||||
completeModelConfigInfo: '请完成模型配置信息',
|
||||
knowledgeBaseIdMissing: '知识库ID缺失',
|
||||
knowledgeBaseSettingsUpdateSuccess: '知识库设置更新成功',
|
||||
configUpdateSuccess: '配置更新成功',
|
||||
systemInitComplete: '系统初始化完成',
|
||||
operationFailed: '操作失败',
|
||||
updateKnowledgeBaseInfoFailed: '更新知识库基本信息失败',
|
||||
knowledgeBaseIdMissingCannotSave: '知识库ID缺失,无法保存配置',
|
||||
operationFailedCheckNetwork: '操作失败,请检查网络连接',
|
||||
imageUploadSuccess: '图片上传成功,可以开始测试',
|
||||
multimodalConfigIncomplete: '多模态配置不完整,请先完成多模态配置后再上传图片',
|
||||
pleaseSelectImage: '请选择图片',
|
||||
multimodalTestSuccess: '多模态测试成功',
|
||||
multimodalTestFailed: '多模态测试失败',
|
||||
pleaseEnterSampleText: '请输入示例文本',
|
||||
pleaseEnterRelationType: '请输入关系类型',
|
||||
pleaseEnterLLMModelConfig: '请输入LLM大语言模型配置',
|
||||
noValidNodesExtracted: '未提取到有效节点',
|
||||
noValidRelationsExtracted: '未提取到有效关系',
|
||||
extractionFailedCheckNetwork: '提取失败,请检查网络或文本格式',
|
||||
generateFailedRetry: '生成失败,请重试',
|
||||
pleaseCheckForm: '请检查表单填写是否正确',
|
||||
detectionSuccessful: '检测成功,维度自动填充为',
|
||||
detectionFailed: '检测失败',
|
||||
detectionFailedCheckConfig: '检测失败,请检查配置',
|
||||
modelDownloadSuccess: '模型下载成功',
|
||||
modelDownloadFailed: '模型下载失败',
|
||||
downloadStartFailed: '下载启动失败',
|
||||
queryProgressFailed: '进度查询失败',
|
||||
checkOllamaStatusFailed: 'Ollama状态检查失败',
|
||||
getKnowledgeBaseInfoFailed: '获取知识库信息失败',
|
||||
textRelationExtractionFailed: '文本关系提取失败',
|
||||
pleaseEnterKnowledgeBaseName: '请输入知识库名称',
|
||||
knowledgeBaseNameLength: '知识库名称长度必须为1-50个字符',
|
||||
knowledgeBaseDescriptionLength: '知识库描述不能超过200个字符',
|
||||
pleaseEnterLLMModelName: '请输入LLM模型名称',
|
||||
pleaseEnterBaseURL: '请输入BaseURL',
|
||||
pleaseEnterEmbeddingModelName: '请输入嵌入模型名称',
|
||||
pleaseEnterEmbeddingDimension: '请输入嵌入维度',
|
||||
dimensionMustBeInteger: '维度必须是有效整数,通常为768、1024、1536、3584等',
|
||||
pleaseEnterTextContent: '请输入文本内容',
|
||||
textContentMinLength: '文本内容必须包含至少10个字符',
|
||||
pleaseEnterValidTag: '请输入有效标签',
|
||||
tagAlreadyExists: '此标签已存在',
|
||||
checkFailed: '检查失败',
|
||||
startingDownload: '正在启动下载...',
|
||||
downloadStarted: '下载已开始',
|
||||
model: '模型',
|
||||
startModelDownloadFailed: '启动模型下载失败',
|
||||
downloadCompleted: '下载完成',
|
||||
downloadFailed: '下载失败',
|
||||
knowledgeBaseSettingsModeMissingId: '知识库设置模式缺少知识库ID',
|
||||
completeEmbeddingConfig: '请先完成嵌入配置',
|
||||
detectionSuccess: '检测成功,',
|
||||
dimensionAutoFilled: '维度已自动填充:',
|
||||
checkFormCorrectness: '请检查表单填写是否正确',
|
||||
systemInitializationCompleted: '系统初始化完成',
|
||||
generationFailedRetry: '生成失败,请重试',
|
||||
chunkSizeDesc: '每个文本块的大小。较大的块保留更多上下文,但可能降低搜索准确性。',
|
||||
chunkOverlapDesc: '相邻块之间重叠的字符数。有助于保持块边界处的上下文。',
|
||||
selectRelationType: '选择关系类型'
|
||||
},
|
||||
auth: {
|
||||
login: '登录',
|
||||
logout: '退出',
|
||||
username: '用户名',
|
||||
email: '邮箱',
|
||||
password: '密码',
|
||||
confirmPassword: '确认密码',
|
||||
rememberMe: '记住我',
|
||||
forgotPassword: '忘记密码?',
|
||||
loginSuccess: '登录成功!',
|
||||
loginFailed: '登录失败',
|
||||
loggingIn: '登录中...',
|
||||
register: '注册',
|
||||
registering: '注册中...',
|
||||
createAccount: '创建账户',
|
||||
haveAccount: '已有账户?',
|
||||
noAccount: '还没有账户?',
|
||||
backToLogin: '返回登录',
|
||||
registerNow: '立即注册',
|
||||
registerSuccess: '注册成功!系统已为您创建专属租户,请登录',
|
||||
registerFailed: '注册失败',
|
||||
subtitle: '基于大模型的文档理解和语义搜索框架',
|
||||
registerSubtitle: '注册后系统将为您创建专属租户',
|
||||
emailPlaceholder: '输入邮箱地址',
|
||||
passwordPlaceholder: '输入密码(8-32个字符,包含字母和数字)',
|
||||
confirmPasswordPlaceholder: '再次输入密码',
|
||||
usernamePlaceholder: '输入用户名',
|
||||
emailRequired: '请输入邮箱地址',
|
||||
emailInvalid: '请输入正确的邮箱格式',
|
||||
passwordRequired: '请输入密码',
|
||||
passwordMinLength: '密码至少8个字符',
|
||||
passwordMaxLength: '密码不能超过32个字符',
|
||||
passwordMustContainLetter: '密码必须包含字母',
|
||||
passwordMustContainNumber: '密码必须包含数字',
|
||||
usernameRequired: '请输入用户名',
|
||||
usernameMinLength: '用户名至少2个字符',
|
||||
usernameMaxLength: '用户名不能超过20个字符',
|
||||
usernameInvalid: '用户名只能包含字母、数字、下划线和中文字符',
|
||||
confirmPasswordRequired: '请确认密码',
|
||||
passwordMismatch: '两次输入的密码不一致',
|
||||
loginError: '登录错误,请检查邮箱或密码',
|
||||
loginErrorRetry: '登录错误,请稍后重试',
|
||||
registerError: '注册错误,请稍后重试',
|
||||
forgotPasswordNotAvailable: '密码找回功能暂不可用,请联系管理员'
|
||||
},
|
||||
common: {
|
||||
confirm: '确认',
|
||||
cancel: '取消',
|
||||
save: '保存',
|
||||
delete: '删除',
|
||||
edit: '编辑',
|
||||
create: '创建',
|
||||
search: '搜索',
|
||||
filter: '筛选',
|
||||
export: '导出',
|
||||
import: '导入',
|
||||
upload: '上传',
|
||||
download: '下载',
|
||||
refresh: '刷新',
|
||||
loading: '加载中...',
|
||||
noData: '暂无数据',
|
||||
error: '错误',
|
||||
success: '成功',
|
||||
warning: '警告',
|
||||
info: '信息',
|
||||
yes: '是',
|
||||
no: '否',
|
||||
ok: '确定',
|
||||
close: '关闭',
|
||||
back: '返回',
|
||||
next: '下一步',
|
||||
finish: '完成',
|
||||
all: '全部',
|
||||
reset: '重置',
|
||||
clear: '清空'
|
||||
},
|
||||
file: {
|
||||
upload: '上传文件',
|
||||
uploadSuccess: '文件上传成功',
|
||||
uploadFailed: '文件上传失败',
|
||||
delete: '删除文件',
|
||||
deleteSuccess: '文件删除成功',
|
||||
deleteFailed: '文件删除失败',
|
||||
download: '下载文件',
|
||||
preview: '预览',
|
||||
unsupportedFormat: '不支持的文件格式',
|
||||
maxSizeExceeded: '文件大小超过限制',
|
||||
selectFile: '选择文件'
|
||||
},
|
||||
tenant: {
|
||||
title: '租户信息',
|
||||
name: '租户名称',
|
||||
id: '租户ID',
|
||||
createdAt: '创建时间',
|
||||
updatedAt: '更新时间',
|
||||
status: '状态',
|
||||
active: '活跃',
|
||||
inactive: '未活跃',
|
||||
systemInfo: '系统信息',
|
||||
viewSystemInfo: '查看系统版本和用户账户配置信息',
|
||||
version: '版本',
|
||||
buildTime: '构建时间',
|
||||
goVersion: 'Go版本',
|
||||
userInfo: '用户信息',
|
||||
userId: '用户ID',
|
||||
username: '用户名',
|
||||
email: '邮箱',
|
||||
tenantInfo: '租户信息',
|
||||
tenantId: '租户ID',
|
||||
tenantName: '租户名称',
|
||||
description: '描述',
|
||||
business: '业务',
|
||||
noDescription: '无描述',
|
||||
noBusiness: '无',
|
||||
statusActive: '活跃',
|
||||
statusInactive: '未激活',
|
||||
statusSuspended: '已暂停',
|
||||
statusUnknown: '未知',
|
||||
apiKey: 'API密钥',
|
||||
keepApiKeySafe: '请妥善保管您的API密钥,不要在公共场所或代码仓库中泄露',
|
||||
storageInfo: '存储信息',
|
||||
storageQuota: '存储配额',
|
||||
used: '已使用',
|
||||
usage: '使用率',
|
||||
apiDevDocs: 'API开发文档',
|
||||
useApiKey: '使用您的API密钥开始开发,查看完整的API文档和代码示例。',
|
||||
viewApiDoc: '查看API文档',
|
||||
loadingAccountInfo: '加载账户信息中...',
|
||||
loadFailed: '加载失败',
|
||||
retry: '重试',
|
||||
apiKeyCopied: 'API密钥已复制到剪贴板',
|
||||
unknown: '未知',
|
||||
formatError: '格式错误'
|
||||
},
|
||||
error: {
|
||||
network: '网络错误',
|
||||
server: '服务器错误',
|
||||
notFound: '未找到',
|
||||
unauthorized: '未授权',
|
||||
forbidden: '禁止访问',
|
||||
unknown: '未知错误',
|
||||
tryAgain: '请重试'
|
||||
},
|
||||
model: {
|
||||
llmModel: 'LLM模型',
|
||||
embeddingModel: '嵌入模型',
|
||||
rerankModel: '重排序模型',
|
||||
vlmModel: '多模态模型',
|
||||
modelName: '模型名称',
|
||||
modelProvider: '模型提供商',
|
||||
modelUrl: '模型地址',
|
||||
apiKey: 'API密钥',
|
||||
testConnection: '测试连接',
|
||||
connectionSuccess: '连接成功',
|
||||
connectionFailed: '连接失败',
|
||||
dimension: '维度',
|
||||
maxTokens: '最大令牌数',
|
||||
temperature: '温度',
|
||||
topP: 'Top P',
|
||||
selectModel: '选择模型',
|
||||
customModel: '自定义模型',
|
||||
builtinModel: '内置模型'
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import i18n from "./i18n";
|
||||
import "./assets/fonts.css";
|
||||
import TDesign from "tdesign-vue-next";
|
||||
// 引入组件库的少量全局样式变量
|
||||
@@ -12,5 +13,6 @@ const app = createApp(App);
|
||||
app.use(TDesign);
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
app.use(i18n);
|
||||
|
||||
app.mount("#app");
|
||||
|
||||
@@ -5,16 +5,16 @@ import { defineStore } from 'pinia';
|
||||
export const useMenuStore = defineStore('menuStore', {
|
||||
state: () => ({
|
||||
menuArr: reactive([
|
||||
{ title: '知识库', icon: 'zhishiku', path: 'knowledge-bases' },
|
||||
{ titleKey: 'menu.knowledgeBase', icon: 'zhishiku', path: 'knowledge-bases' },
|
||||
{
|
||||
title: '对话',
|
||||
titleKey: 'menu.chat',
|
||||
icon: 'prefixIcon',
|
||||
path: 'creatChat',
|
||||
childrenPath: 'chat',
|
||||
children: reactive<object[]>([]),
|
||||
},
|
||||
{ title: '系统信息', icon: 'tenant', path: 'tenant' },
|
||||
{ title: '退出登录', icon: 'logout', path: 'logout' }
|
||||
{ titleKey: 'menu.tenant', icon: 'tenant', path: 'tenant' },
|
||||
{ titleKey: 'menu.logout', icon: 'logout', path: 'logout' }
|
||||
]),
|
||||
isFirstSession: false,
|
||||
firstQuery: ''
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<!-- Переключатель языка -->
|
||||
<div class="language-switcher-wrapper">
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<div class="login-card" v-if="!isRegisterMode">
|
||||
<!-- 系统Logo和标题 -->
|
||||
@@ -7,7 +12,7 @@
|
||||
<div class="logo">
|
||||
<img src="@/assets/img/weknora.png" alt="WeKnora" class="logo-img" />
|
||||
</div>
|
||||
<p class="login-subtitle">基于大模型的文档理解与语义检索框架</p>
|
||||
<p class="login-subtitle">{{ t('auth.subtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="login-form">
|
||||
@@ -18,20 +23,20 @@
|
||||
@submit="handleLogin"
|
||||
layout="vertical"
|
||||
>
|
||||
<t-form-item label="邮箱" name="email">
|
||||
<t-form-item :label="t('auth.email')" name="email">
|
||||
<t-input
|
||||
v-model="formData.email"
|
||||
placeholder="请输入邮箱地址"
|
||||
:placeholder="t('auth.emailPlaceholder')"
|
||||
type="email"
|
||||
size="large"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</t-form-item>
|
||||
|
||||
<t-form-item label="密码" name="password">
|
||||
<t-form-item :label="t('auth.password')" name="password">
|
||||
<t-input
|
||||
v-model="formData.password"
|
||||
placeholder="请输入密码(8-32位,包含字母和数字)"
|
||||
:placeholder="t('auth.passwordPlaceholder')"
|
||||
type="password"
|
||||
size="large"
|
||||
:disabled="loading"
|
||||
@@ -47,15 +52,15 @@
|
||||
:loading="loading"
|
||||
class="login-button"
|
||||
>
|
||||
{{ loading ? '登录中...' : '登录' }}
|
||||
{{ loading ? t('auth.loggingIn') : t('auth.login') }}
|
||||
</t-button>
|
||||
</t-form>
|
||||
|
||||
<!-- 注册链接 -->
|
||||
<div class="register-link">
|
||||
<span>还没有账号?</span>
|
||||
<span>{{ t('auth.noAccount') }} </span>
|
||||
<a href="#" @click.prevent="toggleMode" class="register-btn">
|
||||
立即注册
|
||||
{{ t('auth.registerNow') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,8 +69,8 @@
|
||||
<!-- 注册表单 -->
|
||||
<div class="register-card" v-if="isRegisterMode">
|
||||
<div class="login-header">
|
||||
<h1 class="login-title">创建账号</h1>
|
||||
<p class="login-subtitle">注册后系统将为您创建专属租户</p>
|
||||
<h1 class="login-title">{{ t('auth.createAccount') }}</h1>
|
||||
<p class="login-subtitle">{{ t('auth.registerSubtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="login-form">
|
||||
@@ -76,39 +81,39 @@
|
||||
@submit="handleRegister"
|
||||
layout="vertical"
|
||||
>
|
||||
<t-form-item label="用户名" name="username">
|
||||
<t-form-item :label="t('auth.username')" name="username">
|
||||
<t-input
|
||||
v-model="registerData.username"
|
||||
placeholder="请输入用户名"
|
||||
:placeholder="t('auth.usernamePlaceholder')"
|
||||
size="large"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</t-form-item>
|
||||
|
||||
<t-form-item label="邮箱" name="email">
|
||||
<t-form-item :label="t('auth.email')" name="email">
|
||||
<t-input
|
||||
v-model="registerData.email"
|
||||
placeholder="请输入邮箱地址"
|
||||
:placeholder="t('auth.emailPlaceholder')"
|
||||
type="email"
|
||||
size="large"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</t-form-item>
|
||||
|
||||
<t-form-item label="密码" name="password">
|
||||
<t-form-item :label="t('auth.password')" name="password">
|
||||
<t-input
|
||||
v-model="registerData.password"
|
||||
placeholder="请输入密码(8-32位,包含字母和数字)"
|
||||
:placeholder="t('auth.passwordPlaceholder')"
|
||||
type="password"
|
||||
size="large"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</t-form-item>
|
||||
|
||||
<t-form-item label="确认密码" name="confirmPassword">
|
||||
<t-form-item :label="t('auth.confirmPassword')" name="confirmPassword">
|
||||
<t-input
|
||||
v-model="registerData.confirmPassword"
|
||||
placeholder="请再次输入密码"
|
||||
:placeholder="t('auth.confirmPasswordPlaceholder')"
|
||||
type="password"
|
||||
size="large"
|
||||
:disabled="loading"
|
||||
@@ -124,15 +129,15 @@
|
||||
:loading="loading"
|
||||
class="login-button"
|
||||
>
|
||||
{{ loading ? '注册中...' : '注册' }}
|
||||
{{ loading ? t('auth.registering') : t('auth.register') }}
|
||||
</t-button>
|
||||
</t-form>
|
||||
|
||||
<!-- 返回登录 -->
|
||||
<div class="register-link">
|
||||
<span>已有账号?</span>
|
||||
<span>{{ t('auth.haveAccount') }} </span>
|
||||
<a href="#" @click.prevent="toggleMode" class="register-btn">
|
||||
返回登录
|
||||
{{ t('auth.backToLogin') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,9 +148,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, nextTick, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { MessagePlugin } from 'tdesign-vue-next'
|
||||
import { login, register } from '@/api/auth'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
@@ -174,52 +183,52 @@ const registerData = reactive<{[key: string]: any}>({
|
||||
})
|
||||
|
||||
// 登录表单验证规则
|
||||
const formRules = {
|
||||
const formRules = computed(() => ({
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱地址', type: 'error' },
|
||||
{ email: true, message: '请输入正确的邮箱格式', type: 'error' }
|
||||
{ required: true, message: t('auth.emailRequired'), type: 'error' },
|
||||
{ email: true, message: t('auth.emailInvalid'), type: 'error' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', type: 'error' },
|
||||
{ min: 8, message: '密码至少8位', type: 'error' },
|
||||
{ max: 32, message: '密码不能超过32位', type: 'error' },
|
||||
{ pattern: /[a-zA-Z]/, message: '密码必须包含字母', type: 'error' },
|
||||
{ pattern: /\d/, message: '密码必须包含数字', type: 'error' }
|
||||
{ required: true, message: t('auth.passwordRequired'), type: 'error' },
|
||||
{ min: 8, message: t('auth.passwordMinLength'), type: 'error' },
|
||||
{ max: 32, message: t('auth.passwordMaxLength'), type: 'error' },
|
||||
{ pattern: /[a-zA-Z]/, message: t('auth.passwordMustContainLetter'), type: 'error' },
|
||||
{ pattern: /\d/, message: t('auth.passwordMustContainNumber'), type: 'error' }
|
||||
]
|
||||
}
|
||||
}))
|
||||
|
||||
// 注册表单验证规则
|
||||
const registerRules = {
|
||||
const registerRules = computed(() => ({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', type: 'error' },
|
||||
{ min: 2, message: '用户名至少2位', type: 'error' },
|
||||
{ max: 20, message: '用户名不能超过20位', type: 'error' },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_\u4e00-\u9fa5]+$/,
|
||||
message: '用户名只能包含字母、数字、下划线和中文',
|
||||
type: 'error'
|
||||
{ required: true, message: t('auth.usernameRequired'), type: 'error' },
|
||||
{ min: 2, message: t('auth.usernameMinLength'), type: 'error' },
|
||||
{ max: 20, message: t('auth.usernameMaxLength'), type: 'error' },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_\u4e00-\u9fa5]+$/,
|
||||
message: t('auth.usernameInvalid'),
|
||||
type: 'error'
|
||||
}
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱地址', type: 'error' },
|
||||
{ email: true, message: '请输入正确的邮箱格式', type: 'error' }
|
||||
{ required: true, message: t('auth.emailRequired'), type: 'error' },
|
||||
{ email: true, message: t('auth.emailInvalid'), type: 'error' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', type: 'error' },
|
||||
{ min: 8, message: '密码至少8位', type: 'error' },
|
||||
{ max: 32, message: '密码不能超过32位', type: 'error' },
|
||||
{ pattern: /[a-zA-Z]/, message: '密码必须包含字母', type: 'error' },
|
||||
{ pattern: /\d/, message: '密码必须包含数字', type: 'error' }
|
||||
{ required: true, message: t('auth.passwordRequired'), type: 'error' },
|
||||
{ min: 8, message: t('auth.passwordMinLength'), type: 'error' },
|
||||
{ max: 32, message: t('auth.passwordMaxLength'), type: 'error' },
|
||||
{ pattern: /[a-zA-Z]/, message: t('auth.passwordMustContainLetter'), type: 'error' },
|
||||
{ pattern: /\d/, message: t('auth.passwordMustContainNumber'), type: 'error' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请确认密码', type: 'error' },
|
||||
{ required: true, message: t('auth.confirmPasswordRequired'), type: 'error' },
|
||||
{
|
||||
validator: (val: string) => val === registerData.password,
|
||||
message: '两次输入的密码不一致',
|
||||
message: t('auth.passwordMismatch'),
|
||||
type: 'error'
|
||||
}
|
||||
]
|
||||
}
|
||||
}))
|
||||
|
||||
// 切换登录/注册模式
|
||||
const toggleMode = () => {
|
||||
@@ -269,18 +278,18 @@ const handleLogin = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
MessagePlugin.success('登录成功!')
|
||||
MessagePlugin.success(t('auth.loginSuccess'))
|
||||
|
||||
|
||||
// 等待状态更新完成后再跳转
|
||||
await nextTick()
|
||||
router.replace('/platform/knowledge-bases')
|
||||
} else {
|
||||
MessagePlugin.error(response.message || '登录失败,请检查邮箱或密码')
|
||||
MessagePlugin.error(response.message || t('auth.loginError'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('登录错误:', error)
|
||||
MessagePlugin.error(error.message || '登录失败,请稍后重试')
|
||||
console.error(t('auth.loginError') + ':', error)
|
||||
MessagePlugin.error(error.message || t('auth.loginErrorRetry'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -301,7 +310,7 @@ const handleRegister = async () => {
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
MessagePlugin.success('注册成功!系统已为您创建专属租户,请登录使用')
|
||||
MessagePlugin.success(t('auth.registerSuccess'))
|
||||
|
||||
// 切换到登录模式并填入邮箱
|
||||
isRegisterMode.value = false
|
||||
@@ -312,11 +321,11 @@ const handleRegister = async () => {
|
||||
(registerData as any)[key] = ''
|
||||
})
|
||||
} else {
|
||||
MessagePlugin.error(response.message || '注册失败')
|
||||
MessagePlugin.error(response.message || t('auth.registerFailed'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('注册错误:', error)
|
||||
MessagePlugin.error(error.message || '注册失败,请稍后重试')
|
||||
console.error(t('auth.registerError') + ':', error)
|
||||
MessagePlugin.error(error.message || t('auth.registerError'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -324,7 +333,7 @@ const handleRegister = async () => {
|
||||
|
||||
// 处理忘记密码
|
||||
const handleForgotPassword = () => {
|
||||
MessagePlugin.info('忘记密码功能暂未开放,请联系管理员')
|
||||
MessagePlugin.info(t('auth.forgotPasswordNotAvailable'))
|
||||
}
|
||||
|
||||
// 检查是否已登录
|
||||
@@ -344,6 +353,34 @@ onMounted(() => {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.language-switcher-wrapper {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 10;
|
||||
|
||||
:deep(.t-select) {
|
||||
min-width: 140px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
:deep(.t-select__wrap) {
|
||||
border-color: #e7e7e7;
|
||||
|
||||
&:hover {
|
||||
border-color: #07C05F;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.t-is-focused .t-select__wrap) {
|
||||
border-color: #07C05F;
|
||||
box-shadow: 0 0 0 2px rgba(7, 192, 95, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.login-card,
|
||||
@@ -525,6 +562,15 @@ onMounted(() => {
|
||||
.login-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.language-switcher-wrapper {
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
|
||||
:deep(.t-select) {
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-card,
|
||||
.register-card {
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
<div ref="parentMd">
|
||||
<!-- 消息正在总结中则渲染加载gif -->
|
||||
<img v-if="session.thinking" class="botanswer_laoding_gif" src="@/assets/img/botanswer_loading.gif"
|
||||
alt="正在总结答案……">
|
||||
:alt="$t('chat.summarizingAnswer')">
|
||||
<div v-for="(item, index) in processedMarkdown" :key="index">
|
||||
<img class="ai-markdown-img" @click="preview(item)" v-if="isLink(item)" :src="item" alt="">
|
||||
<div v-else class="ai-markdown-template" v-html="processMarkdown(item)"></div>
|
||||
</div>
|
||||
<div v-if="isImgLoading" class="img_loading"><t-loading size="small"></t-loading><span>加载中...</span></div>
|
||||
<div v-if="isImgLoading" class="img_loading"><t-loading size="small"></t-loading><span>{{ $t('chat.loading') }}</span></div>
|
||||
</div>
|
||||
<picturePreview :reviewImg="reviewImg" :reviewUrl="reviewUrl" @closePreImg="closePreImg"></picturePreview>
|
||||
</div>
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
<template #header>
|
||||
<div class="deep-title">
|
||||
<div v-if="deepSession.thinking" class="thinking">
|
||||
<img class="img_gif" src="@/assets/img/think.gif" alt="">思考中···
|
||||
<img class="img_gif" src="@/assets/img/think.gif" :alt="$t('chat.thinking')">
|
||||
</div>
|
||||
<div v-else class="done">
|
||||
<img class="icon deep_icon" src="@/assets/img/Frame3718.svg" alt=""></img>已深度思考
|
||||
<img class="icon deep_icon" src="@/assets/img/Frame3718.svg" :alt="$t('chat.deepThinking')">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -29,7 +29,9 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, watch, computed, ref, reactive, defineProps } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { sanitizeHTML } from '@/utils/security';
|
||||
const { t } = useI18n();
|
||||
|
||||
const isFold = ref(true)
|
||||
const props = defineProps({
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="refer_header" @click="referBoxSwitch" v-if="session.knowledge_references && session.knowledge_references.length">
|
||||
<div class="refer_title">
|
||||
<img src="@/assets/img/ziliao.svg" alt="" />
|
||||
<span>参考了{{ session.knowledge_references && session.knowledge_references.length }}个相关内容</span>
|
||||
<span>{{ $t('chat.referencedContent', { count: session.knowledge_references && session.knowledge_references.length }) }}</span>
|
||||
</div>
|
||||
<div class="refer_show_icon">
|
||||
<t-icon :name="showReferBox ? 'chevron-up' : 'chevron-down'" />
|
||||
@@ -28,7 +28,9 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, defineProps, computed, ref, reactive } from "vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { sanitizeHTML } from '@/utils/security';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
// 必填项
|
||||
content: {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<t-textarea resize="none" :autosize="false" v-model="value" placeholder="请输入描述文案" name="description" @change="onChange" />
|
||||
<t-textarea resize="none" :autosize="false" v-model="value" :placeholder="$t('chat.enterDescription')" name="description" @change="onChange" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, watch, computed, ref, reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const value = ref('');
|
||||
const onChange = (value,e) => {
|
||||
console.log(value)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
<div v-if="loading"
|
||||
style="height: 41px;display: flex;align-items: center;background: #fff;width: 58px;">
|
||||
<img class="botanswer_laoding_gif" src="@/assets/img/botanswer_loading.gif" alt="正在等待答案……">
|
||||
<img class="botanswer_laoding_gif" src="@/assets/img/botanswer_loading.gif" :alt="$t('chat.waitingForAnswer')">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,12 +26,14 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, onMounted, onUnmounted, nextTick, watch, reactive, onBeforeUnmount } from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import InputField from '../../components/Input-field.vue';
|
||||
import botmsg from './components/botmsg.vue';
|
||||
import usermsg from './components/usermsg.vue';
|
||||
import { getMessageList, generateSessionsTitle } from "@/api/chat/index";
|
||||
import { useStream } from '../../api/chat/streame'
|
||||
import { useMenuStore } from '@/stores/menu';
|
||||
const { t } = useI18n();
|
||||
const usemenuStore = useMenuStore();
|
||||
const { menuArr, isFirstSession, firstQuery } = storeToRefs(usemenuStore);
|
||||
const { output, onChunk, isStreaming, isLoading, error, startStream, stopStream } = useStream();
|
||||
@@ -124,7 +126,7 @@ const handleMsgList = async (data, isScrollType = false, newScrollHeight) => {
|
||||
}
|
||||
}
|
||||
if (item.is_completed && !item.content) {
|
||||
item.content = "抱歉,我无法回答这个问题。";
|
||||
item.content = t('chat.cannotAnswer');
|
||||
}
|
||||
messagesList.unshift(item);
|
||||
if (isFirstEnter.value) {
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
<div class="dialogue-wrap">
|
||||
<div class="dialogue-answers">
|
||||
<div class="dialogue-title">
|
||||
<span>基于知识库内容问答</span>
|
||||
<span>{{ t('chat.knowledgeBaseQandA') }}</span>
|
||||
</div>
|
||||
<InputField @send-msg="sendMsg"></InputField>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<t-dialog v-model:visible="selectVisible" header="选择知识库" :confirmBtn="{ content: '开始对话', theme: 'primary' }" :onConfirm="confirmSelect" :onCancel="() => selectVisible = false">
|
||||
<t-dialog v-model:visible="selectVisible" :header="t('knowledgeBase.title')" :confirmBtn="{ content: t('chat.newChat'), theme: 'primary' }" :onConfirm="confirmSelect" :onCancel="() => selectVisible = false">
|
||||
<t-form :data="{ kb: selectedKbId }">
|
||||
<t-form-item label="知识库">
|
||||
<t-select v-model="selectedKbId" :loading="kbLoading" placeholder="请选择知识库">
|
||||
<t-form-item :label="t('knowledgeBase.title')">
|
||||
<t-select v-model="selectedKbId" :loading="kbLoading" :placeholder="t('knowledgeBase.selectKnowledgeBaseFirst')">
|
||||
<t-option v-for="kb in kbList" :key="kb.id" :value="kb.id" :label="kb.name" />
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
@@ -21,6 +21,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onUnmounted, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import InputField from '@/components/Input-field.vue';
|
||||
import EmptyKnowledge from '@/components/empty-knowledge.vue';
|
||||
import { getSessionsList, createSessions, generateSessionsTitle } from "@/api/chat/index";
|
||||
@@ -29,6 +30,8 @@ import { useRoute, useRouter } from 'vue-router';
|
||||
import useKnowledgeBase from '@/hooks/useKnowledgeBase';
|
||||
import { listKnowledgeBases } from '@/api/knowledge-base';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
let { cardList } = useKnowledgeBase()
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@@ -75,10 +78,10 @@ async function createNewSession(value: string) {
|
||||
await getTitle(res.data.id, value)
|
||||
} else {
|
||||
// 错误处理
|
||||
console.error("创建会话失败");
|
||||
console.error(t('chat.createSessionFailed'));
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error("创建会话出错:", error);
|
||||
console.error(t('chat.createSessionError') + ':', error);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -92,19 +95,19 @@ const confirmSelect = async () => {
|
||||
if (res.data && res.data.id) {
|
||||
await getTitle(res.data.id, value, selectedKbId.value)
|
||||
} else {
|
||||
console.error('创建会话失败')
|
||||
console.error(t('chat.createSessionFailed'))
|
||||
}
|
||||
}).catch((e:any) => console.error('创建会话出错:', e))
|
||||
}).catch((e:any) => console.error(t('chat.createSessionError') + ':', e))
|
||||
}
|
||||
|
||||
const getTitle = async (session_id: string, value: string, kbId?: string) => {
|
||||
const finalKbId = kbId || await ensureKbId();
|
||||
if (!finalKbId) {
|
||||
console.error('无法获取知识库ID');
|
||||
console.error(t('chat.unableToGetKnowledgeBaseId'));
|
||||
return;
|
||||
}
|
||||
|
||||
let obj = { title: '新会话', path: `chat/${finalKbId}/${session_id}`, id: session_id, isMore: false, isNoTitle: true }
|
||||
let obj = { title: t('menu.newSession'), path: `chat/${finalKbId}/${session_id}`, id: session_id, isMore: false, isNoTitle: true }
|
||||
usemenuStore.updataMenuChildren(obj);
|
||||
usemenuStore.changeIsFirstSession(true);
|
||||
usemenuStore.changeFirstQuery(value);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,8 @@ import EmptyKnowledge from '@/components/empty-knowledge.vue';
|
||||
import { getSessionsList, createSessions, generateSessionsTitle } from "@/api/chat/index";
|
||||
import { useMenuStore } from '@/stores/menu';
|
||||
import { MessagePlugin } from 'tdesign-vue-next';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
const usemenuStore = useMenuStore();
|
||||
const router = useRouter();
|
||||
import {
|
||||
@@ -34,14 +36,17 @@ const getPageSize = () => {
|
||||
}
|
||||
getPageSize()
|
||||
// 直接调用 API 获取知识库文件列表
|
||||
console.log(t('knowledgeBase.apiCallKnowledgeFiles'));
|
||||
const loadKnowledgeFiles = async (kbIdValue: string) => {
|
||||
if (!kbIdValue) return;
|
||||
|
||||
|
||||
try {
|
||||
const result = await listKnowledgeFiles(kbIdValue, { page: 1, page_size: pageSize });
|
||||
|
||||
|
||||
// 由于响应拦截器已经返回了 data,所以 result 就是响应的 data 部分
|
||||
// 按照 useKnowledgeBase hook 中的方式处理
|
||||
console.log(t('knowledgeBase.responseInterceptorData'));
|
||||
console.log(t('knowledgeBase.hookProcessing'));
|
||||
const { data, total: totalResult } = result as any;
|
||||
|
||||
if (!data || !Array.isArray(data)) {
|
||||
@@ -65,32 +70,34 @@ const loadKnowledgeFiles = async (kbIdValue: string) => {
|
||||
cardList.value = cardList_ as any[];
|
||||
total.value = totalResult;
|
||||
} catch (err) {
|
||||
console.error('Failed to load knowledge files:', err);
|
||||
console.error(t('knowledgeBase.errorHandling') + ':', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听路由参数变化,重新获取知识库内容
|
||||
watch(() => kbId.value, (newKbId, oldKbId) => {
|
||||
if (newKbId && newKbId !== oldKbId) {
|
||||
console.log(t('knowledgeBase.routeParamChange'));
|
||||
loadKnowledgeFiles(newKbId);
|
||||
}
|
||||
}, { immediate: false });
|
||||
|
||||
// 监听文件上传事件
|
||||
const handleFileUploaded = (event: CustomEvent) => {
|
||||
const uploadedKbId = event.detail.kbId;
|
||||
console.log('接收到文件上传事件,上传的知识库ID:', uploadedKbId, '当前知识库ID:', kbId.value);
|
||||
if (uploadedKbId && uploadedKbId === kbId.value) {
|
||||
console.log('匹配当前知识库,开始刷新文件列表');
|
||||
// 如果上传的文件属于当前知识库,使用 loadKnowledgeFiles 刷新文件列表
|
||||
loadKnowledgeFiles(uploadedKbId);
|
||||
}
|
||||
const uploadedKbId = event.detail.kbId;
|
||||
console.log(t('knowledgeBase.fileUploadEventReceived', { uploadedKbId, currentKbId: kbId.value }));
|
||||
if (uploadedKbId && uploadedKbId === kbId.value) {
|
||||
console.log(t('knowledgeBase.matchingKnowledgeBase'));
|
||||
// 如果上传的文件属于当前知识库,使用 loadKnowledgeFiles 刷新文件列表
|
||||
loadKnowledgeFiles(uploadedKbId);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getKnowled({ page: 1, page_size: pageSize });
|
||||
|
||||
// 监听文件上传事件
|
||||
console.log(t('knowledgeBase.fileUploadEventListening'));
|
||||
window.addEventListener('knowledgeFileUploaded', handleFileUploaded as EventListener);
|
||||
});
|
||||
|
||||
@@ -176,7 +183,7 @@ const sendMsg = (value: string) => {
|
||||
};
|
||||
|
||||
const getTitle = (session_id: string, value: string) => {
|
||||
let obj = { title: '新会话', path: `chat/${kbId.value}/${session_id}`, id: session_id, isMore: false, isNoTitle: true };
|
||||
let obj = { title: t('knowledgeBase.newSession'), path: `chat/${kbId.value}/${session_id}`, id: session_id, isMore: false, isNoTitle: true };
|
||||
usemenuStore.updataMenuChildren(obj);
|
||||
usemenuStore.changeIsFirstSession(true);
|
||||
usemenuStore.changeFirstQuery(value);
|
||||
@@ -185,23 +192,25 @@ const getTitle = (session_id: string, value: string) => {
|
||||
|
||||
async function createNewSession(value: string): Promise<void> {
|
||||
// 优先使用当前页面的知识库ID
|
||||
console.log(t('knowledgeBase.priorityCurrentPageKbId'));
|
||||
let sessionKbId = kbId.value;
|
||||
|
||||
|
||||
// 如果当前页面没有知识库ID,尝试从localStorage获取设置中的知识库ID
|
||||
if (!sessionKbId) {
|
||||
console.log(t('knowledgeBase.fallbackLocalStorageKbId'));
|
||||
const settingsStr = localStorage.getItem("WeKnora_settings");
|
||||
if (settingsStr) {
|
||||
try {
|
||||
const settings = JSON.parse(settingsStr);
|
||||
sessionKbId = settings.knowledgeBaseId;
|
||||
} catch (e) {
|
||||
console.error("解析设置失败:", e);
|
||||
console.error(t('knowledgeBase.settingsParsingFailed') + ":", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sessionKbId) {
|
||||
MessagePlugin.warning("请先选择一个知识库");
|
||||
MessagePlugin.warning(t('knowledgeBase.selectKnowledgeBaseFirst'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -210,10 +219,10 @@ async function createNewSession(value: string): Promise<void> {
|
||||
getTitle(res.data.id, value);
|
||||
} else {
|
||||
// 错误处理
|
||||
console.error("创建会话失败");
|
||||
console.error(t('knowledgeBase.sessionCreationFailed'));
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error("创建会话出错:", error);
|
||||
console.error(t('knowledgeBase.sessionCreationError') + ":", error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -233,7 +242,7 @@ async function createNewSession(value: string): Promise<void> {
|
||||
</div>
|
||||
<template #content>
|
||||
<t-icon class="icon svg-icon del-card" name="delete" />
|
||||
<span class="del-card" style="margin-left: 8px">删除文档</span>
|
||||
<span class="del-card" style="margin-left: 8px">{{ t('knowledgeBase.deleteDocument') }}</span>
|
||||
</template>
|
||||
</t-popup>
|
||||
</div>
|
||||
@@ -241,7 +250,7 @@ async function createNewSession(value: string): Promise<void> {
|
||||
<t-icon :name="item.parse_status == 'failed' ? 'close-circle' : 'loading'" class="card-analyze-loading"
|
||||
:class="[item.parse_status == 'failed' ? 'failure' : '']"></t-icon>
|
||||
<span class="card-analyze-txt" :class="[item.parse_status == 'failed' ? 'failure' : '']">{{
|
||||
item.parse_status == "failed" ? "解析失败" : "解析中..."
|
||||
item.parse_status == "failed" ? t('knowledgeBase.parsingFailed') : t('knowledgeBase.parsingInProgress')
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-show="item.parse_status == 'completed'" class="card-content-txt">
|
||||
@@ -260,14 +269,14 @@ async function createNewSession(value: string): Promise<void> {
|
||||
<div class="circle-wrap">
|
||||
<div class="header">
|
||||
<img class="circle-img" src="@/assets/img/circle.png" alt="">
|
||||
<span class="circle-title">删除确认</span>
|
||||
<span class="circle-title">{{ t('knowledgeBase.deleteConfirmation') }}</span>
|
||||
</div>
|
||||
<span class="del-circle-txt">
|
||||
{{ `确认要删除技能"${knowledge.file_name}",删除后不可恢复` }}
|
||||
{{ t('knowledgeBase.confirmDeleteDocument', { fileName: knowledge.file_name }) }}
|
||||
</span>
|
||||
<div class="circle-btn">
|
||||
<span class="circle-btn-txt" @click="delDialog = false">取消</span>
|
||||
<span class="circle-btn-txt confirm" @click="delCardConfirm">确认删除</span>
|
||||
<span class="circle-btn-txt" @click="delDialog = false">{{ t('knowledgeBase.cancel') }}</span>
|
||||
<span class="circle-btn-txt confirm" @click="delCardConfirm">{{ t('knowledgeBase.confirmDelete') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</t-dialog>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<template>
|
||||
<div class="kb-list-container">
|
||||
<div class="header">
|
||||
<h2>知识库</h2>
|
||||
<t-button theme="primary" @click="openCreate">新建知识库</t-button>
|
||||
<h2>{{ t('knowledgeBase.title') }}</h2>
|
||||
<t-button theme="primary" @click="openCreate">{{ t('knowledgeBase.createNewKnowledgeBase') }}</t-button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 未初始化知识库提示 -->
|
||||
<div v-if="hasUninitializedKbs" class="warning-banner">
|
||||
<t-icon name="info-circle" size="16px" />
|
||||
<span>部分知识库尚未初始化,需要先在设置中配置模型信息才能添加知识文档</span>
|
||||
<span>{{ t('knowledgeBase.uninitializedWarning') }}</span>
|
||||
</div>
|
||||
<t-table :data="kbs" :columns="columns" row-key="id" size="medium" hover>
|
||||
<template #status="{ row }">
|
||||
<div class="status-cell">
|
||||
<t-tag
|
||||
<t-tag
|
||||
:theme="isInitialized(row) ? 'success' : 'warning'"
|
||||
size="small"
|
||||
>
|
||||
{{ isInitialized(row) ? '已初始化' : '未初始化' }}
|
||||
{{ isInitialized(row) ? t('knowledgeBase.initializedStatus') : t('knowledgeBase.notInitializedStatus') }}
|
||||
</t-tag>
|
||||
<t-tooltip
|
||||
v-if="!isInitialized(row)"
|
||||
content="需要先在设置中配置模型信息才能添加知识"
|
||||
<t-tooltip
|
||||
v-if="!isInitialized(row)"
|
||||
:content="t('knowledgeBase.needSettingsFirst')"
|
||||
placement="top"
|
||||
>
|
||||
<span class="warning-icon">⚠</span>
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #description="{ row }">
|
||||
<div class="description-text">{{ row.description || '暂无描述' }}</div>
|
||||
<div class="description-text">{{ row.description || t('knowledgeBase.noDescription') }}</div>
|
||||
</template>
|
||||
<template #op="{ row }">
|
||||
<t-space size="small">
|
||||
@@ -39,30 +39,30 @@
|
||||
:disabled="!isInitialized(row)"
|
||||
:theme="isInitialized(row) ? 'primary' : 'default'"
|
||||
:variant="isInitialized(row) ? 'base' : 'outline'"
|
||||
:title="!isInitialized(row) ? '请先在设置中配置模型信息' : ''"
|
||||
:title="!isInitialized(row) ? t('knowledgeBase.configureModelsFirst') : ''"
|
||||
>
|
||||
文档
|
||||
{{ t('knowledgeBase.documents') }}
|
||||
</t-button>
|
||||
<t-button size="small" variant="outline" @click="goSettings(row.id)">设置</t-button>
|
||||
<t-popconfirm content="确认删除该知识库?" @confirm="remove(row.id)">
|
||||
<t-button size="small" theme="danger" variant="text">删除</t-button>
|
||||
<t-button size="small" variant="outline" @click="goSettings(row.id)">{{ t('knowledgeBase.settings') }}</t-button>
|
||||
<t-popconfirm :content="t('knowledgeBase.confirmDeleteKnowledgeBase')" @confirm="remove(row.id)">
|
||||
<t-button size="small" theme="danger" variant="text">{{ t('knowledgeBase.delete') }}</t-button>
|
||||
</t-popconfirm>
|
||||
</t-space>
|
||||
</template>
|
||||
</t-table>
|
||||
|
||||
<t-dialog v-model:visible="createVisible" header="新建知识库" :footer="false">
|
||||
<t-dialog v-model:visible="createVisible" :header="t('knowledgeBase.createKnowledgeBaseDialog')" :footer="false">
|
||||
<t-form :data="createForm" @submit="create">
|
||||
<t-form-item label="名称" name="name" :rules="[{ required: true, message: '请输入名称' }]">
|
||||
<t-form-item :label="t('knowledgeBase.name')" name="name" :rules="[{ required: true, message: t('knowledgeBase.enterNameKb') }]">
|
||||
<t-input v-model="createForm.name" />
|
||||
</t-form-item>
|
||||
<t-form-item label="描述" name="description">
|
||||
<t-form-item :label="t('knowledgeBase.description')" name="description">
|
||||
<t-textarea v-model="createForm.description" />
|
||||
</t-form-item>
|
||||
<t-form-item>
|
||||
<t-space>
|
||||
<t-button theme="primary" type="submit" :loading="creating">创建</t-button>
|
||||
<t-button variant="outline" @click="createVisible = false">取消</t-button>
|
||||
<t-button theme="primary" type="submit" :loading="creating">{{ t('knowledgeBase.createKb') }}</t-button>
|
||||
<t-button variant="outline" @click="createVisible = false">{{ t('common.cancel') }}</t-button>
|
||||
</t-space>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
@@ -76,6 +76,8 @@ import { useRouter } from 'vue-router'
|
||||
import { MessagePlugin } from 'tdesign-vue-next'
|
||||
import { listKnowledgeBases, createKnowledgeBase, deleteKnowledgeBase } from '@/api/knowledge-base'
|
||||
import { formatStringDate } from '@/utils/index'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -91,11 +93,11 @@ const kbs = ref<KB[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
const columns = [
|
||||
{ colKey: 'name', title: '名称' },
|
||||
{ colKey: 'description', title: '描述', cell: 'description', width: 300 },
|
||||
{ colKey: 'status', title: '状态', cell: 'status', width: 100 },
|
||||
{ colKey: 'updated_at', title: '更新时间' },
|
||||
{ colKey: 'op', title: '操作', cell: 'op', width: 220 },
|
||||
{ colKey: 'name', title: t('knowledgeBase.name') },
|
||||
{ colKey: 'description', title: t('knowledgeBase.description'), cell: 'description', width: 300 },
|
||||
{ colKey: 'status', title: t('knowledgeBase.status'), cell: 'status', width: 100 },
|
||||
{ colKey: 'updated_at', title: t('knowledgeBase.uploadTime') },
|
||||
{ colKey: 'op', title: t('knowledgeBase.actions'), cell: 'op', width: 220 },
|
||||
]
|
||||
|
||||
const fetchList = () => {
|
||||
@@ -131,26 +133,26 @@ const create = () => {
|
||||
}
|
||||
createKnowledgeBase({ name: createForm.name, description: createForm.description, chunking_config }).then((res: any) => {
|
||||
if (res.success) {
|
||||
MessagePlugin.success('创建成功')
|
||||
MessagePlugin.success(t('knowledgeBase.createSuccess'))
|
||||
createVisible.value = false
|
||||
fetchList()
|
||||
} else {
|
||||
MessagePlugin.error(res.message || '创建失败')
|
||||
MessagePlugin.error(res.message || t('knowledgeBase.createFailed'))
|
||||
}
|
||||
}).catch((e: any) => {
|
||||
MessagePlugin.error(e?.message || '创建失败')
|
||||
MessagePlugin.error(e?.message || t('knowledgeBase.createFailed'))
|
||||
}).finally(() => creating.value = false)
|
||||
}
|
||||
|
||||
const remove = (id: string) => {
|
||||
deleteKnowledgeBase(id).then((res: any) => {
|
||||
if (res.success) {
|
||||
MessagePlugin.success('已删除')
|
||||
MessagePlugin.success(t('knowledgeBase.deleted'))
|
||||
fetchList()
|
||||
} else {
|
||||
MessagePlugin.error(res.message || '删除失败')
|
||||
MessagePlugin.error(res.message || t('knowledgeBase.deleteFailedKb'))
|
||||
}
|
||||
}).catch((e: any) => MessagePlugin.error(e?.message || '删除失败'))
|
||||
}).catch((e: any) => MessagePlugin.error(e?.message || t('knowledgeBase.deleteFailedKb')))
|
||||
}
|
||||
|
||||
const isInitialized = (kb: KB) => {
|
||||
|
||||
@@ -12,11 +12,14 @@
|
||||
import Menu from '@/components/menu.vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import useKnowledgeBase from '@/hooks/useKnowledgeBase'
|
||||
import UploadMask from '@/components/upload-mask.vue'
|
||||
import { getKnowledgeBaseById } from '@/api/knowledge-base/index'
|
||||
import { MessagePlugin } from 'tdesign-vue-next'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
let { requestMethod } = useKnowledgeBase()
|
||||
const route = useRoute();
|
||||
let ismask = ref(false)
|
||||
@@ -32,7 +35,7 @@ const checkKnowledgeBaseInitialization = async (): Promise<boolean> => {
|
||||
const currentKbId = getCurrentKbId();
|
||||
|
||||
if (!currentKbId) {
|
||||
MessagePlugin.error("缺少知识库ID");
|
||||
MessagePlugin.error(t('knowledgeBase.missingId'));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -41,12 +44,12 @@ const checkKnowledgeBaseInitialization = async (): Promise<boolean> => {
|
||||
const kb = kbResponse.data;
|
||||
|
||||
if (!kb.embedding_model_id || !kb.summary_model_id) {
|
||||
MessagePlugin.warning("该知识库尚未完成初始化配置,请先前往设置页面配置模型信息后再上传文件");
|
||||
MessagePlugin.warning(t('knowledgeBase.notInitialized'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
MessagePlugin.error("获取知识库信息失败,无法上传文件");
|
||||
MessagePlugin.error(t('knowledgeBase.getInfoFailed'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -91,7 +94,7 @@ const handleGlobalDrop = async (event: DragEvent) => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
MessagePlugin.warning('请拖拽文件而不是文本或链接');
|
||||
MessagePlugin.warning(t('knowledgeBase.dragFileNotText'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<template>
|
||||
<div class="settings-container">
|
||||
<div class="settings-header">
|
||||
<h2>系统配置</h2>
|
||||
<h2>{{ t('settings.systemConfig') }}</h2>
|
||||
</div>
|
||||
<div class="settings-form">
|
||||
<t-form ref="form" :data="formData" :rules="rules" @submit="onSubmit">
|
||||
<t-form-item label="API 服务端点" name="endpoint">
|
||||
<t-input v-model="formData.endpoint" placeholder="请输入API服务端点,例如:http://localhost" />
|
||||
<t-form-item :label="t('settings.apiEndpoint')" name="endpoint">
|
||||
<t-input v-model="formData.endpoint" :placeholder="t('settings.enterApiEndpoint')" />
|
||||
</t-form-item>
|
||||
<t-form-item label="API Key" name="apiKey">
|
||||
<t-input v-model="formData.apiKey" placeholder="请输入API Key" />
|
||||
<t-form-item :label="t('settings.apiKey')" name="apiKey">
|
||||
<t-input v-model="formData.apiKey" :placeholder="t('settings.enterApiKey')" />
|
||||
</t-form-item>
|
||||
<t-form-item label="知识库ID" name="knowledgeBaseId">
|
||||
<t-input v-model="formData.knowledgeBaseId" placeholder="请输入知识库ID" />
|
||||
<t-form-item :label="t('settings.knowledgeBaseId')" name="knowledgeBaseId">
|
||||
<t-input v-model="formData.knowledgeBaseId" :placeholder="t('settings.enterKnowledgeBaseId')" />
|
||||
</t-form-item>
|
||||
<t-form-item>
|
||||
<t-space>
|
||||
<t-button theme="primary" type="submit">保存配置</t-button>
|
||||
<t-button theme="default" @click="resetForm">重置</t-button>
|
||||
<t-button theme="primary" type="submit">{{ t('settings.saveConfig') }}</t-button>
|
||||
<t-button theme="default" @click="resetForm">{{ t('settings.reset') }}</t-button>
|
||||
</t-space>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
@@ -26,10 +26,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { MessagePlugin } from 'tdesign-vue-next';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const form = ref(null);
|
||||
|
||||
@@ -39,11 +42,11 @@ const formData = reactive({
|
||||
knowledgeBaseId: ''
|
||||
});
|
||||
|
||||
const rules = {
|
||||
endpoint: [{ required: true, message: '请输入API服务端点', trigger: 'blur' }],
|
||||
apiKey: [{ required: true, message: '请输入API Key', trigger: 'blur' }],
|
||||
knowledgeBaseId: [{ required: true, message: '请输入知识库ID', trigger: 'blur' }]
|
||||
};
|
||||
const rules = computed(() => ({
|
||||
endpoint: [{ required: true, message: t('settings.enterApiEndpointRequired'), trigger: 'blur' }],
|
||||
apiKey: [{ required: true, message: t('settings.enterApiKeyRequired'), trigger: 'blur' }],
|
||||
knowledgeBaseId: [{ required: true, message: t('settings.enterKnowledgeBaseIdRequired'), trigger: 'blur' }]
|
||||
}));
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化表单数据
|
||||
@@ -60,7 +63,7 @@ const onSubmit = ({ validateResult }) => {
|
||||
apiKey: formData.apiKey,
|
||||
knowledgeBaseId: formData.knowledgeBaseId
|
||||
});
|
||||
MessagePlugin.success('配置保存成功');
|
||||
MessagePlugin.success(t('settings.configSaved'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<div class="system-settings-container">
|
||||
<!-- 页面标题区域 -->
|
||||
<div class="settings-header">
|
||||
<h2>{{ isKbSettings ? '知识库设置' : '系统设置' }}</h2>
|
||||
<p class="settings-subtitle">{{ isKbSettings ? '配置该知识库的模型与文档切分参数' : '管理和更新系统模型与服务配置' }}</p>
|
||||
<h2>{{ isKbSettings ? t('settings.knowledgeBaseSettings') : t('settings.system') }}</h2>
|
||||
<p class="settings-subtitle">{{ isKbSettings ? t('settings.configureKbModels') : t('settings.manageSystemModels') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 配置内容 -->
|
||||
@@ -14,31 +14,31 @@
|
||||
<div v-else>
|
||||
<t-form :data="kbForm" @submit="saveKb">
|
||||
<div class="config-section">
|
||||
<h3><span class="section-icon">⚙️</span>基础信息</h3>
|
||||
<t-form-item label="名称" name="name" :rules="[{ required: true, message: '请输入名称' }]">
|
||||
<h3><span class="section-icon">⚙️</span>{{ t('settings.basicInfo') }}</h3>
|
||||
<t-form-item :label="t('settings.name')" name="name" :rules="[{ required: true, message: t('settings.enterNameRequired') }]">
|
||||
<t-input v-model="kbForm.name" />
|
||||
</t-form-item>
|
||||
<t-form-item label="描述" name="description">
|
||||
<t-form-item :label="t('settings.description')" name="description">
|
||||
<t-textarea v-model="kbForm.description" />
|
||||
</t-form-item>
|
||||
</div>
|
||||
<div class="config-section">
|
||||
<h3><span class="section-icon">📄</span>文档切分</h3>
|
||||
<h3><span class="section-icon">📄</span>{{ t('settings.documentSplitting') }}</h3>
|
||||
<t-row :gutter="16">
|
||||
<t-col :span="6">
|
||||
<t-form-item label="Chunk Size" name="chunkSize">
|
||||
<t-form-item :label="t('settings.chunkSize')" name="chunkSize">
|
||||
<t-input-number v-model="kbForm.config.chunking_config.chunk_size" :min="1" />
|
||||
</t-form-item>
|
||||
</t-col>
|
||||
<t-col :span="6">
|
||||
<t-form-item label="Chunk Overlap" name="chunkOverlap">
|
||||
<t-form-item :label="t('settings.chunkOverlap')" name="chunkOverlap">
|
||||
<t-input-number v-model="kbForm.config.chunking_config.chunk_overlap" :min="0" />
|
||||
</t-form-item>
|
||||
</t-col>
|
||||
</t-row>
|
||||
</div>
|
||||
<div class="submit-section">
|
||||
<t-button theme="primary" type="submit" :loading="saving">保存</t-button>
|
||||
<t-button theme="primary" type="submit" :loading="saving">{{ t('settings.save') }}</t-button>
|
||||
</div>
|
||||
</t-form>
|
||||
</div>
|
||||
@@ -47,11 +47,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, onMounted, reactive, ref } from 'vue'
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { MessagePlugin } from 'tdesign-vue-next'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getKnowledgeBaseById, updateKnowledgeBase } from '@/api/knowledge-base'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// 异步加载初始化配置组件
|
||||
const InitializationContent = defineAsyncComponent(() => import('../initialization/InitializationContent.vue'))
|
||||
|
||||
@@ -97,12 +100,12 @@ const saveKb = () => {
|
||||
updateKnowledgeBase(kbId, { name: kbForm.name, description: kbForm.description, config: { chunking_config: { chunk_size: kbForm.config.chunking_config.chunk_size, chunk_overlap: kbForm.config.chunking_config.chunk_overlap, separators: [], enable_multimodal: false }, image_processing_config: { model_id: '' } } })
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
MessagePlugin.success('保存成功')
|
||||
MessagePlugin.success(t('settings.saveSuccess'))
|
||||
} else {
|
||||
MessagePlugin.error(res.message || '保存失败')
|
||||
MessagePlugin.error(res.message || t('settings.saveFailed'))
|
||||
}
|
||||
})
|
||||
.catch((e: any) => MessagePlugin.error(e?.message || '保存失败'))
|
||||
.catch((e: any) => MessagePlugin.error(e?.message || t('settings.saveFailed')))
|
||||
.finally(() => saving.value = false)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<template>
|
||||
<div class="tenant-info-container">
|
||||
<div class="tenant-header">
|
||||
<h2>系统信息</h2>
|
||||
<p class="tenant-subtitle">查看系统版本信息和用户账户配置</p>
|
||||
<h2>{{ $t('tenant.systemInfo') }}</h2>
|
||||
<p class="tenant-subtitle">{{ $t('tenant.viewSystemInfo') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="tenant-content" v-if="!loading && !error">
|
||||
<!-- 系统信息卡片 -->
|
||||
<t-card class="info-card" :bordered="false">
|
||||
<template #header>
|
||||
<div class="card-title">系统信息</div>
|
||||
<div class="card-title">{{ $t('tenant.systemInfo') }}</div>
|
||||
</template>
|
||||
<div class="info-content">
|
||||
<t-descriptions :column="1" layout="vertical">
|
||||
<t-descriptions-item label="版本号">
|
||||
{{ systemInfo?.version || '未知' }}
|
||||
<t-descriptions-item :label="$t('tenant.version')">
|
||||
{{ systemInfo?.version || $t('tenant.unknown') }}
|
||||
<span v-if="systemInfo?.commit_id" class="commit-info">
|
||||
({{ systemInfo.commit_id }})
|
||||
</span>
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="构建时间" v-if="systemInfo?.build_time">
|
||||
<t-descriptions-item :label="$t('tenant.buildTime')" v-if="systemInfo?.build_time">
|
||||
{{ systemInfo.build_time }}
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="Go版本" v-if="systemInfo?.go_version">
|
||||
<t-descriptions-item :label="$t('tenant.goVersion')" v-if="systemInfo?.go_version">
|
||||
{{ systemInfo.go_version }}
|
||||
</t-descriptions-item>
|
||||
</t-descriptions>
|
||||
@@ -32,20 +32,20 @@
|
||||
<!-- 用户信息卡片 -->
|
||||
<t-card class="info-card" :bordered="false">
|
||||
<template #header>
|
||||
<div class="card-title">用户信息</div>
|
||||
<div class="card-title">{{ $t('tenant.userInfo') }}</div>
|
||||
</template>
|
||||
<div class="info-content">
|
||||
<t-descriptions :column="1" layout="vertical">
|
||||
<t-descriptions-item label="用户 ID">
|
||||
<t-descriptions-item :label="$t('tenant.userId')">
|
||||
{{ userInfo?.id }}
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="用户名">
|
||||
<t-descriptions-item :label="$t('tenant.username')">
|
||||
{{ userInfo?.username }}
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="邮箱">
|
||||
<t-descriptions-item :label="$t('tenant.email')">
|
||||
{{ userInfo?.email }}
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="创建时间">
|
||||
<t-descriptions-item :label="$t('tenant.createdAt')">
|
||||
{{ formatDate(userInfo?.created_at) }}
|
||||
</t-descriptions-item>
|
||||
</t-descriptions>
|
||||
@@ -55,31 +55,31 @@
|
||||
<!-- 租户信息卡片 -->
|
||||
<t-card class="info-card" :bordered="false">
|
||||
<template #header>
|
||||
<div class="card-title">租户信息</div>
|
||||
<div class="card-title">{{ $t('tenant.tenantInfo') }}</div>
|
||||
</template>
|
||||
<div class="info-content">
|
||||
<t-descriptions :column="1" layout="vertical">
|
||||
<t-descriptions-item label="租户 ID">
|
||||
<t-descriptions-item :label="$t('tenant.tenantId')">
|
||||
{{ tenantInfo?.id }}
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="租户名称">
|
||||
<t-descriptions-item :label="$t('tenant.tenantName')">
|
||||
{{ tenantInfo?.name }}
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="描述">
|
||||
{{ tenantInfo?.description || '暂无描述' }}
|
||||
<t-descriptions-item :label="$t('tenant.description')">
|
||||
{{ tenantInfo?.description || $t('tenant.noDescription') }}
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="业务">
|
||||
{{ tenantInfo?.business || '暂无' }}
|
||||
<t-descriptions-item :label="$t('tenant.business')">
|
||||
{{ tenantInfo?.business || $t('tenant.noBusiness') }}
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="状态">
|
||||
<t-tag
|
||||
:theme="getStatusTheme(tenantInfo?.status)"
|
||||
<t-descriptions-item :label="$t('tenant.status')">
|
||||
<t-tag
|
||||
:theme="getStatusTheme(tenantInfo?.status)"
|
||||
variant="light"
|
||||
>
|
||||
{{ getStatusText(tenantInfo?.status) }}
|
||||
</t-tag>
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="创建时间">
|
||||
<t-descriptions-item :label="$t('tenant.createdAt')">
|
||||
{{ formatDate(tenantInfo?.created_at) }}
|
||||
</t-descriptions-item>
|
||||
</t-descriptions>
|
||||
@@ -90,7 +90,7 @@
|
||||
<t-card class="info-card" :bordered="false">
|
||||
<template #header>
|
||||
<div class="card-header-with-actions">
|
||||
<div class="card-title">API Key</div>
|
||||
<div class="card-title">{{ $t('tenant.apiKey') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="api-key-content">
|
||||
@@ -104,7 +104,7 @@
|
||||
<template #icon>
|
||||
<t-icon name="error-circle" />
|
||||
</template>
|
||||
请妥善保管您的 API Key,不要在公共场所或代码仓库中暴露
|
||||
{{ $t('tenant.keepApiKeySafe') }}
|
||||
</t-alert>
|
||||
</div>
|
||||
</t-card>
|
||||
@@ -116,17 +116,17 @@
|
||||
v-if="tenantInfo?.storage_quota !== undefined"
|
||||
>
|
||||
<template #header>
|
||||
<div class="card-title">存储信息</div>
|
||||
<div class="card-title">{{ $t('tenant.storageInfo') }}</div>
|
||||
</template>
|
||||
<div class="storage-content">
|
||||
<t-descriptions :column="1" layout="vertical">
|
||||
<t-descriptions-item label="存储配额">
|
||||
<t-descriptions-item :label="$t('tenant.storageQuota')">
|
||||
{{ formatBytes(tenantInfo.storage_quota) }}
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="已使用">
|
||||
<t-descriptions-item :label="$t('tenant.used')">
|
||||
{{ formatBytes(tenantInfo.storage_used || 0) }}
|
||||
</t-descriptions-item>
|
||||
<t-descriptions-item label="使用率">
|
||||
<t-descriptions-item :label="$t('tenant.usage')">
|
||||
<div class="usage-info">
|
||||
<span class="usage-text">{{ getUsagePercentage() }}%</span>
|
||||
<t-progress
|
||||
@@ -144,10 +144,10 @@
|
||||
<!-- API 开发文档卡片 -->
|
||||
<t-card class="info-card" :bordered="false">
|
||||
<template #header>
|
||||
<div class="card-title">API 开发文档</div>
|
||||
<div class="card-title">{{ $t('tenant.apiDevDocs') }}</div>
|
||||
</template>
|
||||
<div class="doc-content">
|
||||
<p class="doc-description">使用您的 API Key 开始开发,查看完整的 API 文档和示例代码。</p>
|
||||
<p class="doc-description">{{ $t('tenant.useApiKey') }}</p>
|
||||
<t-space class="doc-actions">
|
||||
<t-button
|
||||
theme="primary"
|
||||
@@ -156,7 +156,7 @@
|
||||
<template #icon>
|
||||
<t-icon name="link" />
|
||||
</template>
|
||||
查看 API 文档
|
||||
{{ $t('tenant.viewApiDoc') }}
|
||||
</t-button>
|
||||
</t-space>
|
||||
|
||||
@@ -167,14 +167,14 @@
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<t-loading size="large" />
|
||||
<p class="loading-text">正在加载账户信息...</p>
|
||||
<p class="loading-text">{{ $t('tenant.loadingAccountInfo') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-if="error" class="error-container">
|
||||
<t-result theme="error" title="加载失败" :description="error">
|
||||
<t-result theme="error" :title="$t('tenant.loadFailed')" :description="error">
|
||||
<template #extra>
|
||||
<t-button theme="primary" @click="loadTenantInfo">重试</t-button>
|
||||
<t-button theme="primary" @click="loadTenantInfo">{{ $t('tenant.retry') }}</t-button>
|
||||
</template>
|
||||
</t-result>
|
||||
</div>
|
||||
@@ -183,8 +183,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getCurrentUser, type TenantInfo, type UserInfo } from '@/api/auth'
|
||||
import { getSystemInfo, type SystemInfo } from '@/api/system'
|
||||
const { t } = useI18n()
|
||||
|
||||
// 响应式数据
|
||||
const tenantInfo = ref<TenantInfo | null>(null)
|
||||
@@ -262,7 +264,7 @@ const copyApiKey = async () => {
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textArea)
|
||||
import('tdesign-vue-next').then(({ MessagePlugin }) => {
|
||||
MessagePlugin.success('API Key 已复制到剪贴板')
|
||||
MessagePlugin.success($t('tenant.apiKeyCopied'))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -274,13 +276,13 @@ const openApiDoc = () => {
|
||||
const getStatusText = (status: string | undefined) => {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return '活跃'
|
||||
return $t('tenant.statusActive')
|
||||
case 'inactive':
|
||||
return '未激活'
|
||||
return $t('tenant.statusInactive')
|
||||
case 'suspended':
|
||||
return '已暂停'
|
||||
return $t('tenant.statusSuspended')
|
||||
default:
|
||||
return '未知'
|
||||
return $t('tenant.statusUnknown')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,11 +300,11 @@ const getStatusTheme = (status: string | undefined) => {
|
||||
}
|
||||
|
||||
const formatDate = (dateStr: string | undefined) => {
|
||||
if (!dateStr) return '未知'
|
||||
|
||||
if (!dateStr) return $t('tenant.unknown')
|
||||
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
return date.toLocaleString('ru-RU', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
@@ -311,7 +313,7 @@ const formatDate = (dateStr: string | undefined) => {
|
||||
second: '2-digit'
|
||||
})
|
||||
} catch {
|
||||
return '格式错误'
|
||||
return $t('tenant.formatError')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user