feat: split create new chat step

This commit is contained in:
ulleo
2025-06-25 15:19:21 +08:00
parent 8968f68377
commit 72b6e117c8
5 changed files with 228 additions and 34 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ async def start_chat(session: SessionDep, current_user: CurrentUser, create_chat
return create_chat(session, current_user, create_chat_obj)
except Exception as e:
raise HTTPException(
status_code=400,
status_code=500,
detail=str(e)
)
+1 -1
View File
@@ -79,7 +79,7 @@ def create_chat(session: SessionDep, current_user: CurrentUser, create_chat_obj:
raise Exception("Datasource cannot be None")
if not create_chat_obj.question or create_chat_obj.question.strip() == '':
raise Exception("Question cannot be Empty")
create_chat_obj.question = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
chat = Chat(create_time=datetime.datetime.now(),
create_by=current_user.id,
+1 -1
View File
@@ -52,7 +52,7 @@ class ChatRecord(SQLModel, table=True):
class CreateChat(BaseModel):
id: int = None
question: str = ''
question: str = None
datasource: int = None
+153
View File
@@ -0,0 +1,153 @@
<script setup lang="ts">
import { chatApi, ChatInfo } from '@/api/chat.ts'
import { onMounted, ref } from 'vue'
import { datasourceApi } from '@/api/datasource.ts'
import DatasourceItemCard from '../ds/DatasourceItemCard.vue'
const dsList = ref<Array<any>>([])
const emits = defineEmits(['onChatCreated'])
function listDs() {
datasourceApi.list().then((res) => {
dsList.value = res
})
}
const dialogVisible = ref(false)
const DatasourceListRef = ref()
const innerDs = ref()
const loading = ref(false)
function showDs() {
listDs()
dialogVisible.value = true
}
function hideDs() {
innerDs.value = undefined
dialogVisible.value = false
}
function selectDsInDialog(ds: any) {
innerDs.value = ds.id
}
function confirmSelectDs() {
if (innerDs.value) {
createChat(innerDs.value)
}
}
function createChat(datasource: number) {
loading.value = true
chatApi
.startChat({
datasource: datasource,
})
.then((res) => {
const chat: ChatInfo | undefined = chatApi.toChatInfo(res)
if (chat == undefined) {
throw Error('chat is undefined')
}
emits('onChatCreated', chat)
hideDs()
})
.catch((e) => {
console.error(e)
})
.finally(() => {
loading.value = false
})
}
onMounted(() => {
listDs()
})
defineExpose({
showDs,
hideDs,
})
</script>
<template>
<div>
<el-drawer
ref="DatasourceListRef"
v-model="dialogVisible"
direction="btt"
:close-on-press-escape="false"
:close-on-click-modal="false"
destroy-on-close
:show-close="false"
size="100%"
>
<template #header>
<div>
<div>Choose Datasource</div>
</div>
</template>
<el-scrollbar v-loading="loading">
<div class="ds-row-container">
<template v-for="(item, _index) in dsList" :key="_index">
<DatasourceItemCard
:ds="item"
class="ds-card"
:class="[item?.id === innerDs ? 'ds-card-selected' : '']"
@click="selectDsInDialog(item)"
/>
</template>
</div>
</el-scrollbar>
<template #footer>
<div class="dialog-footer">
<el-button :disabled="loading" @click="hideDs">Cancel</el-button>
<el-button
type="primary"
:disabled="loading || innerDs === undefined"
@click="confirmSelectDs"
>
Confirm
</el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<style scoped lang="less">
.welcome-content {
padding: 12px;
}
.sub {
color: grey;
font-size: 0.8em;
}
.ds-select-row {
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-between;
}
.ds-row-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
}
.ds-card {
cursor: pointer;
}
.ds-card-selected {
box-shadow: 0 1px 3px var(--ed-color-primary-light-5);
border: 1px solid var(--ed-color-primary-light-5);
}
</style>
+72 -31
View File
@@ -24,7 +24,20 @@
</el-aside>
<el-container :loading="loading">
<el-main class="chat-record-list">
<el-scrollbar ref="chatListRef">
<div v-if="computedMessages.length == 0" class="welcome-content-block">
<div class="welcome-content">
<div class="logo">SQLBot</div>
<div>{{ t('qa.greeting') }}</div>
<div class="sub">{{ t('qa.description') }}</div>
<el-button size="large" type="primary" @click="createNewChat">
<el-icon>
<Plus />
</el-icon>
{{ t('qa.New Conversation') }}
</el-button>
</div>
</div>
<el-scrollbar v-if="computedMessages.length > 0" ref="chatListRef">
<template v-for="(message, _index) in computedMessages" :key="_index">
<ChatRow
v-model:datasource="currentChat.datasource"
@@ -83,7 +96,7 @@
</template>
</el-scrollbar>
</el-main>
<el-footer class="chat-footer">
<el-footer v-if="computedMessages.length > 0" class="chat-footer">
<div style="height: 24px">
<template v-if="currentChat.datasource && currentChat.datasource_name">
{{ t('ds.title') }}:{{ currentChat.datasource_name }}
@@ -115,6 +128,8 @@
</div>
</el-footer>
</el-container>
<ChatCreator ref="chatCreatorRef" @on-chat-created="onChatCreated" />
</el-container>
</template>
@@ -127,6 +142,7 @@ import ChatRow from './ChatRow.vue'
import ChatAnswer from './ChatAnswer.vue'
import MdComponent from './component/MdComponent.vue'
import PredictChartBlock from './component/PredictChartBlock.vue'
import ChatCreator from './ChatCreator.vue'
import { useI18n } from 'vue-i18n'
import { find } from 'lodash-es'
@@ -135,6 +151,7 @@ const { t } = useI18n()
const inputMessage = ref('')
const chatListRef = ref()
const chatCreatorRef = ref()
function scrollToBottom() {
nextTick(() => {
@@ -155,17 +172,18 @@ const isAnalysisTyping = ref<boolean>(false)
const isPredictTyping = ref<boolean>(false)
const computedMessages = computed<Array<ChatMessage>>(() => {
const welcome: ChatMessage = {
const firstMessage: ChatMessage = {
role: 'assistant',
create_time: currentChat.value?.create_time,
content: currentChat.value?.datasource,
isTyping: false,
isWelcome: true,
}
const messages: Array<ChatMessage> = [welcome]
const messages: Array<ChatMessage> = []
if (currentChatId.value === undefined) {
return messages
}
messages.push(firstMessage)
for (let i = 0; i < currentChat.value.records.length; i++) {
const record = currentChat.value.records[i]
if (record.question !== undefined) {
@@ -186,12 +204,17 @@ const computedMessages = computed<Array<ChatMessage>>(() => {
return messages
})
const createNewChat = () => {
const goEmpty = () => {
currentChat.value = new ChatInfo()
currentChatId.value = undefined
inputMessage.value = ''
}
const createNewChat = () => {
goEmpty()
chatCreatorRef.value?.showDs()
}
function getChatList() {
loading.value = true
chatApi
@@ -229,9 +252,12 @@ function onChatDeleted(id: number) {
for (let i = 0; i < chatList.value.length; i++) {
if (chatList.value[i].id === id) {
chatList.value.splice(i, 1)
return
break
}
}
if (id === currentChatId.value) {
goEmpty()
}
}
function onChatRenamed(chat: Chat) {
@@ -245,6 +271,12 @@ function onChatRenamed(chat: Chat) {
}
}
function onChatCreated(chat: ChatInfo) {
chatList.value.unshift(chat)
currentChatId.value = chat.id
currentChat.value = chat
}
onMounted(() => {
getChatList()
})
@@ -268,32 +300,9 @@ const sendMessage = async () => {
currentChat.value.records.push(currentRecord)
inputMessage.value = ''
let error = false
let error: boolean = false
if (currentChatId.value === undefined) {
await chatApi
.startChat({
question: currentRecord.question.trim(),
datasource: currentChat.value.datasource,
})
.then((res) => {
const chat = chatApi.toChatInfo(res)
if (chat !== undefined) {
chatList.value.unshift(chat)
currentChatId.value = chat.id
chat.records.push(currentRecord)
currentChat.value = chat
} else {
error = true
}
})
.catch((e) => {
isTyping.value = false
error = true
console.error(e)
})
.finally(() => {
loading.value = false
})
error = true
}
if (error) return
@@ -683,4 +692,36 @@ const handleCtrlEnter = (e: KeyboardEvent) => {
line-height: 1.7692307692;
padding: 16px 22px;
}
.welcome-content-block {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
.welcome-content {
padding: 12px;
width: fit-content;
display: flex;
gap: 16px;
align-items: center;
flex-direction: column;
.logo {
line-height: 60px;
font-size: 3em;
font-weight: bold;
color: var(--el-color-primary);
text-align: left;
}
.sub {
color: grey;
font-size: 0.8em;
}
}
}
</style>