feat: import Sample SQL / Terminologies

This commit is contained in:
ulleo
2025-11-28 18:40:55 +08:00
committed by ulleo
parent 69447ef343
commit 06a869f624
14 changed files with 614 additions and 371 deletions
@@ -74,8 +74,8 @@ async def export_excel(session: SessionDep, trans: Trans, current_user: CurrentU
data_list.append(_data)
fields = []
fields.append(AxisObj(name=trans('i18n_data_training.data_training'), value='question'))
fields.append(AxisObj(name=trans('i18n_data_training.problem_description'), value='description'))
fields.append(AxisObj(name=trans('i18n_data_training.problem_description'), value='question'))
fields.append(AxisObj(name=trans('i18n_data_training.sample_sql'), value='description'))
fields.append(AxisObj(name=trans('i18n_data_training.effective_data_sources'), value='datasource_name'))
if current_user.oid == 1:
fields.append(
@@ -98,6 +98,43 @@ async def export_excel(session: SessionDep, trans: Trans, current_user: CurrentU
return StreamingResponse(result, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@router.get("/template")
async def excel_template(trans: Trans, current_user: CurrentUser):
def inner():
data_list = []
_data1 = {
"question": '查询TEST表内所有ID',
"description": 'SELECT id FROM TEST',
"datasource_name": '生效数据源1',
"advanced_application_name": '生效高级应用名称',
}
data_list.append(_data1)
fields = []
fields.append(AxisObj(name=trans('i18n_data_training.problem_description_template'), value='question'))
fields.append(AxisObj(name=trans('i18n_data_training.sample_sql_template'), value='description'))
fields.append(AxisObj(name=trans('i18n_data_training.effective_data_sources_template'), value='datasource_name'))
if current_user.oid == 1:
fields.append(
AxisObj(name=trans('i18n_data_training.advanced_application_template'), value='advanced_application_name'))
md_data, _fields_list = DataFormat.convert_object_array_for_pandas(fields, data_list)
df = pd.DataFrame(md_data, columns=_fields_list)
buffer = io.BytesIO()
with pd.ExcelWriter(buffer, engine='xlsxwriter',
engine_kwargs={'options': {'strings_to_numbers': False}}) as writer:
df.to_excel(writer, sheet_name='Sheet1', index=False)
buffer.seek(0)
return io.BytesIO(buffer.getvalue())
result = await asyncio.to_thread(inner)
return StreamingResponse(result, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
path = settings.EXCEL_PATH
from sqlalchemy.orm import sessionmaker, scoped_session
@@ -98,6 +98,51 @@ async def export_excel(session: SessionDep, trans: Trans, current_user: CurrentU
return StreamingResponse(result, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@router.get("/template")
async def excel_template(trans: Trans):
def inner():
data_list = []
_data1 = {
"word": trans('i18n_terminology.term_name_template_example_1'),
"other_words": trans('i18n_terminology.synonyms_template_example_1'),
"description": trans('i18n_terminology.term_description_template_example_1'),
"all_data_sources": 'N',
"datasource": trans('i18n_terminology.effective_data_sources_template_example_1'),
}
data_list.append(_data1)
_data2 = {
"word": trans('i18n_terminology.term_name_template_example_2'),
"other_words": trans('i18n_terminology.synonyms_template_example_2'),
"description": trans('i18n_terminology.term_description_template_example_2'),
"all_data_sources": 'Y',
"datasource": '',
}
data_list.append(_data2)
fields = []
fields.append(AxisObj(name=trans('i18n_terminology.term_name_template'), value='word'))
fields.append(AxisObj(name=trans('i18n_terminology.synonyms_template'), value='other_words'))
fields.append(AxisObj(name=trans('i18n_terminology.term_description_template'), value='description'))
fields.append(AxisObj(name=trans('i18n_terminology.effective_data_sources_template'), value='datasource'))
fields.append(AxisObj(name=trans('i18n_terminology.all_data_sources_template'), value='all_data_sources'))
md_data, _fields_list = DataFormat.convert_object_array_for_pandas(fields, data_list)
df = pd.DataFrame(md_data, columns=_fields_list)
buffer = io.BytesIO()
with pd.ExcelWriter(buffer, engine='xlsxwriter',
engine_kwargs={'options': {'strings_to_numbers': False}}) as writer:
df.to_excel(writer, sheet_name='Sheet1', index=False)
buffer.seek(0)
return io.BytesIO(buffer.getvalue())
result = await asyncio.to_thread(inner)
return StreamingResponse(result, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
path = settings.EXCEL_PATH
from sqlalchemy.orm import sessionmaker, scoped_session
+28
View File
@@ -48,6 +48,18 @@
"effective_data_sources": "Effective Data Sources",
"all_data_sources": "All Data Sources",
"synonyms": "Synonyms",
"term_name_template": "Terminology Name (Required)",
"term_description_template": "Terminology Description (Required)",
"effective_data_sources_template": "Effective Data Sources (Multiple supported, separated by \",\")",
"all_data_sources_template": "All Data Sources (Y: Apply to all data sources, N: Apply to specified data sources)",
"synonyms_template": "Synonyms (Multiple supported, separated by \",\")",
"term_name_template_example_1": "Term1",
"term_description_template_example_1": "Term1 Description",
"effective_data_sources_template_example_1": "Datasource1, Datasource2",
"synonyms_template_example_1": "Synonym1, Synonym2",
"term_name_template_example_2": "Term2",
"term_description_template_example_2": "Term2 Description",
"synonyms_template_example_2": "Synonym3",
"word_cannot_be_empty": "Term cannot be empty",
"description_cannot_be_empty": "Term description cannot be empty",
"datasource_not_found": "Datasource not found"
@@ -62,6 +74,13 @@
"sample_sql": "Sample SQL",
"effective_data_sources": "Effective Data Sources",
"advanced_application": "Advanced Application",
"problem_description_template": "Problem Description (Required)",
"sample_sql_template": "Sample SQL (Required)",
"effective_data_sources_template": "Effective Data Sources",
"advanced_application_template": "Advanced Application",
"problem_description_template_example": "Query all IDs in the TEST table",
"effective_data_sources_template_example": "Effective Datasource 1",
"advanced_application_template_example": "Effective Advanced Application Name",
"error_info": "Error Information",
"question_cannot_be_empty": "Question cannot be empty",
"description_cannot_be_empty": "Sample SQL cannot be empty",
@@ -75,6 +94,15 @@
"prompt_word_content": "Prompt word content",
"effective_data_sources": "Effective Data Sources",
"all_data_sources": "All Data Sources",
"prompt_word_name_template": "Prompt Name (Required)",
"prompt_word_content_template": "Prompt Content (Required)",
"effective_data_sources_template": "Effective Data Sources (Multiple supported, separated by \",\")",
"all_data_sources_template": "All Data Sources (Y: Apply to all data sources, N: Apply to specified data sources)",
"prompt_word_name_template_example1": "Prompt1",
"prompt_word_content_template_example1": "Describe your prompt in detail",
"effective_data_sources_template_example1": "Datasource1, Datasource2",
"prompt_word_name_template_example2": "Prompt2",
"prompt_word_content_template_example2": "Describe your prompt in detail",
"name_cannot_be_empty": "Name cannot be empty",
"prompt_cannot_be_empty": "Prompt content cannot be empty",
"type_cannot_be_empty": "Type cannot be empty",
+28
View File
@@ -48,6 +48,18 @@
"effective_data_sources": "유효 데이터 소스",
"all_data_sources": "모든 데이터 소스",
"synonyms": "동의어",
"term_name_template": "용어 이름 (필수)",
"term_description_template": "용어 설명 (필수)",
"effective_data_sources_template": "유효 데이터 소스 (여러 개 지원, \",\"로 구분)",
"all_data_sources_template": "모든 데이터 소스 (Y: 모든 데이터 소스에 적용, N: 지정된 데이터 소스에 적용)",
"synonyms_template": "동의어 (여러 개 지원, \",\"로 구분)",
"term_name_template_example_1": "용어1",
"term_description_template_example_1": "용어1 설명",
"effective_data_sources_template_example_1": "데이터소스1, 데이터소스2",
"synonyms_template_example_1": "동의어1, 동의어2",
"term_name_template_example_2": "용어2",
"term_description_template_example_2": "용어2 설명",
"synonyms_template_example_2": "동의어3",
"word_cannot_be_empty": "용어는 비울 수 없습니다",
"description_cannot_be_empty": "용어 설명은 비울 수 없습니다",
"datasource_not_found": "데이터 소스를 찾을 수 없음"
@@ -62,6 +74,13 @@
"sample_sql": "예시 SQL",
"effective_data_sources": "유효 데이터 소스",
"advanced_application": "고급 애플리케이션",
"problem_description_template": "문제 설명 (필수)",
"sample_sql_template": "예시 SQL (필수)",
"effective_data_sources_template": "유효 데이터 소스",
"advanced_application_template": "고급 애플리케이션",
"problem_description_template_example": "TEST 테이블 내 모든 ID 조회",
"effective_data_sources_template_example": "유효 데이터소스1",
"advanced_application_template_example": "유효 고급 애플리케이션 이름",
"error_info": "오류 정보",
"question_cannot_be_empty": "질문은 비울 수 없습니다",
"description_cannot_be_empty": "예시 SQL은 비울 수 없습니다",
@@ -75,6 +94,15 @@
"prompt_word_content": "프롬프트 내용",
"effective_data_sources": "유효 데이터 소스",
"all_data_sources": "모든 데이터 소스",
"prompt_word_name_template": "프롬프트 이름 (필수)",
"prompt_word_content_template": "프롬프트 내용 (필수)",
"effective_data_sources_template": "유효 데이터 소스 (여러 개 지원, \",\"로 구분)",
"all_data_sources_template": "모든 데이터 소스 (Y: 모든 데이터 소스에 적용, N: 지정된 데이터 소스에 적용)",
"prompt_word_name_template_example1": "프롬프트1",
"prompt_word_content_template_example1": "프롬프트를 상세히 설명해 주세요",
"effective_data_sources_template_example1": "데이터소스1, 데이터소스2",
"prompt_word_name_template_example2": "프롬프트2",
"prompt_word_content_template_example2": "프롬프트를 상세히 설명해 주세요",
"name_cannot_be_empty": "이름은 비울 수 없습니다",
"prompt_cannot_be_empty": "프롬프트 내용은 비울 수 없습니다",
"type_cannot_be_empty": "유형은 비울 수 없습니다",
+28
View File
@@ -48,6 +48,18 @@
"effective_data_sources": "生效数据源",
"all_data_sources": "所有数据源",
"synonyms": "同义词",
"term_name_template": "术语名称(必填)",
"term_description_template": "术语描述(必填)",
"effective_data_sources_template": "生效数据源(支持多个,用\",\"分割)",
"all_data_sources_template": "所有数据源(Y:应用到全部数据源,N:应用到指定数据源)",
"synonyms_template": "同义词(支持多个,用\",\"分割)",
"term_name_template_example_1": "术语1",
"term_description_template_example_1": "术语1描述",
"effective_data_sources_template_example_1": "生效数据源1, 生效数据源2",
"synonyms_template_example_1": "同义词1, 同义词2",
"term_name_template_example_2": "术语2",
"term_description_template_example_2": "术语2描述",
"synonyms_template_example_2": "同义词3",
"word_cannot_be_empty": "术语不能为空",
"description_cannot_be_empty": "术语描述不能为空",
"datasource_not_found": "找不到数据源"
@@ -62,6 +74,13 @@
"sample_sql": "示例 SQL",
"effective_data_sources": "生效数据源",
"advanced_application": "高级应用",
"problem_description_template": "问题描述(必填)",
"sample_sql_template": "示例 SQL(必填)",
"effective_data_sources_template": "生效数据源",
"advanced_application_template": "高级应用",
"problem_description_template_example": "查询TEST表内所有ID",
"effective_data_sources_template_example": "生效数据源1",
"advanced_application_template_example": "生效高级应用名称",
"error_info": "错误信息",
"question_cannot_be_empty": "问题不能为空",
"description_cannot_be_empty": "示例 SQL 不能为空",
@@ -75,6 +94,15 @@
"prompt_word_content": "提示词内容",
"effective_data_sources": "生效数据源",
"all_data_sources": "所有数据源",
"prompt_word_name_template": "提示词名称(必填)",
"prompt_word_content_template": "提示词内容(必填)",
"effective_data_sources_template": "生效数据源(支持多个,用\",\"分割)",
"all_data_sources_template": "所有数据源(Y:应用到全部数据源,N:应用到指定数据源)",
"prompt_word_name_template_example1": "提示词1",
"prompt_word_content_template_example1": "详细描述你的提示词",
"effective_data_sources_template_example1": "生效数据源1, 生效数据源2",
"prompt_word_name_template_example2": "提示词2",
"prompt_word_content_template_example2": "详细描述你的提示词",
"name_cannot_be_empty": "名称不能为空",
"prompt_cannot_be_empty": "提示词内容不能为空",
"type_cannot_be_empty": "类型不能为空",
+1 -1
View File
@@ -39,7 +39,7 @@ dependencies = [
"pyyaml (>=6.0.2,<7.0.0)",
"fastapi-mcp (>=0.3.4,<0.4.0)",
"tabulate>=0.9.0",
"sqlbot-xpack>=0.0.3.47,<1.0.0",
"sqlbot-xpack>=0.0.3.53,<1.0.0",
"fastapi-cache2>=0.2.2",
"sqlparse>=0.5.3",
"redis>=6.2.0",
+6
View File
@@ -17,4 +17,10 @@ export const settingsApi = {
requestOptions: { customError: true },
}
),
downloadTemplate: (url: any) =>
request.get(url, {
responseType: 'blob',
requestOptions: { customError: true },
}),
}
+6
View File
@@ -117,6 +117,12 @@
"english": "English",
"re_upload": "Re-upload",
"not_exceed_50mb": "Supports XLS, XLSX, CSV formats, file size does not exceed 50MB",
"excel_file_type_limit": "Only XLS and XLSX formats are supported",
"click_to_select_file": "Click to select file",
"upload_hint_first": "Please ",
"upload_hint_download_template": "download the template",
"upload_hint_end": " first, then upload after filling it out as required",
"continue_to_upload": "Continue to import",
"reset_password": "Reset password",
"password_reset_successful": "Password reset successful",
"or": "Or",
+6
View File
@@ -117,6 +117,12 @@
"english": "English",
"re_upload": "다시 업로드",
"not_exceed_50mb": "XLS, XLSX, CSV 형식을 지원하며, 파일 크기는 50MB를 초과할 수 없습니다",
"excel_file_type_limit": "XLS 및 XLSX 형식만 지원됩니다",
"click_to_select_file": "파일 선택을 클릭하세요",
"upload_hint_first": "먼저 ",
"upload_hint_download_template": "템플릿을 다운로드",
"upload_hint_end": ", 한 후 요구사항에 따라 작성하여 업로드하세요",
"continue_to_upload": "계속 가져오기",
"reset_password": "비밀번호 재설정",
"password_reset_successful": "비밀번호 재설정 성공",
"or": "또는",
+6
View File
@@ -117,6 +117,12 @@
"english": "English",
"re_upload": "重新上传",
"not_exceed_50mb": "支持 XLS、XLSX、CSV 格式,文件大小不超过 50MB",
"excel_file_type_limit": "仅支持 XLS、XLSX 格式",
"click_to_select_file": "点击选择文件",
"upload_hint_first": "先",
"upload_hint_download_template": "下载模板",
"upload_hint_end": ",按要求填写后上传",
"continue_to_upload": "继续导入",
"reset_password": "重置密码",
"password_reset_successful": "重置密码成功",
"or": "或者",
@@ -0,0 +1,372 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { endsWith, startsWith } from 'lodash-es'
import { useI18n } from 'vue-i18n'
import icon_warning_filled from '@/assets/svg/icon_warning_filled.svg'
import icon_upload_outlined from '@/assets/svg/icon_upload_outlined.svg'
import icon_fileExcel_colorful from '@/assets/datasource/icon_excel.png'
import { genFileId, type UploadInstance, type UploadProps, type UploadRawFile } from 'element-plus'
import { useCache } from '@/utils/useCache.ts'
import { settingsApi } from '@/api/setting.ts'
import ccmUpload from '@/assets/svg/icon_ccm-upload_outlined.svg'
const { t } = useI18n()
const { wsCache } = useCache()
const emits = defineEmits(['upload-finished'])
const props = defineProps<{
uploadPath: string
templatePath: string
templateName: string
}>()
const uploadRef = ref<UploadInstance>()
const uploadLoading = ref(false)
const token = wsCache.get('user.token')
const headers = ref<any>({ 'X-SQLBOT-TOKEN': `Bearer ${token}` })
const getUploadURL = () => {
return import.meta.env.VITE_API_BASE_URL + props.uploadPath
}
function downloadTemplate() {
settingsApi
.downloadTemplate(props.templatePath)
.then((res) => {
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
})
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = props.templateName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
.catch(async (error) => {
if (error.response) {
try {
let text = await error.response.data.text()
try {
text = JSON.parse(text)
} finally {
ElMessage({
message: text,
type: 'error',
showClose: true,
})
}
} catch (e) {
console.error('Error processing error response:', e)
}
} else {
console.error('Other error:', error)
ElMessage({
message: error,
type: 'error',
showClose: true,
})
}
})
}
const handleExceed: UploadProps['onExceed'] = (files) => {
uploadRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
uploadRef.value!.handleStart(file)
}
const fileList = ref<Array<any>>([])
const fileName = computed(() => {
if (fileList.value.length > 0) {
return fileList.value[0].name
}
return undefined
})
const beforeUpload = (rawFile: any) => {
if (rawFile.size / 1024 / 1024 > 50) {
ElMessage.error(t('common.not_exceed_50mb'))
return false
}
uploadLoading.value = true
return true
}
const errorFileName = ref<string>('')
function downloadErrorFile() {
settingsApi
.downloadError(errorFileName.value)
.then((res) => {
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
})
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = errorFileName.value
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
.catch(async (error) => {
if (error.response) {
try {
let text = await error.response.data.text()
try {
text = JSON.parse(text)
} finally {
ElMessage({
message: text,
type: 'error',
showClose: true,
})
}
} catch (e) {
console.error('Error processing error response:', e)
}
} else {
console.error('Other error:', error)
ElMessage({
message: error,
type: 'error',
showClose: true,
})
}
})
}
const infoMessage = ref<string>('')
const hasError = ref(false)
const onSuccess = (response: any) => {
uploadRef.value!.clearFiles()
fileList.value = []
hasError.value = false
infoMessage.value = ''
emits('upload-finished')
if (response?.data?.failed_count > 0 && response?.data?.error_excel_filename) {
infoMessage.value = t('training.upload_failed', {
success: response.data.success_count,
fail: response.data.failed_count,
fail_info: '',
})
errorFileName.value = response.data.error_excel_filename
hasError.value = true
} else {
infoMessage.value = t('training.upload_success')
hasError.value = false
}
uploadLoading.value = false
close()
showResult()
}
const onError = (err: Error) => {
uploadLoading.value = false
uploadRef.value!.clearFiles()
fileList.value = []
let msg = err.message
if (startsWith(msg, '"') && endsWith(msg, '"')) {
msg = msg.slice(1, msg.length - 1)
}
errorFileName.value = ''
infoMessage.value = msg
hasError.value = true
close()
showResult()
}
const dialogShow = ref<boolean>(false)
const resultShow = ref(false)
function showResult() {
resultShow.value = true
}
function closeResult() {
resultShow.value = false
}
function backToUpload() {
closeResult()
open()
}
function open() {
dialogShow.value = true
errorFileName.value = ''
fileList.value = []
hasError.value = false
infoMessage.value = ''
}
function close() {
uploadRef.value!.clearFiles()
fileList.value = []
dialogShow.value = false
}
const submitUpload = () => {
uploadRef.value!.submit()
}
</script>
<template>
<el-button class="no-margin" secondary @click="open">
<template #icon>
<ccmUpload></ccmUpload>
</template>
{{ $t('user.batch_import') }}
</el-button>
<el-dialog
v-if="dialogShow"
v-model="dialogShow"
:title="t('user.batch_import')"
width="600px"
class="user-import-class"
@close="close"
>
<div class="import-container">
<div class="down-template">
<span class="icon-span">
<el-icon>
<Icon name="icon_warning_filled"><icon_warning_filled class="svg-icon" /></Icon>
</el-icon>
</span>
<div class="down-template-content">
<span>{{ t('common.upload_hint_first') }}</span>
<el-button type="primary" link class="down-button" @click="downloadTemplate">
{{ t('common.upload_hint_download_template') }}
</el-button>
<span>{{ t('common.upload_hint_end') }}</span>
</div>
</div>
<div style="width: 100%">
<el-upload
ref="uploadRef"
v-model:file-list="fileList"
style="width: 100%"
:multiple="false"
accept=".xlsx,.xls"
:action="getUploadURL()"
:before-upload="beforeUpload"
:headers="headers"
:on-success="onSuccess"
:on-error="onError"
:show-file-list="false"
:auto-upload="false"
:limit="1"
:on-exceed="handleExceed"
>
<el-input
v-model="fileName"
style="width: 100%"
:placeholder="t('common.click_to_select_file')"
readonly
>
<template #suffix>
<el-icon>
<Icon name="icon_upload_outlined">
<icon_upload_outlined class="svg-icon" />
</Icon>
</el-icon>
</template>
<template #prefix>
<img v-if="!!fileName" :src="icon_fileExcel_colorful" width="16px" height="16px" />
</template>
</el-input>
</el-upload>
<div>
<el-link class="font12" type="info" disabled>
{{ t('common.excel_file_type_limit') }}
</el-link>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="close">{{ t('common.cancel') }}</el-button>
<el-button
:type="fileName ? 'primary' : 'info'"
:disabled="!fileName"
@click="submitUpload"
>
{{ t('common.continue_to_upload') }}
</el-button>
</span>
</template>
</el-dialog>
<el-dialog
v-if="resultShow"
v-model="resultShow"
:title="hasError ? t('user.data_import_failed') : t('user.data_import_completed')"
width="600px"
class="user-import-class"
@close="closeResult"
>
<div class="down-template-content">
{{ infoMessage }}
<el-button
v-if="hasError && errorFileName.length > 0"
type="primary"
link
class="down-button"
@click="downloadErrorFile"
>
{{ errorFileName }}
</el-button>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="closeResult">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="backToUpload">
{{ t('common.continue_to_upload') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<style lang="less">
.down-template-content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
line-height: 24px;
height: 24px;
.down-button {
padding: 0;
height: unset !important;
min-width: unset;
}
}
</style>
<style scoped lang="less">
.no-margin {
margin: 0;
}
.import-container {
display: flex;
flex-direction: column;
gap: 16px;
:deep(.ed-upload) {
width: 100%;
}
.down-template {
display: flex;
flex-direction: row;
align-items: center;
}
.font12 {
font-size: 12px;
}
}
</style>
+13 -123
View File
@@ -4,20 +4,17 @@ import icon_export_outlined from '@/assets/svg/icon_export_outlined.svg'
import { professionalApi } from '@/api/professional'
import { formatTimestamp } from '@/utils/date'
import { datasourceApi } from '@/api/datasource'
import ccmUpload from '@/assets/svg/icon_ccm-upload_outlined.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import IconOpeEdit from '@/assets/svg/icon_edit_outlined.svg'
import IconOpeDelete from '@/assets/svg/icon_delete.svg'
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
import { useI18n } from 'vue-i18n'
import { cloneDeep, endsWith, startsWith } from 'lodash-es'
import { genFileId, type UploadInstance, type UploadProps, type UploadRawFile } from 'element-plus'
import { useCache } from '@/utils/useCache.ts'
import { settingsApi } from '@/api/setting.ts'
import { cloneDeep } from 'lodash-es'
import { convertFilterText, FilterText } from '@/components/filter-text'
import { DrawerMain } from '@/components/drawer-main'
import iconFilter from '@/assets/svg/icon-filter_outlined.svg'
import Uploader from '@/views/system/excel-upload/Uploader.vue'
interface Form {
id?: string | null
@@ -30,7 +27,6 @@ interface Form {
}
const { t } = useI18n()
const { wsCache } = useCache()
const multipleSelectionAll = ref<any[]>([])
const allDsList = ref<any[]>([])
const keywords = ref('')
@@ -83,101 +79,6 @@ const cancelDelete = () => {
isIndeterminate.value = false
}
const uploadRef = ref<UploadInstance>()
const uploadLoading = ref(false)
const token = wsCache.get('user.token')
const headers = ref<any>({ 'X-SQLBOT-TOKEN': `Bearer ${token}` })
const getUploadURL = import.meta.env.VITE_API_BASE_URL + '/system/terminology/uploadExcel'
const handleExceed: UploadProps['onExceed'] = (files) => {
uploadRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
uploadRef.value!.handleStart(file)
}
const beforeUpload = (rawFile: any) => {
if (rawFile.size / 1024 / 1024 > 50) {
ElMessage.error(t('common.not_exceed_50mb'))
return false
}
uploadLoading.value = true
return true
}
const onSuccess = (response: any) => {
uploadRef.value!.clearFiles()
search()
if (response?.data?.failed_count > 0 && response?.data?.error_excel_filename) {
ElMessage.error(
t('training.upload_failed', {
success: response.data.success_count,
fail: response.data.failed_count,
fail_info: response.data.error_excel_filename,
})
)
settingsApi
.downloadError(response.data.error_excel_filename)
.then((res) => {
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
})
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = response.data.error_excel_filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
.catch(async (error) => {
if (error.response) {
try {
let text = await error.response.data.text()
try {
text = JSON.parse(text)
} finally {
ElMessage({
message: text,
type: 'error',
showClose: true,
})
}
} catch (e) {
console.error('Error processing error response:', e)
}
} else {
console.error('Other error:', error)
ElMessage({
message: error,
type: 'error',
showClose: true,
})
}
})
.finally(() => {
uploadLoading.value = false
})
} else {
ElMessage.success(t('training.upload_success'))
uploadLoading.value = false
}
}
const onError = (err: Error) => {
uploadLoading.value = false
uploadRef.value!.clearFiles()
let msg = err.message
if (startsWith(msg, '"') && endsWith(msg, '"')) {
msg = msg.slice(1, msg.length - 1)
}
ElMessage({
message: msg,
type: 'error',
showClose: true,
})
}
const exportExcel = () => {
ElMessageBox.confirm(t('professional.export_hint', { msg: pageInfo.total }), {
confirmButtonType: 'primary',
@@ -543,33 +444,19 @@ const changeStatus = (id: any, val: any) => {
</template>
{{ $t('professional.export_all') }}
</el-button>
<el-upload
ref="uploadRef"
:multiple="false"
accept=".xlsx,.xls"
:action="getUploadURL"
:before-upload="beforeUpload"
:headers="headers"
:on-success="onSuccess"
:on-error="onError"
:show-file-list="false"
:limit="1"
:on-exceed="handleExceed"
>
<el-button secondary>
<template #icon>
<ccmUpload></ccmUpload>
</template>
{{ $t('user.batch_import') }}
</el-button>
</el-upload>
<el-button secondary @click="drawerMainOpen">
<Uploader
upload-path="/system/terminology/uploadExcel"
template-path="/system/terminology/template"
:template-name="`${t('professional.professional_terminology')}.xlsx`"
@upload-finished="search"
/>
<el-button class="no-margin" secondary @click="drawerMainOpen">
<template #icon>
<iconFilter></iconFilter>
</template>
{{ $t('user.filter') }}
</el-button>
<el-button type="primary" @click="editHandler(null)">
<el-button class="no-margin" type="primary" @click="editHandler(null)">
<template #icon>
<icon_add_outlined></icon_add_outlined>
</template>
@@ -878,6 +765,9 @@ const changeStatus = (id: any, val: any) => {
</template>
<style lang="less" scoped>
.no-margin {
margin: 0;
}
.professional {
height: 100%;
position: relative;
+23 -120
View File
@@ -4,7 +4,6 @@ import icon_export_outlined from '@/assets/svg/icon_export_outlined.svg'
import { promptApi } from '@/api/prompt'
import { formatTimestamp } from '@/utils/date'
import { datasourceApi } from '@/api/datasource'
import ccmUpload from '@/assets/svg/icon_ccm-upload_outlined.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import IconOpeEdit from '@/assets/svg/icon_edit_outlined.svg'
import icon_copy_outlined from '@/assets/embedded/icon_copy_outlined.svg'
@@ -13,13 +12,12 @@ import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlin
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
import { useClipboard } from '@vueuse/core'
import { useI18n } from 'vue-i18n'
import { cloneDeep, endsWith, startsWith } from 'lodash-es'
import { genFileId, type UploadInstance, type UploadProps, type UploadRawFile } from 'element-plus'
import { settingsApi } from '@/api/setting.ts'
import { useCache } from '@/utils/useCache.ts'
import { cloneDeep } from 'lodash-es'
import { convertFilterText, FilterText } from '@/components/filter-text'
import { DrawerMain } from '@/components/drawer-main'
import iconFilter from '@/assets/svg/icon-filter_outlined.svg'
import Uploader from '@/views/system/excel-upload/Uploader.vue'
interface Form {
id?: string | null
type: string | null
@@ -94,102 +92,18 @@ const cancelDelete = () => {
isIndeterminate.value = false
}
const uploadRef = ref<UploadInstance>()
const uploadLoading = ref(false)
const { wsCache } = useCache()
const token = wsCache.get('user.token')
const headers = ref<any>({ 'X-SQLBOT-TOKEN': `Bearer ${token}` })
const getUploadURL = (type: string) => {
return import.meta.env.VITE_API_BASE_URL + `/system/custom_prompt/${type}/uploadExcel`
}
const handleExceed: UploadProps['onExceed'] = (files) => {
uploadRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
uploadRef.value!.handleStart(file)
}
const beforeUpload = (rawFile: any) => {
if (rawFile.size / 1024 / 1024 > 50) {
ElMessage.error(t('common.not_exceed_50mb'))
return false
const getFileName = () => {
let title = ''
if (currentType.value === 'GENERATE_SQL') {
title = t('prompt.ask_sql')
}
uploadLoading.value = true
return true
}
const onSuccess = (response: any) => {
uploadRef.value!.clearFiles()
search()
if (response?.data?.failed_count > 0 && response?.data?.error_excel_filename) {
ElMessage.error(
t('training.upload_failed', {
success: response.data.success_count,
fail: response.data.failed_count,
fail_info: response.data.error_excel_filename,
})
)
settingsApi
.downloadError(response.data.error_excel_filename)
.then((res) => {
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
})
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = response.data.error_excel_filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
.catch(async (error) => {
if (error.response) {
try {
let text = await error.response.data.text()
try {
text = JSON.parse(text)
} finally {
ElMessage({
message: text,
type: 'error',
showClose: true,
})
}
} catch (e) {
console.error('Error processing error response:', e)
}
} else {
console.error('Other error:', error)
ElMessage({
message: error,
type: 'error',
showClose: true,
})
}
})
.finally(() => {
uploadLoading.value = false
})
} else {
ElMessage.success(t('training.upload_success'))
uploadLoading.value = false
if (currentType.value === 'ANALYSIS') {
title = t('prompt.data_analysis')
}
}
const onError = (err: Error) => {
uploadLoading.value = false
uploadRef.value!.clearFiles()
let msg = err.message
if (startsWith(msg, '"') && endsWith(msg, '"')) {
msg = msg.slice(1, msg.length - 1)
if (currentType.value === 'PREDICT_DATA') {
title = t('prompt.data_prediction')
}
ElMessage({
message: msg,
type: 'error',
showClose: true,
})
return `${title}.xlsx`
}
const exportExcel = () => {
@@ -566,33 +480,19 @@ const drawerMainClose = () => {
</template>
{{ $t('professional.export_all') }}
</el-button>
<el-upload
ref="uploadRef"
:multiple="false"
accept=".xlsx,.xls"
:action="getUploadURL(currentType)"
:before-upload="beforeUpload"
:headers="headers"
:on-success="onSuccess"
:on-error="onError"
:show-file-list="false"
:limit="1"
:on-exceed="handleExceed"
>
<el-button secondary>
<template #icon>
<ccmUpload></ccmUpload>
</template>
{{ $t('user.batch_import') }}
</el-button>
</el-upload>
<el-button secondary @click="drawerMainOpen">
<Uploader
:upload-path="`/system/custom_prompt/${currentType}/uploadExcel`"
:template-path="`/system/custom_prompt/template`"
:template-name="getFileName()"
@upload-finished="search"
/>
<el-button class="no-margin" secondary @click="drawerMainOpen">
<template #icon>
<iconFilter></iconFilter>
</template>
{{ $t('user.filter') }}
</el-button>
<el-button type="primary" @click="editHandler(null)">
<el-button class="no-margin" type="primary" @click="editHandler(null)">
<template #icon>
<icon_add_outlined></icon_add_outlined>
</template>
@@ -851,6 +751,9 @@ const drawerMainClose = () => {
</template>
<style lang="less" scoped>
.no-margin {
margin: 0;
}
.prompt {
height: 100%;
position: relative;
+13 -125
View File
@@ -2,10 +2,8 @@
import { computed, nextTick, onMounted, reactive, ref, unref } from 'vue'
import icon_export_outlined from '@/assets/svg/icon_export_outlined.svg'
import { trainingApi } from '@/api/training'
import { settingsApi } from '@/api/setting.ts'
import { formatTimestamp } from '@/utils/date'
import { datasourceApi } from '@/api/datasource'
import ccmUpload from '@/assets/svg/icon_ccm-upload_outlined.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import IconOpeEdit from '@/assets/svg/icon_edit_outlined.svg'
import icon_copy_outlined from '@/assets/embedded/icon_copy_outlined.svg'
@@ -15,12 +13,9 @@ import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
import { useClipboard } from '@vueuse/core'
import { useUserStore } from '@/stores/user'
import { useI18n } from 'vue-i18n'
import { cloneDeep, endsWith, startsWith } from 'lodash-es'
import { cloneDeep } from 'lodash-es'
import { getAdvancedApplicationList } from '@/api/embedded.ts'
import { genFileId } from 'element-plus'
import type { UploadInstance, UploadProps, UploadRawFile } from 'element-plus'
import { useCache } from '@/utils/useCache.ts'
import Uploader from '@/views/system/excel-upload/Uploader.vue'
interface Form {
id?: string | null
@@ -38,7 +33,6 @@ const keywords = ref('')
const oldKeywords = ref('')
const searchLoading = ref(false)
const { copy } = useClipboard({ legacy: true })
const { wsCache } = useCache()
const options = ref<any[]>([])
const adv_options = ref<any[]>([])
const selectable = () => {
@@ -91,101 +85,6 @@ const cancelDelete = () => {
isIndeterminate.value = false
}
const uploadRef = ref<UploadInstance>()
const uploadLoading = ref(false)
const token = wsCache.get('user.token')
const headers = ref<any>({ 'X-SQLBOT-TOKEN': `Bearer ${token}` })
const getUploadURL = import.meta.env.VITE_API_BASE_URL + '/system/data-training/uploadExcel'
const handleExceed: UploadProps['onExceed'] = (files) => {
uploadRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
uploadRef.value!.handleStart(file)
}
const beforeUpload = (rawFile: any) => {
if (rawFile.size / 1024 / 1024 > 50) {
ElMessage.error(t('common.not_exceed_50mb'))
return false
}
uploadLoading.value = true
return true
}
const onSuccess = (response: any) => {
uploadRef.value!.clearFiles()
search()
if (response?.data?.failed_count > 0 && response?.data?.error_excel_filename) {
ElMessage.error(
t('training.upload_failed', {
success: response.data.success_count,
fail: response.data.failed_count,
fail_info: response.data.error_excel_filename,
})
)
settingsApi
.downloadError(response.data.error_excel_filename)
.then((res) => {
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
})
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = response.data.error_excel_filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
.catch(async (error) => {
if (error.response) {
try {
let text = await error.response.data.text()
try {
text = JSON.parse(text)
} finally {
ElMessage({
message: text,
type: 'error',
showClose: true,
})
}
} catch (e) {
console.error('Error processing error response:', e)
}
} else {
console.error('Other error:', error)
ElMessage({
message: error,
type: 'error',
showClose: true,
})
}
})
.finally(() => {
uploadLoading.value = false
})
} else {
ElMessage.success(t('training.upload_success'))
uploadLoading.value = false
}
}
const onError = (err: Error) => {
uploadLoading.value = false
uploadRef.value!.clearFiles()
let msg = err.message
if (startsWith(msg, '"') && endsWith(msg, '"')) {
msg = msg.slice(1, msg.length - 1)
}
ElMessage({
message: msg,
type: 'error',
showClose: true,
})
}
const exportExcel = () => {
ElMessageBox.confirm(t('training.export_hint', { msg: pageInfo.total }), {
confirmButtonType: 'primary',
@@ -460,7 +359,7 @@ const onRowFormClose = () => {
</script>
<template>
<div v-loading="searchLoading || uploadLoading" class="training">
<div v-loading="searchLoading" class="training">
<div class="tool-left">
<span class="page-title">{{ $t('training.data_training') }}</span>
<div class="tool-row">
@@ -483,27 +382,13 @@ const onRowFormClose = () => {
</template>
{{ $t('professional.export_all') }}
</el-button>
<el-upload
ref="uploadRef"
:multiple="false"
accept=".xlsx,.xls"
:action="getUploadURL"
:before-upload="beforeUpload"
:headers="headers"
:on-success="onSuccess"
:on-error="onError"
:show-file-list="false"
:limit="1"
:on-exceed="handleExceed"
>
<el-button secondary>
<template #icon>
<ccmUpload></ccmUpload>
</template>
{{ $t('user.batch_import') }}
</el-button>
</el-upload>
<el-button type="primary" @click="editHandler(null)">
<Uploader
upload-path="/system/data-training/uploadExcel"
template-path="/system/data-training/template"
:template-name="`${t('training.data_training')}.xlsx`"
@upload-finished="search"
/>
<el-button class="no-margin" type="primary" @click="editHandler(null)">
<template #icon>
<icon_add_outlined></icon_add_outlined>
</template>
@@ -775,6 +660,9 @@ const onRowFormClose = () => {
</template>
<style lang="less" scoped>
.no-margin {
margin: 0;
}
.training {
height: 100%;
position: relative;