Browse Source

aichat助手交互升级

master
xueyinfei 1 week ago
parent
commit
222dafa188
  1. 15
      vue-fastapi-backend/module_admin/controller/aichat_controller.py
  2. 18
      vue-fastapi-backend/module_admin/dao/aichat_dao.py
  3. 3
      vue-fastapi-backend/module_admin/entity/do/aichat_do.py
  4. 3
      vue-fastapi-backend/module_admin/entity/vo/aichat_vo.py
  5. 30
      vue-fastapi-backend/module_admin/service/aichat_service.py
  6. 18
      vue-fastapi-frontend/src/api/aichat/aichat.js
  7. 7
      vue-fastapi-frontend/src/components/codemirror/SQLCodeMirrorSqlFlow.vue
  8. 88
      vue-fastapi-frontend/src/views/aichat/Interrupt.vue
  9. 289
      vue-fastapi-frontend/src/views/aichat/aichat.vue
  10. 5
      vue-fastapi-frontend/src/views/aichat/index.vue
  11. 2
      vue-fastapi-frontend/src/views/meta/metaInfo/bloodRelation.vue
  12. 2
      vue-fastapi-frontend/src/views/meta/metaInfo/bloodRelationSql.vue
  13. 15
      vue-fastapi-frontend/src/views/sqlFlow/index.vue

15
vue-fastapi-backend/module_admin/controller/aichat_controller.py

