Commit 59e9ce00 authored by nick zheng's avatar nick zheng

feat: 数据库配置&&应用

parent 7643dadc
......@@ -23,6 +23,7 @@ export default [
ViteEnv: 'readonly',
AnyObject: 'readonly',
KnowledgeContentResultItem: 'readonly',
DBChainResultItem: 'readonly',
ConversationMessageItem: 'readonly',
ConversationMessageItemInfo: 'readonly',
MittEvents: 'readonly',
......
......@@ -58,3 +58,11 @@ export function fetchGetDataBaseDetail<T>(id: string) {
export function fetchGetDBTableList<T>(id: string) {
return request.post<T>(`/databaseRest/getTableInfo.json?id=${id}`)
}
/**
* @query ids 数据库Id列表
* @returns 通过Id列表获取数据库详情
*/
export function fetchGetDataListByIds<T>(ids: number[]) {
return request.post<T>(`/databaseRest/getByIds.json?ids=${ids}`)
}
......@@ -10,6 +10,7 @@ interface ResponseData {
reasoningContent: string
function: { name: string }
knowledgeContentResult: KnowledgeContentResultItem[]
dbChainResult: DBChainResultItem[]
}
const { t } = i18n.global
......
......@@ -155,6 +155,12 @@ common_module:
cancel_authorization: 'Cancel authorization'
get_code: 'Get Code'
database: 'Database'
no_database_table: 'No database table'
view_database_table: 'View database table'
total_database_table: '1 database table in total | {count} database tables in total'
add_database_successfully: 'Database set {0} was added successfully'
remove_database_successfully: 'Database set {0} was removed successfully'
database_QA_executed_successfully: 'Database Q&A executed successfully'
dialogue_module:
continue_question_message: 'You can keep asking questions'
......@@ -377,6 +383,8 @@ personal_space_module:
back_chunk_content: 'Back chunk content'
from_knowledge_base: 'From knowledge base'
score: 'Score'
only_one_database_can_be_added: 'Only one database can be added'
associated_database_desc: 'You can upload local spreadsheet data or connect to a business database to build your dataset. When users ask numerical questions, the application can query, compute, and analyze the data to provide answers. Note: The app supports association with only 1 database at most'
upload_file: 'Upload file'
upload_file_desc: 'Enable the user to upload files for chat, support TXT, MD, PDF, DOC, DOCX format files'
dialogue: 'Dialogue'
......@@ -646,6 +654,7 @@ personal_space_module:
connect_test_fail: 'Database connection failed. Please verify your input'
connect_test_must_pass_to_create: 'Connect test must pass to create'
connect_test_must_pass_to_update: 'Connect test must pass to update'
add_database: 'Add database'
share_agent_module:
please: 'Please first'
......
......@@ -154,6 +154,12 @@ common_module:
cancel_authorization: '取消授权'
get_code: '获取验证码'
database: '数据库'
no_database_table: '暂无数据库表'
view_database_table: '查看数据库表'
total_database_table: '共{count}个数据库表'
add_database_successfully: '数据库 {0} 添加成功'
remove_database_successfully: '数据库 {0} 移除成功'
database_QA_executed_successfully: '数据库问答执行成功'
dialogue_module:
continue_question_message: '你可以继续提问'
......@@ -375,6 +381,8 @@ personal_space_module:
back_chunk_content: '返回切片内容'
from_knowledge_base: '所属知识库'
score: '匹配分'
only_one_database_can_be_added: '仅支持添加一个数据库'
associated_database_desc: '可上传本地表格数据或连接业务数据库构建数据库。用户询问数值类问题时,应用能够查询、计算和分析数据并答复。应用最多可关联1个数据库'
upload_file: '上传文件'
upload_file_desc: '开启后支持用户上传文件进行对话聊天, 支持TXT、MD、PDF、DOC、DOCX格式的文件'
dialogue: '对话'
......@@ -644,6 +652,7 @@ personal_space_module:
connect_test_fail: '无法联通数据库,请检查填写内容'
connect_test_must_pass_to_create: '测试通过才能创建'
connect_test_must_pass_to_update: '测试通过才能更新'
add_database: '添加数据库'
share_agent_module:
please: '请先'
......
......@@ -154,6 +154,12 @@ common_module:
cancel_authorization: '取消授權'
get_code: '獲取驗証碼'
database: '數據庫'
no_database_table: '暫無數據庫表'
view_database_table: '查看數據庫表'
total_database_table: '共{count}個數據庫表'
add_database_successfully: '數據庫 {0} 添加成功'
remove_database_successfully: '數據庫 {0} 移除成功'
database_QA_executed_successfully: '數據庫問答執行成功'
dialogue_module:
continue_question_message: '你可以繼續提問'
......@@ -375,6 +381,8 @@ personal_space_module:
back_chunk_content: '返回切片內容'
from_knowledge_base: '所屬知識庫'
score: '匹配分'
only_one_database_can_be_added: '僅支持添加一個數據庫'
associated_database_desc: '可上傳本地表格數據或連接業務數據庫構建數據庫。用户詢問數值類問題時,應用能夠查詢、計算和分析數據並答覆。應用最多可關聯1個數據庫'
upload_file: '上傳文件'
upload_file_desc: '開啓後支持用户上傳文件進行對話聊天, 支持TXT、MD、PDF、DOC、DOCX格式的文件'
dialogue: '對話'
......@@ -644,6 +652,7 @@ personal_space_module:
connect_test_fail: '無法聯通數據庫,請檢查填寫內容'
connect_test_must_pass_to_create: '測試通過才能創建'
connect_test_must_pass_to_update: '測試通過才能更新'
add_database: '添加數據庫'
share_agent_module:
please: '請先'
......
......@@ -34,6 +34,9 @@ export function defaultPersonalAppConfigState(): PersonalAppConfigState {
knowledgeSimilarity: 0.4,
knowledgeNResult: 3,
},
databaseConfig: {
ids: [],
},
commModelConfig: {
largeModel: '文心4.0 (8K)',
topP: 0.7,
......
......@@ -35,6 +35,9 @@ export interface PersonalAppConfigState {
knowledgeSimilarity: number //最低相似度 0.01-0.99
knowledgeNResult: number //知识库返回结果数 1-10
}
databaseConfig: {
ids: number[]
}
commModelConfig: {
largeModel: string //大模型
topP: number //topP 0-1.00
......
......@@ -101,6 +101,7 @@ function messageItemFactory() {
imageUrl: '',
reasoningContent: '',
knowledgeContentResult: [],
dbChainSQLContent: '',
} as MessageItemInterface
}
......@@ -196,7 +197,19 @@ function handleQuestionSubmit() {
if (data.function && data.function.name) {
emit('updateMessageItem', answerMessageId, { pluginName: data.function.name }, modelIndex)
emit('messageListScrollToBottom')
return
}
// 数据库
if (data.dbChainResult && data.dbChainResult?.[0]) {
emit(
'updateMessageItem',
answerMessageId,
{
dbChainSQLContent: data.dbChainResult?.[0]?.sql || '',
},
modelIndex,
)
emit('messageListScrollToBottom')
}
// 知识库
......
......@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
import { CheckOne, Down } from '@icon-park/vue-next'
import type { MessageItemInterface } from '../types'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
import ExecuteCodeRender from '@/components/execute-code-render/execute-code-render.vue'
import MessageBubbleLoading from './message-bubble-loading.vue'
interface Props {
......@@ -122,6 +123,14 @@ function handleShowReasoningContentSwitch() {
</div>
<div v-else>
<!-- 数据库问答结果 -->
<div v-show="messageItem.dbChainSQLContent" class="mb-[10px] w-full">
<ExecuteCodeRender
:title="t('common_module.database_QA_executed_successfully')"
:raw-text-content="messageItem.dbChainSQLContent"
/>
</div>
<MarkdownRender
:raw-text-content="
messageItem.content ? messageItem.content : t('common_module.dialogue_module.empty_message_content')
......
......@@ -37,6 +37,7 @@ export interface MessageItemInterface {
imageUrl?: string
reasoningContent: string
knowledgeContentResult: KnowledgeContentResultItem[]
dbChainSQLContent: string
}
export interface LargeModelItem {
......
......@@ -10,6 +10,7 @@ interface ResponseData {
reasoningContent: string
function: { name: string }
knowledgeContentResult: KnowledgeContentResultItem[]
dbChainResult: DBChainResultItem[]
}
const { t } = i18n.global
......
......@@ -151,6 +151,7 @@ function messageItemFactory(): ConversationMessageItem {
reasoningContent: '',
modelName: '',
knowledgeContentResult: [],
dbChainSQLContent: '',
}
}
......@@ -259,13 +260,19 @@ function handleMessageSend() {
reasoningContent += data.reasoningContent
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { reasoningContent: reasoningContent })
emit('updatePageScroll')
return
}
// 插件
if (data.function && data.function.name) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { pluginName: data.function.name })
emit('updatePageScroll')
}
// 数据库
if (data.dbChainResult && data.dbChainResult?.[0]) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
dbChainSQLContent: data.dbChainResult?.[0]?.sql || '',
})
return
}
......@@ -274,6 +281,7 @@ function handleMessageSend() {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
knowledgeContentResult: data.knowledgeContentResult,
})
return
}
// 回复消息
......
......@@ -6,6 +6,7 @@ import { CheckOne, Down } from '@icon-park/vue-next'
import CustomLoading from './custom-loading.vue'
import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
import ExecuteCodeRender from '@/components/execute-code-render/execute-code-render.vue'
import { useUserStore } from '@/store/modules/user'
import { asBlob } from 'html-docx-js-typescript'
import { downloadFile } from '@/utils/download-file'
......@@ -187,6 +188,14 @@ const handleContentCopy = throttle(
</div>
<div v-else>
<!-- 数据库问答结果 -->
<div v-show="messageItem.dbChainSQLContent" class="mb-[10px] w-full">
<ExecuteCodeRender
:title="t('common_module.database_QA_executed_successfully')"
:raw-text-content="messageItem.dbChainSQLContent"
/>
</div>
<p class="break-all">
<MarkdownRender
ref="markdownRenderRef"
......
......@@ -35,7 +35,8 @@ const router = useRouter()
const personalAppConfigStore = usePersonalAppConfigStore()
const userStore = useUserStore()
const { baseInfo, commConfig, commModelConfig, knowledgeConfig, unitIds } = storeToRefs(personalAppConfigStore)
const { baseInfo, commConfig, commModelConfig, knowledgeConfig, databaseConfig, unitIds } =
storeToRefs(personalAppConfigStore)
const emitter = inject<Emitter<MittEvents>>('emitter')
......@@ -614,7 +615,10 @@ async function handleEquityInfoValidate() {
<div class="flex-1 overflow-auto">
<AgentPlugin v-model:unit-ids="unitIds" />
<AgentAssociatedKnowledge v-model:knowledge-config="knowledgeConfig" />
<AgentAssociatedKnowledge
v-model:knowledge-config="knowledgeConfig"
v-model:database-config="databaseConfig"
/>
<AgentMemorySetting
v-model:variable-structure="commConfig.variableStructure"
......
......@@ -4,20 +4,27 @@ import { useI18n } from 'vue-i18n'
import { Plus, RightOne } from '@icon-park/vue-next'
import { fetchGetKnowledgeListByKdIds } from '@/apis/knowledge'
import AssociatedKnowledgeModal from './associated-knowledge-modal.vue'
import AssociatedDatabaseModal from './associated-database-modal.vue'
import AgentKnowledgeSettingModal, { KnowledgeConfigType } from './agent-knowledge-setting-modal.vue'
import { KnowledgeItem } from '@/views/personal-space/personal-knowledge/types'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { KnowledgeTypeIcon } from '@/enums/knowledge'
import { fetchGetDataListByIds } from '@/apis/database'
import { DatabaseItemInterface } from '@/views/personal-space/personal-database/type'
const { t } = useI18n()
const knowledgeConfig = defineModel<PersonalAppConfigState['knowledgeConfig']>('knowledgeConfig', { required: true })
const databaseConfig = defineModel<PersonalAppConfigState['databaseConfig']>('databaseConfig', { required: true })
const isShowAssociatedKnowledgeModel = ref(false)
const knowledgeConfigExpandedNames = ref<string[]>([])
const selectKnowledgeList = ref<KnowledgeItem[]>([])
const hoverKdId = ref(0)
const isShowAgentKnowledgeSettingModal = ref(false)
const isShowAssociatedDatabaseModal = ref(false)
const selectDatabaseList = ref<DatabaseItemInterface[]>([])
const hoverDBId = ref(0)
watch(
() => knowledgeConfig.value.knowledgeIds,
......@@ -31,7 +38,18 @@ watch(
knowledgeConfig.value.knowledgeIds.push(knowledgeItem.id)
})
}
knowledgeConfig.value.knowledgeIds.length && (knowledgeConfigExpandedNames.value = ['knowledge'])
knowledgeConfig.value.knowledgeIds.length && knowledgeConfigExpandedNames.value.push('knowledge')
}
},
{ once: true, immediate: true },
)
watch(
() => databaseConfig.value.ids,
async (newDBIds) => {
if (newDBIds?.length > 0) {
await handleGetDatabaseListByIds()
databaseConfig.value.ids?.length && knowledgeConfigExpandedNames.value.push('database')
}
},
{ once: true, immediate: true },
......@@ -41,6 +59,10 @@ const isHoverKnowledgeItem = computed(() => (kdId: number) => {
return hoverKdId.value === kdId
})
const isHoverDatabaseItem = computed(() => (dbId: number) => {
return hoverDBId.value === dbId
})
const isOpenDocumentParsing = computed(() => knowledgeConfig.value.isDocumentParsing === 'Y')
async function handleGetKnowledgeListByIds() {
......@@ -83,7 +105,7 @@ function handleCloseAssociatedKnowledgeModal() {
return
}
knowledgeConfigExpandedNames.value = ['knowledge']
!knowledgeConfigExpandedNames.value.includes('knowledge') && knowledgeConfigExpandedNames.value.push('knowledge')
handleGetKnowledgeListByIds()
}
......@@ -96,6 +118,44 @@ function handleUpdateKnowledgeConfig(newKnowledgeConfig: KnowledgeConfigType) {
isShowAgentKnowledgeSettingModal.value = false
window.$message.success(t('common_module.save_success_message'))
}
async function handleGetDatabaseListByIds() {
if (!databaseConfig.value?.ids?.length) return
const res = await fetchGetDataListByIds<DatabaseItemInterface[]>(databaseConfig.value.ids)
if (res.code === 0) {
selectDatabaseList.value = res.data
}
}
async function handleCloseAssociatedDatabaseModal() {
if (!databaseConfig.value?.ids?.length) {
selectDatabaseList.value = []
return
}
!knowledgeConfigExpandedNames.value.includes('database') && knowledgeConfigExpandedNames.value.push('database')
handleGetDatabaseListByIds()
}
function handleMouseoverDatabaseItem(dbId: number) {
hoverDBId.value = dbId
}
function handleMouseleaveDatabaseItem() {
hoverDBId.value = 0
}
function handleToDatabaseDetail(dbId: number) {
const url = `${window.location.origin}/fe/personal-space/database/table/${dbId}`
window.open(url, '_blank')
}
function handleDeleteDatabaseItem(dbId: number) {
databaseConfig.value.ids = databaseConfig.value.ids.filter((id) => id !== dbId)
selectDatabaseList.value = selectDatabaseList.value.filter((item) => item.id !== dbId)
}
</script>
<template>
......@@ -185,6 +245,66 @@ function handleUpdateKnowledgeConfig(newKnowledgeConfig: KnowledgeConfigType) {
</span>
</NCollapseItem>
<NCollapseItem :title="t('common_module.database')" name="database" class="my-[13px]!">
<template #header-extra>
<NTooltip trigger="hover">
<template #trigger>
<Plus
theme="outline"
size="22"
:stroke-width="3"
class="text-theme-color cursor-pointer"
@click="isShowAssociatedDatabaseModal = true"
/>
</template>
{{ t('personal_space_module.database_module.add_database') }}
</NTooltip>
</template>
<ul>
<li
v-for="databaseItem in selectDatabaseList"
:key="databaseItem.id"
class="rounded-theme flex cursor-pointer justify-between gap-2 border border-[#f2f5f9] px-3 py-1.5 hover:bg-[#f2f5f9]"
@mouseover="handleMouseoverDatabaseItem(databaseItem.id)"
@mouseleave="handleMouseleaveDatabaseItem"
@click="handleToDatabaseDetail(databaseItem.id)"
>
<div class="flex items-center gap-2 overflow-hidden">
<div
class="h-6 w-6 bg-[url('https://gsst-poe-sit.gz.bcebos.com/v1/icon/direct-db.svg')] bg-contain bg-no-repeat"
/>
<n-ellipsis class="flex-1">
<span class="text-xs">{{ databaseItem.title }}</span>
<template #tooltip>
<div class="max-w-[500px]">
<span class="text-xs"> {{ databaseItem.title }}</span>
</div>
</template>
</n-ellipsis>
</div>
<n-tooltip placement="top">
<template #trigger>
<div v-show="isHoverDatabaseItem(databaseItem.id)" class="flex items-center justify-center">
<i
class="hover:text-error-font-color text-font-color iconfont icon-reduce cursor-pointer outline-none"
@click.stop="handleDeleteDatabaseItem(databaseItem.id)"
/>
</div>
</template>
<span>{{ t('common_module.remove') }}</span>
</n-tooltip>
</li>
</ul>
<span v-show="!selectDatabaseList.length" class="text-xs text-[#84868c]">
{{
t('personal_space_module.agent_module.agent_setting_module.agent_config_module.associated_database_desc')
}}
</span>
</NCollapseItem>
<NCollapseItem name="uploadFile" class="my-[13px]!">
<template #header>
<span class="mr-[5px] min-w-[60px]">
......@@ -222,4 +342,10 @@ function handleUpdateKnowledgeConfig(newKnowledgeConfig: KnowledgeConfigType) {
v-model:is-show-modal="isShowAgentKnowledgeSettingModal"
@confirm="handleUpdateKnowledgeConfig"
/>
<AssociatedDatabaseModal
v-model:show-modal="isShowAssociatedDatabaseModal"
v-model:db-ids="databaseConfig.ids"
@close="handleCloseAssociatedDatabaseModal"
/>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { Search } from '@icon-park/vue-next'
import CustomModal from '@/components/custom-modal/custom-modal.vue'
import { formatDateTime } from '@/utils/date-formatter'
import { fetchCreateDataBase, fetchGetDataBaseList } from '@/apis/database'
import {
DatabaseFormInterface,
DatabaseItemInterface,
DatabaseTableInfoItem,
} from '@/views/personal-space/personal-database/type'
import CreateDatabaseModal from '@/views/personal-space/personal-database/components/create-database-modal.vue'
const emit = defineEmits<{
(e: 'close'): void
}>()
const { t } = useI18n()
const showModal = defineModel<boolean>('showModal', { required: true })
const dbIds = defineModel<number[]>('dbIds', { required: true })
const isSearchEmptyList = ref(false)
const databaseListLoading = ref(true)
const refreshIconRotating = ref(false)
const searchQuery = ref('')
const showCreateDatabaseModal = ref(false)
const createDatabaseBtnLoading = ref(false)
const databaseList = ref<DatabaseItemInterface[]>([])
const emptyDatabaseListText = computed(() => {
return isSearchEmptyList.value ? t('common_module.search_empty_data') : t('common_module.empty_data')
})
const isAddedDBId = computed(() => (id: number) => {
return dbIds.value?.includes(id)
})
const isDisabledAddBtn = computed(() => {
return dbIds.value.length >= 1
})
const dbTableNameListText = (tableInfos: DatabaseTableInfoItem[]) => {
let tableNameListText = ''
if (Array.isArray(tableInfos)) {
tableInfos.forEach((tableInfo) => {
if (tableInfo.table_NAME) {
tableNameListText += `${tableInfo.table_NAME}、`
}
})
return tableNameListText.slice(0, -1)
} else {
return '---'
}
}
watch(
() => showModal.value,
async (newValue) => {
if (newValue) {
searchQuery.value = ''
await handleGetDatabaseList()
}
},
)
async function handleGetDatabaseList() {
databaseListLoading.value = true
const res = await fetchGetDataBaseList<DatabaseItemInterface[]>({
search: searchQuery.value,
pagingInfo: { pageSize: 9999999, pageNo: 1, totalRows: 0, totalPages: 0 },
})
if (res.code === 0) {
databaseList.value = res.data
databaseListLoading.value = false
}
}
async function handleRefreshDatabaseList() {
refreshIconRotating.value = true
await handleGetDatabaseList().finally(() => (refreshIconRotating.value = false))
}
function handleShowCreateDatabaseModal() {
showCreateDatabaseModal.value = true
}
async function handleCreateDatabase(databaseFormData: DatabaseFormInterface) {
createDatabaseBtnLoading.value = true
const res = await fetchCreateDataBase<{ id: number }>(databaseFormData).finally(
() => (createDatabaseBtnLoading.value = false),
)
if (res.code === 0) {
showCreateDatabaseModal.value = false
const url = `${window.location.origin}/fe/personal-space/database/table/${res.data.id}`
window.open(url, '_blank')
}
}
function handleToDatabaseDetail(dbId: number) {
const url = `${window.location.origin}/fe/personal-space/database/table/${dbId}`
window.open(url, '_blank')
}
function handleAddDatabaseId(dbId: number, databaseTitle: string) {
dbIds.value.push(dbId)
window.$message.success(t('common_module.add_database_successfully', [databaseTitle]))
}
function handleRemoveDatabaseId(dbId: number, databaseTitle: string) {
dbIds.value = dbIds.value.filter((id) => id !== dbId)
window.$message.success(t('common_module.remove_database_successfully', [databaseTitle]))
}
</script>
<template>
<CustomModal
v-model:is-show="showModal"
:title="''"
:width="690"
:height="675"
:content-style="{ padding: '0 12px 24px' }"
@close="emit('close')"
>
<template #header>
<div class="flex items-center gap-[5px]">
<div class="font-family-medium text-[18px] leading-none">
{{ t('personal_space_module.database_module.add_database') }}
</div>
<div class="rounded-theme bg-[#F2F5F9] px-[10px] py-[2px] text-[12px]">
{{
t(
'personal_space_module.agent_module.agent_setting_module.agent_config_module.only_one_database_can_be_added',
)
}}
</div>
</div>
</template>
<template #content>
<div class="gap-4.5 flex w-full items-center justify-end px-3">
<i
class="iconfont icon-huanyihuan text-gray-font-color cursor-pointer transition-[rotate] duration-150 ease-in-out hover:opacity-80"
:class="{ 'rotate-360': refreshIconRotating }"
@click="handleRefreshDatabaseList"
/>
<n-input
v-model:value="searchQuery"
:placeholder="t('common_module.search')"
class="w-[214px]!"
@keyup.enter="handleGetDatabaseList"
>
<template #prefix>
<Search theme="outline" size="16" fill="#999" class="cursor-pointer" @click="handleGetDatabaseList" />
</template>
</n-input>
<n-button type="primary" :focusable="false" :bordered="false" @click="handleShowCreateDatabaseModal">
{{ t('personal_space_module.database_module.create_database') }}
</n-button>
</div>
<n-virtual-list
v-show="!databaseListLoading"
class="mt-[18px] max-h-[533px] w-full pl-3"
:item-size="136"
:items="databaseList"
key-field="id"
items-style="padding-right: 12px;"
>
<template #default="{ item }: { item: DatabaseItemInterface }">
<div
:key="item.id"
class="border-inactive-border-color hover:border-theme-color mb-4.5 flex h-[118px] w-full cursor-pointer items-center gap-4 rounded-[10px] border px-4 py-3.5"
@click="handleToDatabaseDetail(item.id)"
>
<div class="flex flex-1 flex-col overflow-hidden">
<div class="flex flex-1">
<div
class="h-9 w-9 flex-shrink-0 bg-[url('https://gsst-poe-sit.gz.bcebos.com/v1/icon/direct-db.svg')] bg-contain"
/>
<div class="ml-3.5 flex flex-col items-start overflow-hidden">
<n-ellipsis class="flex-1">
<span class="font-600">{{ item.title }}</span>
</n-ellipsis>
<span class="text-gray-font-color mt-1 line-clamp-1 h-4 break-all text-xs">
{{ dbTableNameListText(item.tableInfos) }}
</span>
<div class="mt-3 flex items-center gap-4">
<span
v-show="!item.tableInfos?.length"
class="text-gray-font-color rounded-theme inline-block cursor-not-allowed bg-[#F7F7FA] px-2.5 py-[5px] text-[12px] leading-4"
>
{{ t('common_module.no_database_table') }}
</span>
<n-popover placement="bottom" content-style="padding: 16px 8px;" scrollable>
<template #trigger>
<span
v-show="item.tableInfos?.length"
class="text-theme-color rounded-theme inline-block cursor-pointer bg-[#F7F7FA] px-2.5 py-[5px] text-[12px] leading-4 hover:opacity-80"
>
{{ t('common_module.view_database_table') }}
</span>
</template>
<div>
<p class="text-gray-font-color h-5 px-4 text-[12px] leading-5">
{{ t('common_module.total_database_table', { count: item.tableInfos?.length || 0 }) }}
</p>
<n-scrollbar class="max-h-[200px] px-4">
<ul class="min-w-[300px]">
<li
v-for="(tableItem, index) in item.tableInfos"
:key="index"
class="text-font-color flex h-[50px] items-center gap-2.5 border-b border-[#F4F4F4] text-xs"
>
<n-ellipsis class="max-w-[400px]">
<span>{{ tableItem.table_NAME }}</span>
</n-ellipsis>
</li>
</ul>
</n-scrollbar>
</div>
</n-popover>
<span class="text-gray-font-color text-[12px]">
{{ t('common_module.modified_time') }}:
{{ formatDateTime(item.modifiedTime, 'YYYY-MM-DD') || '--' }}
</span>
</div>
</div>
</div>
</div>
<n-button
v-show="!isAddedDBId(item.id)"
class="min-w-17!"
:bordered="isDisabledAddBtn"
:type="isDisabledAddBtn ? '' : 'primary'"
:disabled="isDisabledAddBtn"
@click.stop="handleAddDatabaseId(item.id, item.title)"
>
{{ t('common_module.add') }}
</n-button>
<n-button
v-show="isAddedDBId(item.id)"
class="min-w-17!"
type="error"
ghost
@click.stop="handleRemoveDatabaseId(item.id, item.title)"
>
{{ t('common_module.remove') }}
</n-button>
</div>
</template>
</n-virtual-list>
<div v-show="databaseListLoading" class="px-3">
<n-skeleton v-for="i in 4" :key="i" :height="118" :sharp="false" size="medium" class="mt-4.5" />
</div>
<div
v-show="!databaseListLoading && databaseList.length === 0"
class="flex h-[533px] items-center justify-center"
>
<div class="flex flex-col items-center justify-center">
<div
class="mb-5 h-[68px] w-[68px]"
:class="isSearchEmptyList ? 'bg-px-search_empty_list-png' : 'bg-px-empty_list-png'"
/>
<p class="text-gray-font-color select-none">{{ emptyDatabaseListText }}</p>
</div>
</div>
</template>
<template #footer></template>
</CustomModal>
<CreateDatabaseModal
v-model:show-modal="showCreateDatabaseModal"
:btn-loading="createDatabaseBtnLoading"
@confirm="handleCreateDatabase"
/>
</template>
......@@ -60,6 +60,10 @@ async function handleGetAgentDetail(agentId: string) {
agentAvatar:
res.data.baseInfo.agentAvatar || 'https://gsst-poe-sit.gz.bcebos.com/data/20240911/1726041369632.webp',
},
databaseConfig: {
...res.data.databaseConfig,
ids: res.data?.databaseConfig?.ids || [],
},
})
}
})
......
......@@ -3,6 +3,7 @@ import { computed, onMounted, ref, useTemplateRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { Search } from '@icon-park/vue-next'
import { DataTableInst } from 'naive-ui'
import { useRouter } from 'vue-router'
import useTableScrollY from '@/composables/useTableScrollY'
import { usePagination } from '@/composables/usePagination.ts'
import CustomPagination from '@/components/custom-pagination/custom-pagination.vue'
......@@ -15,6 +16,8 @@ type DatabaseEditFormInterface = DatabaseFormInterface & { id: number }
const { t } = useI18n()
const router = useRouter()
const databaseListTableRef = useTemplateRef<DataTableInst>('databaseListTableRef')
const { pageContentWrapRef, tableContentY } = useTableScrollY(48 + 32 + 18 + 16 + 28)
......@@ -96,8 +99,11 @@ function handleDatabaseTableAction(actionType: string, dbId: number) {
}
}
function handleToDatabaseDetail(_dbId: number) {
// TODO
function handleToDatabaseDetail(dbId: number) {
router.push({
name: 'PersonalSpaceDBTable',
params: { id: dbId },
})
}
function handleShowEditDatabaseModal(dbId: number) {
......
......@@ -144,6 +144,7 @@ function messageItemFactory(): ConversationMessageItem {
imageUrl: '',
reasoningContent: '',
knowledgeContentResult: [],
dbChainSQLContent: '',
}
}
......@@ -231,13 +232,19 @@ function handleMessageSend(lastQuestionContent?: string) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { reasoningContent })
emit('updatePageScroll')
return
}
// 插件
if (data.function && data.function.name) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { pluginName: data.function.name })
emit('updatePageScroll')
}
// 数据库
if (data.dbChainResult && data.dbChainResult?.[0]) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
dbChainSQLContent: data.dbChainResult?.[0]?.sql || '',
})
return
}
......
......@@ -7,6 +7,7 @@ import CustomLoading from './custom-loading.vue'
import MusicWavesLoading from './music-waves-loading.vue'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
import EditorDrawer from '@/components/editor-drawer/editor-drawer.vue'
import ExecuteCodeRender from '@/components/execute-code-render/execute-code-render.vue'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { useLayoutConfig } from '@/composables/useLayoutConfig'
import { useUserStore } from '@/store/modules/user'
......@@ -205,6 +206,14 @@ const handleContentCopy = throttle(
</div>
<div v-else>
<!-- 数据库问答结果 -->
<div v-show="messageItem.dbChainSQLContent" class="mb-[10px] w-full">
<ExecuteCodeRender
:title="t('common_module.database_QA_executed_successfully')"
:raw-text-content="messageItem.dbChainSQLContent"
/>
</div>
<p class="break-all">
<MarkdownRender
ref="markdownRenderRef"
......
......@@ -145,6 +145,7 @@ function messageItemFactory(): ConversationMessageItem {
imageUrl: '',
reasoningContent: '',
knowledgeContentResult: [],
dbChainSQLContent: '',
}
}
......@@ -232,13 +233,19 @@ function handleMessageSend(lastQuestionContent?: string) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { reasoningContent })
emit('updatePageScroll')
return
}
// 插件
if (data.function && data.function.name) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, { pluginName: data.function.name })
emit('updatePageScroll')
}
// 数据库
if (data.dbChainResult && data.dbChainResult?.[0]) {
emit('updateSpecifyMessageItem', latestAssistantMessageKey, {
dbChainSQLContent: data.dbChainResult?.[0]?.sql || '',
})
return
}
......
......@@ -135,6 +135,21 @@ function handleShowReasoningContentSwitch() {
</div>
<div v-else>
<!-- 数据库问答结果 -->
<div v-show="messageItem.dbChainSQLContent" class="database-container">
<div class="database-title-container">
<CheckOne theme="outline" size="16" fill="#40bd23" />
<span class="database-name">{{ t('common_module.database_QA_executed_successfully') }}</span>
</div>
<MarkdownRender
ref="markdownRenderRef"
dark-theme
color="#fff"
:font-size="'3.2vw'"
:raw-text-content="messageItem.dbChainSQLContent"
/>
</div>
<p class="break-all">
<MarkdownRender
ref="markdownRenderRef"
......@@ -221,6 +236,18 @@ function handleShowReasoningContentSwitch() {
}
}
.database-container {
@apply mb-[10px] rounded-[10px] bg-[#F3F5F9] p-[14px] pt-0 text-[12px];
.database-title-container {
@apply flex items-center gap-[5px] py-[10px];
.database-name {
@apply text-gray-font-color text-[12px] leading-4;
}
}
}
.content-loading {
@apply py-[6px] pl-[16px];
}
......
......@@ -5,6 +5,13 @@ declare interface KnowledgeContentResultItem {
documentName: string
}
declare interface DBChainResultItem {
result: string
sql: string
sqlResult: string
status: string
}
declare interface ConversationMessageItem {
timestamp: number
role: 'user' | 'assistant'
......@@ -21,4 +28,5 @@ declare interface ConversationMessageItem {
reasoningContent: string
modelName?: string
knowledgeContentResult: KnowledgeContentResultItem[]
dbChainSQLContent: string
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment