Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion backend/apps/chat/api/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from apps.chat.curd.chat import list_chats, get_chat_with_records, create_chat, rename_chat, \
delete_chat, get_chat_chart_data, get_chat_predict_data, get_chat_with_records_with_data, get_chat_record_by_id, \
format_json_data, format_json_list_data, get_chart_config
format_json_data, format_json_list_data, get_chart_config, list_recent_questions
from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, ChatQuestion, AxisObj
from apps.chat.task.llm import LLMService
from common.core.deps import CurrentAssistant, SessionDep, CurrentUser, Trans
Expand Down Expand Up @@ -132,6 +132,10 @@ def _err(_e: Exception):

return StreamingResponse(llm_service.await_result(), media_type="text/event-stream")

@router.get("/recent_questions/{datasource_id}")
async def recommend_questions(session: SessionDep, current_user: CurrentUser, datasource_id: int):
return list_recent_questions(session=session, current_user=current_user, datasource_id=datasource_id)


@router.post("/question")
async def stream_sql(session: SessionDep, current_user: CurrentUser, request_question: ChatQuestion,
Expand Down
16 changes: 16 additions & 0 deletions backend/apps/chat/curd/chat.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
from typing import List
from sqlalchemy import desc, func

import orjson
import sqlparse
Expand Down Expand Up @@ -35,6 +36,21 @@ def list_chats(session: SessionDep, current_user: CurrentUser) -> List[Chat]:
return chart_list


def list_recent_questions(session: SessionDep, current_user: CurrentUser, datasource_id: int) -> List[str]:
chat_records = (
session.query(ChatRecord.question)
.filter(
ChatRecord.datasource == datasource_id,
ChatRecord.question.isnot(None)
)
.group_by(ChatRecord.question)
.order_by(desc(func.max(ChatRecord.create_time)))
.limit(10)
.all()
)
return [record[0] for record in chat_records] if chat_records else []


def rename_chat(session: SessionDep, rename_object: RenameChat) -> str:
chat = session.get(Chat, rename_object.id)
if not chat:
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/api/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ export const chatApi = {
recommendQuestions: (record_id: number | undefined, controller?: AbortController) => {
return request.fetchStream(`/chat/recommend_questions/${record_id}`, {}, controller)
},
recentQuestions: (datasource_id?: number): Promise<any> => {
return request.get(`/chat/recent_questions/${datasource_id}`)
},
checkLLMModel: () => request.get('/system/aimodel/default', { requestOptions: { silent: true } }),
export2Excel: (record_id: number | undefined) =>
request.get(`/chat/record/${record_id}/excel/export`, {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/assets/svg/icon_quick_question.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@
"chart_selected": "Selected {0}"
},
"qa": {
"recently": "recently",
"recommend": "recommend",
"quick_question": "quick question",
"new_chat": "New Chat",
"start_sqlbot": "New Chat",
"title": "Data Q&A",
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/i18n/ko-KR.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@
"chart_selected": "{0}개 선택됨"
},
"qa": {
"recently": "최근",
"recommend": "추천",
"quick_question": "빠른 질문",
"new_chat": "새 대화 생성",
"start_sqlbot": "데이터 조회 시작",
"title": "스마트 데이터 조회",
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@
"chart_selected": "已选{0}"
},
"qa": {
"recently": "最近",
"recommend": "推荐",
"quick_question": "快捷提问",
"new_chat": "新建对话",
"start_sqlbot": "开启问数",
"title": "智能问数",
Expand Down
124 changes: 124 additions & 0 deletions frontend/src/views/chat/QuickQuestion.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<script lang="ts" setup>
import { ref } from 'vue'
import icon_quick_question from '@/assets/svg/icon_quick_question.svg'
import { Close } from '@element-plus/icons-vue'
import RecommendQuestion from '@/views/chat/RecommendQuestion.vue'
import { ChatInfo } from '@/api/chat.ts'
import RecentQuestion from '@/views/chat/RecentQuestion.vue'
const visible = ref(false)
const activeName = ref('recommend')
const recommendQuestionRef = ref()

const getRecommendQuestions = () => {
recommendQuestionRef.value.getRecommendQuestions()
}
const quickAsk = (question: string) => {
emits('quickAsk', question)
}

const onChatStop = () => {
emits('stop')
}

const loadingOver = () => {
emits('loadingOver')
visible.value = false
}

const emits = defineEmits(['quickAsk', 'loadingOver', 'stop'])
defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })

const props = withDefaults(
defineProps<{
recordId?: number
datasourceId?: number
currentChat?: ChatInfo
questions?: string
firstChat?: boolean
disabled?: boolean
}>(),
{
recordId: undefined,
datasourceId: undefined,
currentChat: () => new ChatInfo(),
questions: '[]',
firstChat: false,
disabled: false,
}
)
</script>

<template>
<el-popover
:title="$t('qa.quick_question')"
:visible="visible"
popper-class="quick_question_popover"
placement="top-start"
:width="320"
>
<el-icon class="close_icon"><Close @click="visible = false" /></el-icon>
<el-tabs v-model="activeName" class="quick_question_tab">
<el-tab-pane :label="$t('qa.recommend')" name="recommend">
<RecommendQuestion
ref="recommendQuestionRef"
:current-chat="currentChat"
:record-id="recordId"
:questions="questions"
:disabled="disabled"
:first-chat="firstChat"
position="input"
@click-question="quickAsk"
@stop="onChatStop"
@loading-over="loadingOver"
/>
</el-tab-pane>
<el-tab-pane v-if="datasourceId" :label="$t('qa.recently')" name="recently">
<RecentQuestion v-if="visible" :datasource-id="datasourceId" @click-question="quickAsk">
</RecentQuestion>
</el-tab-pane>
</el-tabs>
<template #reference>
<el-button plain size="small" @click="visible = true">
<el-icon size="16" class="el-icon--left">
<icon_quick_question />
</el-icon>
{{ $t('qa.quick_question') }}
</el-button>
</template>
</el-popover>
</template>

<style lang="less">
.quick_question_popover {
.quick_question_tab {
height: 230px;
}
.ed-tabs__content {
overflow: auto;
}
.ed-popover__title {
font-size: 14px;
font-weight: 500;
margin-bottom: 0;
}
.close_icon {
position: absolute;
cursor: pointer;
top: 12px;
right: 12px;
}
.ed-tabs__item {
font-size: 14px;
height: 38px;
}
.ed-tabs__active-bar {
height: 2px;
}
.ed-tabs__nav-wrap:after {
height: 0;
}
.ed-tabs__content {
padding-top: 12px;
}
}
</style>
95 changes: 95 additions & 0 deletions frontend/src/views/chat/RecentQuestion.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { chatApi } from '@/api/chat.ts'

const props = withDefaults(
defineProps<{
datasourceId?: number
}>(),
{
datasourceId: undefined,
}
)

const emits = defineEmits(['clickQuestion'])

const loading = ref(false)

const computedQuestions = ref([])

function clickQuestion(question: string): void {
emits('clickQuestion', question)
}

onMounted(() => {
getRecentQuestions()
})
async function getRecentQuestions() {
chatApi.recentQuestions(props.datasourceId).then((res) => {
computedQuestions.value = res
})
}
</script>

<template>
<div v-if="computedQuestions.length > 0 || loading" class="recent-questions">
<div class="question-grid-input">
<div
v-for="(question, index) in computedQuestions"
:key="index"
class="question"
@click="clickQuestion(question)"
>
{{ question }}
</div>
</div>
</div>
</template>

<style scoped lang="less">
.recent-questions {
height: 100%;
overflow-x: hidden;
overflow-y: auto;
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);
}
}
}
</style>
25 changes: 23 additions & 2 deletions frontend/src/views/chat/RecommendQuestion.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ const props = withDefaults(
questions?: string
firstChat?: boolean
disabled?: boolean
position?: string
}>(),
{
recordId: undefined,
currentChat: () => new ChatInfo(),
questions: '[]',
firstChat: false,
disabled: false,
position: 'chat',
}
)

Expand Down Expand Up @@ -153,11 +155,24 @@ defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })

<template>
<div v-if="computedQuestions.length > 0 || loading" class="recommend-questions">
<div v-if="firstChat" style="margin-bottom: 8px">{{ t('qa.guess_u_ask') }}</div>
<div v-else class="continue-ask">{{ t('qa.continue_to_ask') }}</div>
<template v-if="position === 'chat'">
<div v-if="firstChat" style="margin-bottom: 8px">{{ t('qa.guess_u_ask') }}</div>
<div v-else class="continue-ask">{{ t('qa.continue_to_ask') }}</div>
</template>
<div v-if="loading">
<el-button style="min-width: unset" type="primary" link loading />
</div>
<div v-else-if="position === 'input'" 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 v-else class="question-grid">
<div
v-for="(question, index) in computedQuestions"
Expand Down Expand Up @@ -186,6 +201,12 @@ defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })
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;
Expand Down
Loading