Skip to content

Commit 3fa44ba

Browse files
authored
feat:Support quick questioning, no need to input questions, can also quickly ask common questions (#505)
1 parent 0280fe6 commit 3fa44ba

File tree

11 files changed

+334
-18
lines changed

11 files changed

+334
-18
lines changed

backend/apps/chat/api/chat.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

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

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

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

136140
@router.post("/question")
137141
async def stream_sql(session: SessionDep, current_user: CurrentUser, request_question: ChatQuestion,

backend/apps/chat/curd/chat.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import datetime
22
from typing import List
3+
from sqlalchemy import desc, func
34

45
import orjson
56
import sqlparse
@@ -35,6 +36,21 @@ def list_chats(session: SessionDep, current_user: CurrentUser) -> List[Chat]:
3536
return chart_list
3637

3738

39+
def list_recent_questions(session: SessionDep, current_user: CurrentUser, datasource_id: int) -> List[str]:
40+
chat_records = (
41+
session.query(ChatRecord.question)
42+
.filter(
43+
ChatRecord.datasource == datasource_id,
44+
ChatRecord.question.isnot(None)
45+
)
46+
.group_by(ChatRecord.question)
47+
.order_by(desc(func.max(ChatRecord.create_time)))
48+
.limit(10)
49+
.all()
50+
)
51+
return [record[0] for record in chat_records] if chat_records else []
52+
53+
3854
def rename_chat(session: SessionDep, rename_object: RenameChat) -> str:
3955
chat = session.get(Chat, rename_object.id)
4056
if not chat:

frontend/src/api/chat.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ export const chatApi = {
331331
recommendQuestions: (record_id: number | undefined, controller?: AbortController) => {
332332
return request.fetchStream(`/chat/recommend_questions/${record_id}`, {}, controller)
333333
},
334+
recentQuestions: (datasource_id?: number): Promise<any> => {
335+
return request.get(`/chat/recent_questions/${datasource_id}`)
336+
},
334337
checkLLMModel: () => request.get('/system/aimodel/default', { requestOptions: { silent: true } }),
335338
export2Excel: (record_id: number | undefined) =>
336339
request.get(`/chat/record/${record_id}/excel/export`, {
Lines changed: 1 addition & 0 deletions
Loading

frontend/src/i18n/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@
184184
"chart_selected": "Selected {0}"
185185
},
186186
"qa": {
187+
"recently": "recently",
188+
"recommend": "recommend",
189+
"quick_question": "quick question",
187190
"new_chat": "New Chat",
188191
"start_sqlbot": "New Chat",
189192
"title": "Data Q&A",

frontend/src/i18n/ko-KR.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@
184184
"chart_selected": "{0}개 선택됨"
185185
},
186186
"qa": {
187+
"recently": "최근",
188+
"recommend": "추천",
189+
"quick_question": "빠른 질문",
187190
"new_chat": "새 대화 생성",
188191
"start_sqlbot": "데이터 조회 시작",
189192
"title": "스마트 데이터 조회",

frontend/src/i18n/zh-CN.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@
184184
"chart_selected": "已选{0}"
185185
},
186186
"qa": {
187+
"recently": "最近",
188+
"recommend": "推荐",
189+
"quick_question": "快捷提问",
187190
"new_chat": "新建对话",
188191
"start_sqlbot": "开启问数",
189192
"title": "智能问数",
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<script lang="ts" setup>
2+
import { ref } from 'vue'
3+
import icon_quick_question from '@/assets/svg/icon_quick_question.svg'
4+
import { Close } from '@element-plus/icons-vue'
5+
import RecommendQuestion from '@/views/chat/RecommendQuestion.vue'
6+
import { ChatInfo } from '@/api/chat.ts'
7+
import RecentQuestion from '@/views/chat/RecentQuestion.vue'
8+
const visible = ref(false)
9+
const activeName = ref('recommend')
10+
const recommendQuestionRef = ref()
11+
12+
const getRecommendQuestions = () => {
13+
recommendQuestionRef.value.getRecommendQuestions()
14+
}
15+
const quickAsk = (question: string) => {
16+
emits('quickAsk', question)
17+
}
18+
19+
const onChatStop = () => {
20+
emits('stop')
21+
}
22+
23+
const loadingOver = () => {
24+
emits('loadingOver')
25+
visible.value = false
26+
}
27+
28+
const emits = defineEmits(['quickAsk', 'loadingOver', 'stop'])
29+
defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })
30+
31+
const props = withDefaults(
32+
defineProps<{
33+
recordId?: number
34+
datasourceId?: number
35+
currentChat?: ChatInfo
36+
questions?: string
37+
firstChat?: boolean
38+
disabled?: boolean
39+
}>(),
40+
{
41+
recordId: undefined,
42+
datasourceId: undefined,
43+
currentChat: () => new ChatInfo(),
44+
questions: '[]',
45+
firstChat: false,
46+
disabled: false,
47+
}
48+
)
49+
</script>
50+
51+
<template>
52+
<el-popover
53+
:title="$t('qa.quick_question')"
54+
:visible="visible"
55+
popper-class="quick_question_popover"
56+
placement="top-start"
57+
:width="320"
58+
>
59+
<el-icon class="close_icon"><Close @click="visible = false" /></el-icon>
60+
<el-tabs v-model="activeName" class="quick_question_tab">
61+
<el-tab-pane :label="$t('qa.recommend')" name="recommend">
62+
<RecommendQuestion
63+
ref="recommendQuestionRef"
64+
:current-chat="currentChat"
65+
:record-id="recordId"
66+
:questions="questions"
67+
:disabled="disabled"
68+
:first-chat="firstChat"
69+
position="input"
70+
@click-question="quickAsk"
71+
@stop="onChatStop"
72+
@loading-over="loadingOver"
73+
/>
74+
</el-tab-pane>
75+
<el-tab-pane v-if="datasourceId" :label="$t('qa.recently')" name="recently">
76+
<RecentQuestion v-if="visible" :datasource-id="datasourceId" @click-question="quickAsk">
77+
</RecentQuestion>
78+
</el-tab-pane>
79+
</el-tabs>
80+
<template #reference>
81+
<el-button plain size="small" @click="visible = true">
82+
<el-icon size="16" class="el-icon--left">
83+
<icon_quick_question />
84+
</el-icon>
85+
{{ $t('qa.quick_question') }}
86+
</el-button>
87+
</template>
88+
</el-popover>
89+
</template>
90+
91+
<style lang="less">
92+
.quick_question_popover {
93+
.quick_question_tab {
94+
height: 230px;
95+
}
96+
.ed-tabs__content {
97+
overflow: auto;
98+
}
99+
.ed-popover__title {
100+
font-size: 14px;
101+
font-weight: 500;
102+
margin-bottom: 0;
103+
}
104+
.close_icon {
105+
position: absolute;
106+
cursor: pointer;
107+
top: 12px;
108+
right: 12px;
109+
}
110+
.ed-tabs__item {
111+
font-size: 14px;
112+
height: 38px;
113+
}
114+
.ed-tabs__active-bar {
115+
height: 2px;
116+
}
117+
.ed-tabs__nav-wrap:after {
118+
height: 0;
119+
}
120+
.ed-tabs__content {
121+
padding-top: 12px;
122+
}
123+
}
124+
</style>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<script setup lang="ts">
2+
import { onMounted, ref } from 'vue'
3+
import { chatApi } from '@/api/chat.ts'
4+
5+
const props = withDefaults(
6+
defineProps<{
7+
datasourceId?: number
8+
}>(),
9+
{
10+
datasourceId: undefined,
11+
}
12+
)
13+
14+
const emits = defineEmits(['clickQuestion'])
15+
16+
const loading = ref(false)
17+
18+
const computedQuestions = ref([])
19+
20+
function clickQuestion(question: string): void {
21+
emits('clickQuestion', question)
22+
}
23+
24+
onMounted(() => {
25+
getRecentQuestions()
26+
})
27+
async function getRecentQuestions() {
28+
chatApi.recentQuestions(props.datasourceId).then((res) => {
29+
computedQuestions.value = res
30+
})
31+
}
32+
</script>
33+
34+
<template>
35+
<div v-if="computedQuestions.length > 0 || loading" class="recent-questions">
36+
<div class="question-grid-input">
37+
<div
38+
v-for="(question, index) in computedQuestions"
39+
:key="index"
40+
class="question"
41+
@click="clickQuestion(question)"
42+
>
43+
{{ question }}
44+
</div>
45+
</div>
46+
</div>
47+
</template>
48+
49+
<style scoped lang="less">
50+
.recent-questions {
51+
height: 100%;
52+
overflow-x: hidden;
53+
overflow-y: auto;
54+
font-size: 14px;
55+
font-weight: 500;
56+
line-height: 22px;
57+
display: flex;
58+
flex-direction: column;
59+
gap: 4px;
60+
61+
.continue-ask {
62+
color: rgba(100, 106, 115, 1);
63+
font-weight: 400;
64+
}
65+
66+
.question-grid-input {
67+
display: grid;
68+
grid-gap: 12px;
69+
grid-template-columns: repeat(1, calc(100% - 6px));
70+
}
71+
72+
.question-grid {
73+
display: grid;
74+
grid-gap: 12px;
75+
grid-template-columns: repeat(2, calc(50% - 6px));
76+
}
77+
78+
.question {
79+
font-weight: 400;
80+
cursor: pointer;
81+
background: rgba(245, 246, 247, 1);
82+
min-height: 32px;
83+
border-radius: 6px;
84+
padding: 5px 12px;
85+
line-height: 22px;
86+
&:hover {
87+
background: rgba(31, 35, 41, 0.1);
88+
}
89+
&.disabled {
90+
cursor: not-allowed;
91+
background: rgba(245, 246, 247, 1);
92+
}
93+
}
94+
}
95+
</style>

frontend/src/views/chat/RecommendQuestion.vue

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ const props = withDefaults(
1111
questions?: string
1212
firstChat?: boolean
1313
disabled?: boolean
14+
position?: string
1415
}>(),
1516
{
1617
recordId: undefined,
1718
currentChat: () => new ChatInfo(),
1819
questions: '[]',
1920
firstChat: false,
2021
disabled: false,
22+
position: 'chat',
2123
}
2224
)
2325
@@ -153,11 +155,24 @@ defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })
153155

154156
<template>
155157
<div v-if="computedQuestions.length > 0 || loading" class="recommend-questions">
156-
<div v-if="firstChat" style="margin-bottom: 8px">{{ t('qa.guess_u_ask') }}</div>
157-
<div v-else class="continue-ask">{{ t('qa.continue_to_ask') }}</div>
158+
<template v-if="position === 'chat'">
159+
<div v-if="firstChat" style="margin-bottom: 8px">{{ t('qa.guess_u_ask') }}</div>
160+
<div v-else class="continue-ask">{{ t('qa.continue_to_ask') }}</div>
161+
</template>
158162
<div v-if="loading">
159163
<el-button style="min-width: unset" type="primary" link loading />
160164
</div>
165+
<div v-else-if="position === 'input'" class="question-grid-input">
166+
<div
167+
v-for="(question, index) in computedQuestions"
168+
:key="index"
169+
class="question"
170+
:class="{ disabled: disabled }"
171+
@click="clickQuestion(question)"
172+
>
173+
{{ question }}
174+
</div>
175+
</div>
161176
<div v-else class="question-grid">
162177
<div
163178
v-for="(question, index) in computedQuestions"
@@ -186,6 +201,12 @@ defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })
186201
font-weight: 400;
187202
}
188203
204+
.question-grid-input {
205+
display: grid;
206+
grid-gap: 12px;
207+
grid-template-columns: repeat(1, calc(100% - 6px));
208+
}
209+
189210
.question-grid {
190211
display: grid;
191212
grid-gap: 12px;

0 commit comments

Comments
 (0)