@ -44,6 +44,14 @@ async def delete_chat_session(request: Request, sessionId: str, query_db: AsyncS
return ResponseUtil.success(msg=delete_chat_session_result.message)
@aichatController.post("/delete/chatList/{chatId}")
async def delete_chat_session(request: Request, chatId: str, query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
delete_chat_session_result = await AiChatService.delete_chat_list(query_db, chatId, current_user)
logger.info(delete_chat_session_result.message)
return ResponseUtil.success(msg=delete_chat_session_result.message)
@aichatController.post("/add")
async def add_chat(request: Request, add_chat: AiChatModel, query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
@ -59,6 +67,13 @@ async def update_chat(request: Request, update_chat: AiChatModel, query_db: Asyn
logger.info(operate_result.message)
return ResponseUtil.success(msg=operate_result.message)
@aichatController.post("/updateChatProcessData")
async def update_chat(request: Request, update_chat: AiChatModel, query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
operate_result = await AiChatService.update_chat_process(query_db, update_chat)
logger.info(operate_result.message)
return ResponseUtil.success(msg=operate_result.message)
@aichatController.post("/upload")
async def upload_file(request: Request, sessionId: str = Form(), file: UploadFile = File(...),

18
vue-fastapi-backend/module_admin/dao/aichat_dao.py

@ -31,12 +31,18 @@ class AiChatDao:
return result
@classmethod
async def get_ai_chat_by_id(cls, sessionId: str, db: AsyncSession, user_id: int):
async def get_chat_session_by_id(cls, sessionId: str, db: AsyncSession, user_id: int):
chat_list = (await db.execute(select(AiChatSession)
.where(AiChatSession.user == user_id,
AiChatSession.sessionId == sessionId))).scalars().first()
return chat_list
@classmethod
async def get_ai_chat_by_id(cls, chatId: str, db: AsyncSession):
chat = (await db.execute(select(AiChatHistory).where(AiChatHistory.chatId == chatId))).scalars().first()
return chat
@classmethod
async def add_ai_chat_session(cls, sessionId: str, sessionName: str, time: str, db: AsyncSession, user_id: int):
chat_session = AiChatSession()
@ -59,6 +65,16 @@ class AiChatDao:
await db.execute(delete(AiChatHistory).where(AiChatHistory.sessionId == sessionId))
await db.execute(delete(AiChatSession).where(AiChatSession.sessionId == sessionId))
@classmethod
async def delete_chat_with_session_and_time(cls, db: AsyncSession, sessionId: str, time: str):
await db.execute(delete(AiChatHistory).where(AiChatHistory.sessionId == sessionId, AiChatHistory.time >= time))
@classmethod
async def update_ai_chat_history(cls, update_chat: AiChatModel, db: AsyncSession):
await db.execute(update(AiChatHistory), [dict(update_chat)])
@classmethod
async def update_chat_process(cls, update_chat: AiChatModel, db: AsyncSession):
await db.execute(update(AiChatHistory)
.values(action=update_chat.action, interrupt=update_chat.interrupt)
.where(AiChatHistory.chatId == update_chat.chatId))

3
vue-fastapi-backend/module_admin/entity/do/aichat_do.py

@ -22,6 +22,9 @@ class AiChatHistory(Base):
operate = Column(String(50), default=None, comment='点赞,差评等操作')
thumbDownReason = Column(String(255), default=None, comment='差评原因')
file = Column(String(255), default=None, comment='文件id集合')
interrupt = Column(LONGTEXT, default=None, comment='中断内容')
checkpointer = Column(LONGTEXT, default=None, comment='结束内容')
action = Column(String(50), default=None, comment='交互审批处理动作')
class AiChatSession(Base):

3
vue-fastapi-backend/module_admin/entity/vo/aichat_vo.py

@ -33,6 +33,9 @@ class AiChatModel(BaseModel):
operate: Optional[str] = None
thumbDownReason: Optional[str] = None
file: Optional[str] = None
interrupt: Optional[str] = None
checkpointer: Optional[str] = None
action: Optional[str] = None
class ThumbOperateModel(BaseModel):

30
vue-fastapi-backend/module_admin/service/aichat_service.py

@ -17,37 +17,45 @@ class AiChatService:
@classmethod
async def get_ai_session_list_services(cls, result_db: AsyncSession, sessionId: str,
current_user: Optional[CurrentUserModel] = None):
ai_session_list = await AiChatDao.get_ai_session_list(result_db, sessionId, current_user.user.user_id) # 查询最新的20条
current_user: Optional[CurrentUserModel] = None):
ai_session_list = await AiChatDao.get_ai_session_list(result_db, sessionId,
current_user.user.user_id) # 查询最新的20条
return ai_session_list
@classmethod
async def get_ai_chat_list_services(cls, result_db: AsyncSession, query: AiListQuery,
current_user: Optional[CurrentUserModel] = None):
current_user: Optional[CurrentUserModel] = None):
ai_session_list = await AiChatDao.get_ai_chat_list(result_db, query, current_user.user.user_id) # 查询最新的20条
return CamelCaseUtil.transform_result(ai_session_list)
@classmethod
async def delete_chat_session(cls, result_db: AsyncSession, sessionId: str,
current_user: Optional[CurrentUserModel] = None):
current_user: Optional[CurrentUserModel] = None):
await AiChatDao.delete_chat_session(result_db, sessionId, current_user.user.user_id)
await result_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
@classmethod
async def delete_chat_list(cls, result_db: AsyncSession, chatId: str,
current_user: Optional[CurrentUserModel] = None):
chat = AiChatDao.get_ai_chat_by_id(chatId, result_db)
await AiChatDao.delete_chat_with_session_and_time(result_db, chat.sessionId, chat.time)
await result_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
@classmethod
async def add_chat(cls, result_db: AsyncSession, add_chat: AiChatModel,
current_user: Optional[CurrentUserModel] = None):
chat_session = await AiChatDao.get_ai_chat_by_id(add_chat.sessionId, result_db, current_user.user.user_id)
current_user: Optional[CurrentUserModel] = None):
chat_session = await AiChatDao.get_chat_session_by_id(add_chat.sessionId, result_db, current_user.user.user_id)
print(chat_session)
add_chat.user = current_user.user.user_id
if add_chat.time is None:
add_chat.time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if chat_session is None:
await AiChatDao.add_ai_chat_session(add_chat.sessionId, add_chat.sessionName,
add_chat.time, result_db, current_user.user.user_id)
add_chat.time, result_db, current_user.user.user_id)
await result_db.commit()
chat_history = AiChatHistory(**add_chat.dict())
chat_history.chatId = uuid.uuid4()
await AiChatDao.add_ai_chat_history(chat_history, result_db)
await result_db.commit()
return CrudResponseModel(is_success=True, message='操作成功')
@ -57,3 +65,9 @@ class AiChatService:
await AiChatDao.update_ai_chat_history(update_chat, result_db)
await result_db.commit()
return CrudResponseModel(is_success=True, message='操作成功')
@classmethod
async def update_chat_process(cls, result_db: AsyncSession, update_chat: AiChatModel):
await AiChatDao.update_chat_process(update_chat, result_db)
await result_db.commit()
return CrudResponseModel(is_success=True, message='操作成功')

18
vue-fastapi-frontend/src/api/aichat/aichat.js

@ -32,10 +32,17 @@ export function DeleteChatSession(sessionId) {
method: 'post'
})
}
//删除chat之后的所有数据
export function DeleteChatList(chatId) {
return request({
url: '/default-api/aichat/delete/chatList/'+chatId,
method: 'post'
})
}
export function updateChat(data) {
export function updateChatProcessData(data) {
return request({
url: '/default-api/aichat/update',
url: '/default-api/aichat/updateChatProcessData',
method: 'post',
data: data
})
@ -48,6 +55,13 @@ export async function addChat(data) {
data: data
})
}
export async function updateChat(data) {
return request({
url: '/default-api/aichat/update',
method: 'post',
data: data
})
}
export async function postDataQuery(data) {
return request({
url: '/dataquery-api/datachat',

7
vue-fastapi-frontend/src/components/codemirror/SQLCodeMirrorSqlFlow.vue

@ -1,5 +1,5 @@
<template>
<codemirror v-model:value="value" :options="sqlOptions" :dbType="dbType" />
<codemirror v-model:value="value" :options="sqlOptions" :dbType="dbType" @input="updateValue" />
</template>
<script setup>
@ -36,6 +36,10 @@ const props = defineProps({
data: String,
dbType: String,
})
const emits = defineEmits(['update:modelValue'])
function updateValue(e) {
emits('update:modelValue', e)
}
const sqlOptions = {
autorefresh: true, //
smartIndent: true, //
@ -62,7 +66,6 @@ const sqlOptions = {
const value = ref("")
watch(() => value.value,
(val) =>{
console.log(props.dbType)
value.value = sqlFormatter.format(val,{ language: props.dbType?.toLowerCase() || "sql" })
}
)

88
vue-fastapi-frontend/src/views/aichat/Interrupt.vue

@ -0,0 +1,88 @@
<template>
<template v-if="formData && formData.block">
<el-form :rules="formRules" ref="dynamicForm" >
<template v-for="(item, index) in formData.block">
<el-form-item :key="'span'+ index" v-if="item.d_type === 'text' && item.ct_type === 'span'">
<span style="font-size: 14px">{{item.default_value}}</span>
</el-form-item>
<el-form-item v-else :key="'comp'+ index" :label="item.name" :prop="'field_' + index" :rules="item.required ? [{ required: true, message: `${item.name}必填` }] : []">
<el-date-picker v-if="item.d_type === 'date' && item.ct_type === 'datePicker'" v-model="item.default_value" type="date" placeholder="请输入日期" value-format="YYYY-MM-DD" :disabled="item.read_only"/>
<el-date-picker v-if="item.d_type === 'date' && item.ct_type === 'dateRangePicker'" v-model="item.default_value" type="daterange" range-separator="" start-placeholder="开始时间" end-placeholder="结束时间" value-format="YYYY-MM-DD" :disabled="item.read_only"/>
<el-input v-if="item.d_type === 'text' && item.ct_type === 'input'" v-model="item.default_value" :disabled="item.read_only"></el-input>
<el-radio-group v-if="item.d_type === 'enum' && item.ct_type === 'radioGroup'" v-model="item.default_value" :disabled="item.read_only">
<el-radio v-for="radio in item.options" :value="radio">{{radio}}</el-radio>
</el-radio-group>
<el-checkbox-group v-if="item.d_type === 'enum' && item.ct_type === 'checkboxGroup'" v-model="item.default_value" :disabled="item.read_only">
<el-checkbox v-for="checkItem in item.options" :label="checkItem" :value="checkItem" />
</el-checkbox-group>
<el-select v-if="item.d_type === 'enum' && item.ct_type === 'select'" v-model="item.default_value" :disabled="item.read_only" :filterable="item.allow_create" :allow-create="item.allow_create">
<el-option
v-for="selectItem in item.options"
:key="selectItem"
:label="selectItem"
:value="selectItem"
/>
</el-select>
<el-select v-if="item.d_type === 'enum' && item.ct_type === 'multiselect'" multiple v-model="item.default_value" :disabled="item.read_only" :filterable="item.allow_create" :allow-create="item.allow_create">
<el-option
v-for="selectItem in item.options"
:key="selectItem"
:label="selectItem"
:value="selectItem"
/>
</el-select>
</el-form-item>
</template>
<el-form-item>
<el-button :disabled="!isLastChat" v-for="item in source.action" :type="item.style" @click="click(item)">{{item.label}}</el-button>
</el-form-item>
</el-form>
</template>
</template>
<script setup>
import { ref, nextTick, computed, watch, reactive, onMounted } from 'vue'
const emit = defineEmits(['processAuth'])
const { proxy } = getCurrentInstance();
const props = defineProps({
source: Object,
checkpointer: Object,
chatId: String,
isLastChat: Boolean
})
const formData = ref({})
const formRules = ref({})
onMounted(()=>{
formData.value = props.source
})
function click(item){
if (item.style === 'primary'){
proxy.$refs.dynamicForm.validate(valid => {
if (valid) {
let data = {
'chatId':props.chatId,
'checkpointer': props.style.checkpointer,
'interrupt': formData.value,
'action': item.action
}
emit('processAuth',data)
}
})
}else{
let data = {
'checkpointer': props.style.checkpointer,
'block': formData.value.block,
'action': item.action
}
emit('processAuth',data)
}
}
</script>
<style scoped lang="scss">
</style>

289
vue-fastapi-frontend/src/views/aichat/aichat.vue

@ -3,40 +3,50 @@
<el-scrollbar ref="scrollDiv" @scroll="handleScrollTop">
<div ref="dialogScrollbar" class="ai-chat__content p-24 chat-width" style="padding: 24px;">
<div class="item-content mb-16" style="margin-bottom: 16px">
<div class="avatar">
<img src="@/assets/logo/logo2.png" height="30px" />
</div>
<!-- <div class="avatar">-->
<!-- <img src="@/assets/logo/logo2.png" height="30px" />-->
<!-- </div>-->
<div class="content">
<el-card shadow="always" class="dialog-card">
<span style="font-size: 14px">您好我是 果知小助手您可以向我提出关于 果知的相关问题</span>
</el-card>
<span style="font-size: 14px">您好我是 果知小助手您可以向我提出关于 果知的相关问题</span>
</div>
</div>
<template v-for="(item, index) in chatList" :key="index">
<!-- 问题 -->
<div v-if="item.type === 'question'" class="item-content mb-16 lighter" style="margin-bottom: 16px;font-weight: 400;display: flex;align-items: flex-start;max-width: 100%;flex-direction: row-reverse;">
<div class="avatar" style="margin-left: 10px">
<el-avatar style="width:30px;height: 30px;background: #3370FF">
<img src="@/assets/aichat/user-icon.svg" style="width: 30px" alt="" />
</el-avatar>
</div>
<div v-if="item.type === 'question'" class="item-content mb-16 lighter" style="font-weight: 400;display: flex;align-items: flex-start;max-width: 100%;flex-direction: row-reverse;">
<!-- <div class="avatar" style="margin-left: 10px">-->
<!-- <el-avatar style="width:30px;height: 30px;background: #3370FF">-->
<!-- <img src="@/assets/aichat/user-icon.svg" style="width: 30px" alt="" />-->
<!-- </el-avatar>-->
<!-- </div>-->
<div class="content" style="max-width: 100%;word-break: break-all;white-space: pre-wrap;">
<div class="text break-all pre-wrap" style="word-break: break-all;white-space: pre-wrap;">
{{ item.content }}
</div>
<el-card class="chat_question_card" shadow="never" style="border-radius: 26px;background-color: #f5f5f5">
<div class="text break-all pre-wrap" style="word-break: break-all;white-space: pre-wrap;font-size: 14px">
{{ item.content }}
</div>
</el-card>
<el-tag type="success" style="margin-bottom: 5px;cursor: pointer;display: inline-block" v-for="(fileName,index) in item.file" :disable-transitions="false" @click="downloadFile(fileName.file,fileName.bucket,item.sessionId)">{{fileName.file}}</el-tag>
</div>
</div>
<!-- 回答 -->
<div v-if="item.type === 'answer'" class="item-content mb-16 lighter" style="margin-bottom: 16px;font-weight: 400">
<div class="avatar">
<img src="@/assets/logo/logo2.png" height="30px" />
</div>
<div v-if="item.type === 'answer'" class="item-content mb-16 lighter" style="font-weight: 400">
<el-popconfirm popper-style="z-index:99999" title="确定要回到这一步吗?" @confirm="confirmReturn(item,index)">
<template #reference>
<div class="returnToHere" style="margin-top: 5px;margin-bottom: 5px">
<i class="ri-bookmark-2-fill returnToHereIcon"></i>
<el-divider class="returnToHereDivider">
<span class="divider-text">回到这里</span>
</el-divider>
<i class="ri-arrow-go-back-line returnToHereIcon"></i>
</div>
</template>
</el-popconfirm>
<!-- <div class="avatar">-->
<!-- <img src="@/assets/logo/logo2.png" height="30px" />-->
<!-- </div>-->
<div class="content">
<el-card shadow="always" class="dialog-card">
<MdRenderer :is_large="is_large" :chatIndex="index" :source="item.content" @fullscreenG6="fullscreen"></MdRenderer>
</el-card>
<MdRenderer :is_large="is_large" :chatIndex="index" :source="item.content" @fullscreenG6="fullscreen"></MdRenderer>
<Interrupt :isLastChat="index === (chatList.length - 1)" :chatId="item.chatId" :source="item.interrupt" :checkpointer="item.checkpointer" @processAuth="processAuth"></Interrupt>
<div class="flex-between mt-8" style="display: flex; justify-content: space-between; align-items: center; margin-top: 8px">
<div>
<el-button
@ -132,6 +142,10 @@
<el-avatar style="width: 30px;height: 30px"><img src="@/assets/aichat/智能体logo.jpg"></el-avatar>
<span style="font-size: 16px;margin-left: 10px">数据治理管理专家</span>
</div>
<div class="machineDiv" @click="chooseMachine('智能导航专家','@/assets/aichat/智能体logo.jpg')" style="align-items: center;width:100%;display: flex">
<el-avatar style="width: 30px;height: 30px"><img src="@/assets/aichat/智能体logo.jpg"></el-avatar>
<span style="font-size: 16px;margin-left: 10px">智能导航专家</span>
</div>
</el-scrollbar>
</div>
</template>
@ -146,6 +160,41 @@
<img v-show="isDisabledChart || loading" src="@/assets/aichat/icon_send.svg" alt="" />
<img v-show="!isDisabledChart && !loading" src="@/assets/aichat/icon_send_colorful.svg" alt="" />
</el-button>
<el-popover
popper-style="z-index:99999;width:300px"
title="自动审批配置"
placement="left-end"
trigger="click"
>
<template #reference>
<el-button
text
style="padding: 0;margin: 0"
>
<el-icon><Setting /></el-icon>
</el-button>
</template>
<template #default>
<div class="autoProcessClass" @click.prevent="handleCheckAllRobot" style="cursor: pointer">
<el-checkbox
v-model="autoProcess.checkAll"
:indeterminate="autoProcess.isIndeterminate"
>
全部自动审批
</el-checkbox>
</div>
<el-divider style="margin-top: 5px;margin-bottom: 5px" />
<el-checkbox-group
v-model="autoProcess.autoArray"
style="display: flex; flex-direction: column"
>
<div v-for="robot in autoProcess.robots" @click.prevent="toggleRobot(robot)" class="autoProcessClass" style="cursor: pointer">
<el-checkbox :key="robot" :label="robot" :value="robot"></el-checkbox>
</div>
</el-checkbox-group>
</template>
</el-popover>
</div>
</div>
</div>
@ -184,10 +233,11 @@ import OperationButton from './OperationButton.vue'
import MdRenderer from '@/views/aichat/MdRenderer.vue'
import fullscreenG6 from '@/views/aichat/fullscreenG6.vue'
import {getToken} from "@/utils/auth.js";
import {postChatMessage} from "@/api/aichat/aichat.js"
import {addChat, DeleteChatList, postChatMessage, updateChatProcessData} from "@/api/aichat/aichat.js"
import cache from "@/plugins/cache.js";
import Cookies from "js-cookie";
import {addChat} from "@/api/aichat/aichat";
import Interrupt from "@/views/aichat/Interrupt.vue";
import { v4 as uuidv4 } from 'uuid';
defineOptions({ name: 'AiChat' })
const route = useRoute()
@ -226,11 +276,17 @@ const scrollDiv = ref()
const dialogScrollbar = ref()
const loading = ref(false)
const inputValue = ref('')
const currentQuestion = ref({})
const chartOpenId = ref('')
const chatList = ref([])
const answerList = ref([])
const controller = ref(null)
const autoProcess = ref({
checkAll: false,
isIndeterminate: false,
autoArray:[],
robots:['数据治理管理专家自动批准','元数据专家自动批准','数据标准专家自动批准','数据安全专家自动批准','数据分析专家自动批准','数据模型专家自动批准','数据质量专家自动批准','智能导航专家自动审批'],
})
const popoverVisible = ref(false)
const currentMachine = ref([])
const currentFiles = ref([])
@ -258,6 +314,39 @@ function setScrollBottom() {
scrollDiv.value.setScrollTop(getMaxHeight())
}
function confirmReturn(item,index){
DeleteChatList(item.chatId).then(res=>{
chatList.value.splice(index, chatList.value.length - index);
let reqData = {
"user_id": cache.local.get("username"),
"session_id": Cookies.get("chatSessionId"),
"checkpointer": item.checkpointer
}
sendChatMessage(reqData)
})
}
function handleCheckAllRobot(){
autoProcess.value.checkAll = !autoProcess.value.checkAll
autoProcess.value.autoArray = autoProcess.value.checkAll ? autoProcess.value.robots : []
autoProcess.value.isIndeterminate = false
}
function toggleRobot(robot){
const index = autoProcess.value.autoArray.indexOf(robot)
if (index !== -1) {
//
autoProcess.value.autoArray = autoProcess.value.autoArray.filter(item => item !== robot)
} else {
//
autoProcess.value.autoArray = [...autoProcess.value.autoArray, robot]
}
const checkedCount = autoProcess.value.autoArray.length
autoProcess.value.checkAll = checkedCount === autoProcess.value.robots.length
autoProcess.value.isIndeterminate = checkedCount > 0 && checkedCount < autoProcess.value.robots.length
}
/**
* 滚动条距离最上面的高度
*/
@ -329,6 +418,7 @@ watch(() => props.cookieSessionId, value => upload.data = {sessionId:value})
function regenerationChart(index){
let question = chatList.value[index - 1]
let chat = {
"chatId":uuidv4(),
"type":"question",
"content":question.content,
"time": formatDate(new Date()),
@ -340,7 +430,6 @@ function regenerationChart(index){
"robot": currentMachine.value.length>0?currentMachine.value[0]:"",
"session_id": Cookies.get("chatSessionId"),
"doc": chat.file,
"history": []
}
sendChatMessage(data)
}
@ -413,6 +502,25 @@ function downloadFile(file,bucket,sessionId){
}, file);
}
function processAuth(data){
let updateData = {
chatId: data.chatId,
action: data.action,
interrupt: JSON.stringify(data.interrupt)
}
updateChatProcessData(updateData).then(res=>{
let reqData = {
"user_id": cache.local.get("username"),
"session_id": Cookies.get("chatSessionId"),
"checkpointer": data.checkpointer,
"action": data.action,
"resume": true,
"block": data.interrupt.block
}
sendChatMessage(reqData)
})
}
async function sendChatHandle(event) {
if (!event.ctrlKey) {
// ctrl
@ -421,6 +529,7 @@ async function sendChatHandle(event) {
(chatList.value[chatList.value.length - 1].isStop ||
chatList.value[chatList.value.length - 1].isEnd))) {
chatList.value.push({
"chatId": uuidv4(),
"type": "question",
"content": inputValue.value.trim(),
"time": formatDate(new Date()),
@ -440,9 +549,14 @@ async function sendChatHandle(event) {
"user_id": cache.local.get("username"),
"robot": currentMachine.value.length > 0 ? currentMachine.value[0] : "",
"session_id": Cookies.get("chatSessionId"),
"doc": currentFiles.value,
"history": []
"doc": currentFiles.value
}
if (chatList.value.length > 1){
// 1 chatList , >1
//checkpointer
data.checkpointer = chatList.value[chatList.value.length - 2].content.checkpointer
}
currentQuestion.value = data
inputValue.value = ''
sendChatMessage(data)
}
@ -456,23 +570,73 @@ function sendChatMessage(data){
controller.value = new AbortController()
postChatMessage(data,{signal:controller.value.signal}).then(res=>{
if (res.status !== 200){
chatList.value.push({"type":"answer","content":[{"type":"text","content":"服务异常,错误码:"+res.status}],"isEnd":true,"isStop":false,"sessionId":chatList.value[0].sessionId,"sessionName":chatList.value[0].sessionName,"operate":'',"thumbDownReason":''})
chatList.value.push({"chatId":uuidv4(),"type":"answer","content":[{"type":"text","content":"服务异常,错误码:"+res.status}],"isEnd":true,"isStop":false,"sessionId":chatList.value[0].sessionId,"sessionName":chatList.value[0].sessionName,"operate":'',"thumbDownReason":''})
}else {
currentFiles.value = []
chatList.value.push({"type":"answer","content":[],"isEnd":false,"isStop":false,"sessionId":chatList.value[0].sessionId,"sessionName":chatList.value[0].sessionName, "operate":'',"thumbDownReason":''})
chatList.value.push({"chatId":uuidv4(),"type":"answer","content":[],"isEnd":false,"isStop":false,"sessionId":chatList.value[0].sessionId,"sessionName":chatList.value[0].sessionName, "operate":'',"thumbDownReason":''})
const reader = res.body.getReader()
const write = getWrite(reader)
reader.read().then(write).then(()=> {
let answer = JSON.parse(JSON.stringify(chatList.value[chatList.value.length - 1]))
answer.content = JSON.stringify(answer.content)
answer.interrupt = JSON.stringify(answer.interrupt)? answer.interrupt: null
answer.checkpointer = JSON.stringify(answer.checkpointer)
addChat(answer)
}).then(()=>{
let answer = JSON.parse(JSON.stringify(chatList.value[chatList.value.length - 1]))
if(answer.interrupt){
let robot = answer.interrupt.robot
let block = answer.interrupt.block
let action = answer.interrupt.action
let autoRequest = false
if (autoProcess.value.autoArray.length > 0 && autoProcess.value.autoArray.indexOf(robot) !== -1){
// block
autoRequest = true
if (block && block.length>0){
for (let i = 0; i < block.length; i++) {
if (block[i].required){
if (block[i].ct_type === 'dateRangePicker' || block[i].ct_type === 'checkboxGroup' || block[i].ct_type === 'multiselect'){
//default_value
if (!block[i].default_value || block[i].default_value === []){
autoRequest = false
}
}
if (block[i].ct_type === 'datePicker' || block[i].ct_type === 'input' || block[i].ct_type === 'radioGroup' ||block[i].ct_type === 'select'){
//default_value
if (!block[i].default_value || block[i].default_value.trim() === ''){
autoRequest = false
}
}
}
}
}
if (autoRequest){
let reqData = {
"user_id": cache.local.get("username"),
"session_id": Cookies.get("chatSessionId"),
"checkpointer": answer.checkpointer,
"action":"",
"resume": true,
"block": block
}
for (let i = 0; i < action.length; i++) {
if (action[i].style === 'primary'){
reqData.action = action[i].action
}
}
sendChatMessage(reqData)
}
}
}
})
}
}).catch((e) => {
chatList.value.push({"type":"answer","content":[{"type":"text","content":"服务异常"}],"isEnd":true,"isStop":false,"sessionId":chatList.value[0].sessionId,"sessionName":chatList.value[0].sessionName,"operate":"","thumbDownReason":""})
chatList.value.push({"chatId":uuidv4(),"type":"answer","content":[{"type":"text","content":"服务异常"}],"isEnd":true,"isStop":false,"sessionId":chatList.value[0].sessionId,"sessionName":chatList.value[0].sessionName,"operate":"","thumbDownReason":""})
})
}
watch(
chatList,
() => {
@ -504,7 +668,6 @@ const getWrite = (reader) => {
for (let i = 0; i < split.length; i++) {
const chunkStr = split[i];
tempResult = tempResult.replace(chunkStr, '', 1); // 4
try {
const chunk = JSON.parse(chunkStr.replace('data:', '').trim());
processChunk(chunk);
@ -512,7 +675,6 @@ const getWrite = (reader) => {
console.error('解析错误:', e, chunkStr);
}
}
//
return reader.read().then(write_stream);
} else {
@ -523,7 +685,6 @@ const getWrite = (reader) => {
const processChunk = (chunk) => {
const lastMsg = chatList.value[chatList.value.length - 1];
console.log(chunk)
// 5
if (chunk.docs?.length) {
lastMsg.content.push({ content: chunk.docs, type: "docs" });
@ -552,13 +713,15 @@ const getWrite = (reader) => {
lastMsg.content.push({ content: text, type: "text" });
}
}
if (chunk.interrupt){
lastMsg.interrupt = chunk.interrupt
}
// 7
if (chunk.isEnd || chunk.is_end) {
if (chunk.checkpointer) {
lastMsg.checkpointer = chunk.checkpointer
lastMsg.isEnd = true;
lastMsg.time = formatDate(new Date());
}
nextTick(() => scrollDiv.value.setScrollTop(getMaxHeight()));
};
@ -586,7 +749,7 @@ defineExpose({
</script>
<style lang="scss" scoped>
.ai-chat {
--padding-left: 40px;
--padding-left: 0;
height: 100%;
display: flex;
flex-direction: column;
@ -733,4 +896,54 @@ defineExpose({
margin: 0 auto;
}
}
.autoProcessClass:hover {
background-color: var(--el-color-primary-light-9);
}
:deep(.chat_question_card .el-card__body) {
padding: 10px 20px !important;
}
.returnToHere {
display: flex;
align-items: center;
cursor: pointer;
/* 文字默认隐藏 */
--text-color: transparent;
--divider-border: rgba(0,0,0,0);
}
.returnToHereIcon {
color: var(--icon-color, #000);
transition: color 0.3s;
}
.returnToHereDivider {
flex: 1;
margin: 0 10px;
--divider-border-color: var(--divider-border);
}
/* 文字默认透明 */
.divider-text {
color: var(--text-color);
transition: color 0.3s;
}
/* 鼠标移入时整体变化 */
.returnToHere:hover {
--icon-color: #409EFF; /* 蓝色 */
--text-color: #409EFF; /* 文字变蓝 */
--divider-border: #409EFF; /* 分隔线变蓝 */
}
/* 处理Element Plus分隔线边框颜色 */
:deep(.el-divider__text) {
background: transparent !important;
padding: 0 !important;
}
:deep(.el-divider__line) {
border-top-color: var(--divider-border-color) !important;
}
</style>

5
vue-fastapi-frontend/src/views/aichat/index.vue

@ -14,7 +14,7 @@
<h4 style="color: #1f2329;font-size: 16px; font-style: normal; font-weight: bold;margin: 0; -webkit-font-smoothing: antialiased">果知小助手</h4>
</div>
</div>
<div class="chat-embed__main">
<div class="chat-embed__main" style="background-color: white">
<AiChat
ref="AiChatRef"
:record="currentRecordList"
@ -134,7 +134,6 @@ function newChat() {
currentRecordList.value = []
sessionId.value = uuidv4()
Cookies.set("chatSessionId",sessionId.value)
}
function handleScroll(event) {
@ -166,6 +165,8 @@ function getChatRecord(data){
for (let i = 0; i < array.length; i++) {
if (array[i].type === 'answer'){
array[i].content = JSON.parse(array[i].content)
array[i].checkpointer = JSON.parse(array[i].checkpointer)
array[i].interrupt = JSON.parse(array[i].interrupt)
}
if (array[i].type === 'question'){
array[i].file = JSON.parse(array[i].file)

2
vue-fastapi-frontend/src/views/meta/metaInfo/bloodRelation.vue

@ -653,7 +653,7 @@ watch(
for (let i = 0; i < props.data.tableList.length; i++) {
let table = props.data.tableList[i]
let g6Tab = {
id: table.ssys_id+"-"+table.mdl_name+"-"+table.tab_eng_name,
id: table.ssys_id+"-"+table.mdl_name.toLowerCase()+"-"+table.tab_eng_name.toLowerCase(),
label: table.tab_eng_name + ((table.tab_cn_name && table.tab_cn_name.length>0)?"("+table.tab_cn_name+")":""),
attrs:[],
collapsed:true

2
vue-fastapi-frontend/src/views/meta/metaInfo/bloodRelationSql.vue

@ -660,7 +660,7 @@ watch(
for (let i = 0; i < props.data.tableList.length; i++) {
let table = props.data.tableList[i]
let g6Tab = {
id: table.ssys_id+"-"+table.mdl_name+"-"+table.tab_eng_name,
id: table.ssys_id+"-"+table.mdl_name.toLowerCase()+"-"+table.tab_eng_name.toLowerCase(),
label: table.tab_eng_name + ((table.tab_cn_name && table.tab_cn_name.length>0)?"("+table.tab_cn_name+")":""),
attrs:[],
collapsed:true

15
vue-fastapi-frontend/src/views/sqlFlow/index.vue

@ -4,8 +4,6 @@
<div class="sql-container" :style="{ width: leftWidth + 'px' }">
<!-- 工具栏 -->
<div class="toolbar">
<!-- 数据库类型 -->
<el-select
v-model="dbType"
@ -14,7 +12,7 @@
style="width: 140px; margin-left: 10px;"
>
<el-option label="MySQL" value="MYSQL" />
<el-option label="PostgreSQL" value="PG" />
<el-option label="PostgreSQL" value="POSTGRESQL" />
<el-option label="SQL Server" value="MSSQL" />
<el-option label="Oracle" value="ORACLE" />
<el-option label="DB2" value="DB2" />
@ -55,8 +53,9 @@
<!-- SQL 编辑器 -->
<SQLCodeMirror
v-model="procStr"
v-if="activeColumnTab === 'proc'"
v-model:data="procStr"
:data="procStr"
:dbType="dbType"
/>
</div>
@ -83,11 +82,13 @@ import cache from "@/plugins/cache";
const userStore = useUserStore()
const dsSysList = userStore.dsSysList
// ========================= =========================
const activeColumnTab = ref('proc')
const procStr = ref('SELECT * FROM users LIMIT 100;')
const procStr = ref('')
const dbType = ref('MYSQL')
const containerRef = ref(null)
const childRef = ref(null)
//
const selectedSystem = ref(dsSysList?.[0]?.id || null)
@ -153,16 +154,15 @@ const changeBloodOption = () => {
* 执行 SQL 并生成血缘分析图
*/
const executeSql = async () => {
if (!selectedSystem.value) {
ElMessage.warning('请选择系统')
return
}
if (!procStr.value.trim()) {
ElMessage.warning('请输入 SQL 语句')
return
}
const params = {
sqlType: dbType.value,
defaultSystem: selectedSystem.value,
@ -171,7 +171,6 @@ const executeSql = async () => {
userName: cache.local.get("username"),
password: cache.local.get("password")
}
console.log(params)
try {
ElMessage.info('正在执行血缘分析,请稍候...')
const res = await runBloodAnalysisBySql(params)

Loading…
Cancel
Save