mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
fix: Recommended optimization assistant problems (#554)
This commit is contained in:
@@ -42,13 +42,16 @@ def list_chats(session: SessionDep, current_user: CurrentUser) -> List[Chat]:
|
||||
|
||||
def list_recent_questions(session: SessionDep, current_user: CurrentUser, datasource_id: int) -> List[str]:
|
||||
chat_records = (
|
||||
session.query(ChatRecord.question)
|
||||
session.query(
|
||||
ChatRecord.question,
|
||||
func.count(ChatRecord.question).label('question_count')
|
||||
)
|
||||
.filter(
|
||||
ChatRecord.datasource == datasource_id,
|
||||
ChatRecord.question.isnot(None)
|
||||
)
|
||||
.group_by(ChatRecord.question)
|
||||
.order_by(desc(func.max(ChatRecord.create_time)))
|
||||
.order_by(desc('question_count'), desc(func.max(ChatRecord.create_time)))
|
||||
.limit(10)
|
||||
.all()
|
||||
)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import icon_quick_question from '@/assets/svg/icon_quick_question.svg'
|
||||
import icon_close from '@/assets/svg/operate/ope-close.svg'
|
||||
import icon_replace_outlined from '@/assets/svg/icon_replace_outlined.svg'
|
||||
import RecommendQuestion from '@/views/chat/RecommendQuestion.vue'
|
||||
import { ChatInfo } from '@/api/chat.ts'
|
||||
import RecentQuestion from '@/views/chat/RecentQuestion.vue'
|
||||
import RecommendQuestionQuick from '@/views/chat/RecommendQuestionQuick.vue'
|
||||
const activeName = ref('recommend')
|
||||
const recommendQuestionRef = ref()
|
||||
const recentQuestionRef = ref()
|
||||
@@ -14,6 +14,7 @@ const getRecommendQuestions = () => {
|
||||
recommendQuestionRef.value.getRecommendQuestions(10)
|
||||
}
|
||||
|
||||
const questions = '[]'
|
||||
const retrieveQuestions = () => {
|
||||
getRecommendQuestions()
|
||||
recentQuestionRef.value.getRecentQuestions()
|
||||
@@ -37,6 +38,10 @@ const loadingOver = () => {
|
||||
emits('loadingOver')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getRecommendQuestions()
|
||||
})
|
||||
|
||||
const emits = defineEmits(['quickAsk', 'loadingOver', 'stop'])
|
||||
defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })
|
||||
|
||||
@@ -45,7 +50,6 @@ const props = withDefaults(
|
||||
recordId?: number
|
||||
datasourceId?: number
|
||||
currentChat?: ChatInfo
|
||||
questions?: string
|
||||
firstChat?: boolean
|
||||
disabled?: boolean
|
||||
}>(),
|
||||
@@ -53,7 +57,6 @@ const props = withDefaults(
|
||||
recordId: undefined,
|
||||
datasourceId: undefined,
|
||||
currentChat: () => new ChatInfo(),
|
||||
questions: '[]',
|
||||
firstChat: false,
|
||||
disabled: false,
|
||||
}
|
||||
@@ -83,7 +86,7 @@ const props = withDefaults(
|
||||
</el-tooltip>
|
||||
<el-tabs v-model="activeName" class="quick_question_tab">
|
||||
<el-tab-pane :label="$t('qa.recommend')" name="recommend">
|
||||
<RecommendQuestion
|
||||
<RecommendQuestionQuick
|
||||
ref="recommendQuestionRef"
|
||||
:current-chat="currentChat"
|
||||
:record-id="recordId"
|
||||
|
||||
@@ -36,14 +36,23 @@ const _currentChat = computed({
|
||||
},
|
||||
})
|
||||
|
||||
const computedQuestions = computed<string>(() => {
|
||||
const computedQuestions = computed<string[]>(() => {
|
||||
if (
|
||||
props.questions &&
|
||||
props.questions.length > 0 &&
|
||||
startsWith(props.questions.trim(), '[') &&
|
||||
endsWith(props.questions.trim(), ']')
|
||||
) {
|
||||
return JSON.parse(props.questions)
|
||||
try {
|
||||
const parsedQuestions = JSON.parse(props.questions)
|
||||
if (Array.isArray(parsedQuestions)) {
|
||||
return parsedQuestions.length > 4 ? parsedQuestions.slice(0, 4) : parsedQuestions
|
||||
}
|
||||
return []
|
||||
} catch (error) {
|
||||
console.error('Failed to parse questions:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, ref } from 'vue'
|
||||
import { endsWith, startsWith } from 'lodash-es'
|
||||
import { chatApi } from '@/api/chat.ts'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
recordId?: number
|
||||
disabled?: boolean
|
||||
}>(),
|
||||
{
|
||||
recordId: undefined,
|
||||
disabled: false,
|
||||
}
|
||||
)
|
||||
|
||||
const emits = defineEmits(['clickQuestion', 'stop', 'loadingOver'])
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const questions = ref('[]')
|
||||
|
||||
const computedQuestions = computed<string>(() => {
|
||||
if (
|
||||
questions.value &&
|
||||
questions.value.length > 0 &&
|
||||
startsWith(questions.value.trim(), '[') &&
|
||||
endsWith(questions.value.trim(), ']')
|
||||
) {
|
||||
return JSON.parse(questions.value)
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
function clickQuestion(question: string): void {
|
||||
if (!props.disabled) {
|
||||
emits('clickQuestion', question)
|
||||
}
|
||||
}
|
||||
|
||||
const stopFlag = ref(false)
|
||||
|
||||
async function getRecommendQuestions(articles_number: number) {
|
||||
stopFlag.value = false
|
||||
loading.value = true
|
||||
try {
|
||||
const controller: AbortController = new AbortController()
|
||||
const params = articles_number ? '?articles_number=' + articles_number : ''
|
||||
const response = await chatApi.recommendQuestions(props.recordId, controller, params)
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder('utf-8')
|
||||
|
||||
let tempResult = ''
|
||||
|
||||
while (true) {
|
||||
if (stopFlag.value) {
|
||||
controller.abort()
|
||||
loading.value = false
|
||||
break
|
||||
}
|
||||
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
break
|
||||
}
|
||||
|
||||
let chunk = decoder.decode(value, { stream: true })
|
||||
tempResult += chunk
|
||||
const split = tempResult.match(/data:.*}\n\n/g)
|
||||
if (split) {
|
||||
chunk = split.join('')
|
||||
tempResult = tempResult.replace(chunk, '')
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
if (chunk && chunk.startsWith('data:{')) {
|
||||
if (split) {
|
||||
for (const str of split) {
|
||||
let data
|
||||
try {
|
||||
data = JSON.parse(str.replace('data:{', '{'))
|
||||
} catch (err) {
|
||||
console.error('JSON string:', str)
|
||||
throw err
|
||||
}
|
||||
|
||||
if (data.code && data.code !== 200) {
|
||||
ElMessage({
|
||||
message: data.msg,
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case 'recommended_question':
|
||||
if (
|
||||
data.content &&
|
||||
data.content.length > 0 &&
|
||||
startsWith(data.content.trim(), '[') &&
|
||||
endsWith(data.content.trim(), ']')
|
||||
) {
|
||||
questions.value = data.content
|
||||
await nextTick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
emits('loadingOver')
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
stopFlag.value = true
|
||||
loading.value = false
|
||||
emits('stop')
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stop()
|
||||
})
|
||||
|
||||
defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="computedQuestions.length > 0 || loading" class="recommend-questions">
|
||||
<div v-if="loading">
|
||||
<el-button style="min-width: unset" type="primary" link loading />
|
||||
</div>
|
||||
<div v-else class="question-grid-input">
|
||||
<div
|
||||
v-for="(question, index) in computedQuestions"
|
||||
:key="index"
|
||||
class="question"
|
||||
:class="{ disabled: disabled }"
|
||||
@click="clickQuestion(question)"
|
||||
>
|
||||
{{ question }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="recommend-questions-error">
|
||||
{{ $t('qa.retrieve_error') }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.recommend-questions {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.continue-ask {
|
||||
color: rgba(100, 106, 115, 1);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.question-grid-input {
|
||||
display: grid;
|
||||
grid-gap: 12px;
|
||||
grid-template-columns: repeat(1, calc(100% - 6px));
|
||||
}
|
||||
|
||||
.question-grid {
|
||||
display: grid;
|
||||
grid-gap: 12px;
|
||||
grid-template-columns: repeat(2, calc(50% - 6px));
|
||||
}
|
||||
|
||||
.question {
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
background: rgba(245, 246, 247, 1);
|
||||
min-height: 32px;
|
||||
border-radius: 6px;
|
||||
padding: 5px 12px;
|
||||
line-height: 22px;
|
||||
&:hover {
|
||||
background: rgba(31, 35, 41, 0.1);
|
||||
}
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
background: rgba(245, 246, 247, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recommend-questions-error {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: rgba(100, 106, 115, 1);
|
||||
margin-top: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -194,6 +194,12 @@ const sendMessage = async () => {
|
||||
case 'chart':
|
||||
_currentChat.value.records[index.value].chart = data.content
|
||||
break
|
||||
case 'datasource':
|
||||
if (!_currentChat.value.datasource) {
|
||||
_currentChat.value.datasource = data.id
|
||||
}
|
||||
_currentChat.value.records[index.value].chart = data.content
|
||||
break
|
||||
case 'finish':
|
||||
emits('finish', currentRecord.id)
|
||||
break
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<el-icon
|
||||
v-if="assistantStore.assistant && !assistantStore.pageEmbedded && assistantStore.type != 4"
|
||||
class="show-history_icon"
|
||||
:class="{ 'embedded-history-hidden': embeddedHistoryHidden }"
|
||||
v-if="assistantStore.assistant && !assistantStore.pageEmbedded && assistantStore.type != 4"
|
||||
size="20"
|
||||
@click="showFloatPopover"
|
||||
>
|
||||
@@ -374,13 +374,12 @@
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="computedMessages.length > 0" class="quick_question">
|
||||
<div v-if="computedMessages.length > 0 && currentChat.datasource" class="quick_question">
|
||||
<quick-question
|
||||
ref="quickQuestionRef"
|
||||
:datasource-id="currentChat.datasource"
|
||||
:current-chat="currentChat"
|
||||
:record-id="computedMessages[0].record?.id"
|
||||
:questions="computedMessages[0].recommended_question"
|
||||
:disabled="isTyping"
|
||||
:first-chat="true"
|
||||
@quick-ask="quickAsk"
|
||||
@@ -701,9 +700,7 @@ const quickQuestionRef = ref()
|
||||
|
||||
function onChatCreated(chat: ChatInfo) {
|
||||
if (chat.records.length === 1 && !chat.records[0].recommended_question) {
|
||||
nextTick(() => {
|
||||
quickQuestionRef.value.getRecommendQuestions()
|
||||
})
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user