Skip to content

Commit 4ca1adb

Browse files
authored
fix: Recommended optimization assistant problems (#554)
1 parent bf63d08 commit 4ca1adb

File tree

6 files changed

+242
-15
lines changed

6 files changed

+242
-15
lines changed

backend/apps/chat/curd/chat.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,16 @@ def list_chats(session: SessionDep, current_user: CurrentUser) -> List[Chat]:
4242

4343
def list_recent_questions(session: SessionDep, current_user: CurrentUser, datasource_id: int) -> List[str]:
4444
chat_records = (
45-
session.query(ChatRecord.question)
45+
session.query(
46+
ChatRecord.question,
47+
func.count(ChatRecord.question).label('question_count')
48+
)
4649
.filter(
4750
ChatRecord.datasource == datasource_id,
4851
ChatRecord.question.isnot(None)
4952
)
5053
.group_by(ChatRecord.question)
51-
.order_by(desc(func.max(ChatRecord.create_time)))
54+
.order_by(desc('question_count'), desc(func.max(ChatRecord.create_time)))
5255
.limit(10)
5356
.all()
5457
)

frontend/src/views/chat/QuickQuestion.vue

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<script lang="ts" setup>
2-
import { ref } from 'vue'
2+
import { onMounted, ref } from 'vue'
33
import icon_quick_question from '@/assets/svg/icon_quick_question.svg'
44
import icon_close from '@/assets/svg/operate/ope-close.svg'
55
import icon_replace_outlined from '@/assets/svg/icon_replace_outlined.svg'
6-
import RecommendQuestion from '@/views/chat/RecommendQuestion.vue'
76
import { ChatInfo } from '@/api/chat.ts'
87
import RecentQuestion from '@/views/chat/RecentQuestion.vue'
8+
import RecommendQuestionQuick from '@/views/chat/RecommendQuestionQuick.vue'
99
const activeName = ref('recommend')
1010
const recommendQuestionRef = ref()
1111
const recentQuestionRef = ref()
@@ -14,6 +14,7 @@ const getRecommendQuestions = () => {
1414
recommendQuestionRef.value.getRecommendQuestions(10)
1515
}
1616
17+
const questions = '[]'
1718
const retrieveQuestions = () => {
1819
getRecommendQuestions()
1920
recentQuestionRef.value.getRecentQuestions()
@@ -37,6 +38,10 @@ const loadingOver = () => {
3738
emits('loadingOver')
3839
}
3940
41+
onMounted(() => {
42+
getRecommendQuestions()
43+
})
44+
4045
const emits = defineEmits(['quickAsk', 'loadingOver', 'stop'])
4146
defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })
4247
@@ -45,15 +50,13 @@ const props = withDefaults(
4550
recordId?: number
4651
datasourceId?: number
4752
currentChat?: ChatInfo
48-
questions?: string
4953
firstChat?: boolean
5054
disabled?: boolean
5155
}>(),
5256
{
5357
recordId: undefined,
5458
datasourceId: undefined,
5559
currentChat: () => new ChatInfo(),
56-
questions: '[]',
5760
firstChat: false,
5861
disabled: false,
5962
}
@@ -83,7 +86,7 @@ const props = withDefaults(
8386
</el-tooltip>
8487
<el-tabs v-model="activeName" class="quick_question_tab">
8588
<el-tab-pane :label="$t('qa.recommend')" name="recommend">
86-
<RecommendQuestion
89+
<RecommendQuestionQuick
8790
ref="recommendQuestionRef"
8891
:current-chat="currentChat"
8992
:record-id="recordId"

frontend/src/views/chat/RecommendQuestion.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,23 @@ const _currentChat = computed({
3636
},
3737
})
3838
39-
const computedQuestions = computed<string>(() => {
39+
const computedQuestions = computed<string[]>(() => {
4040
if (
4141
props.questions &&
4242
props.questions.length > 0 &&
4343
startsWith(props.questions.trim(), '[') &&
4444
endsWith(props.questions.trim(), ']')
4545
) {
46-
return JSON.parse(props.questions)
46+
try {
47+
const parsedQuestions = JSON.parse(props.questions)
48+
if (Array.isArray(parsedQuestions)) {
49+
return parsedQuestions.length > 4 ? parsedQuestions.slice(0, 4) : parsedQuestions
50+
}
51+
return []
52+
} catch (error) {
53+
console.error('Failed to parse questions:', error)
54+
return []
55+
}
4756
}
4857
return []
4958
})
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<script setup lang="ts">
2+
import { computed, nextTick, onBeforeUnmount, ref } from 'vue'
3+
import { endsWith, startsWith } from 'lodash-es'
4+
import { chatApi } from '@/api/chat.ts'
5+
6+
const props = withDefaults(
7+
defineProps<{
8+
recordId?: number
9+
disabled?: boolean
10+
}>(),
11+
{
12+
recordId: undefined,
13+
disabled: false,
14+
}
15+
)
16+
17+
const emits = defineEmits(['clickQuestion', 'stop', 'loadingOver'])
18+
19+
const loading = ref(false)
20+
21+
const questions = ref('[]')
22+
23+
const computedQuestions = computed<string>(() => {
24+
if (
25+
questions.value &&
26+
questions.value.length > 0 &&
27+
startsWith(questions.value.trim(), '[') &&
28+
endsWith(questions.value.trim(), ']')
29+
) {
30+
return JSON.parse(questions.value)
31+
}
32+
return []
33+
})
34+
35+
function clickQuestion(question: string): void {
36+
if (!props.disabled) {
37+
emits('clickQuestion', question)
38+
}
39+
}
40+
41+
const stopFlag = ref(false)
42+
43+
async function getRecommendQuestions(articles_number: number) {
44+
stopFlag.value = false
45+
loading.value = true
46+
try {
47+
const controller: AbortController = new AbortController()
48+
const params = articles_number ? '?articles_number=' + articles_number : ''
49+
const response = await chatApi.recommendQuestions(props.recordId, controller, params)
50+
const reader = response.body.getReader()
51+
const decoder = new TextDecoder('utf-8')
52+
53+
let tempResult = ''
54+
55+
while (true) {
56+
if (stopFlag.value) {
57+
controller.abort()
58+
loading.value = false
59+
break
60+
}
61+
62+
const { done, value } = await reader.read()
63+
if (done) {
64+
break
65+
}
66+
67+
let chunk = decoder.decode(value, { stream: true })
68+
tempResult += chunk
69+
const split = tempResult.match(/data:.*}\n\n/g)
70+
if (split) {
71+
chunk = split.join('')
72+
tempResult = tempResult.replace(chunk, '')
73+
} else {
74+
continue
75+
}
76+
77+
if (chunk && chunk.startsWith('data:{')) {
78+
if (split) {
79+
for (const str of split) {
80+
let data
81+
try {
82+
data = JSON.parse(str.replace('data:{', '{'))
83+
} catch (err) {
84+
console.error('JSON string:', str)
85+
throw err
86+
}
87+
88+
if (data.code && data.code !== 200) {
89+
ElMessage({
90+
message: data.msg,
91+
type: 'error',
92+
showClose: true,
93+
})
94+
return
95+
}
96+
97+
switch (data.type) {
98+
case 'recommended_question':
99+
if (
100+
data.content &&
101+
data.content.length > 0 &&
102+
startsWith(data.content.trim(), '[') &&
103+
endsWith(data.content.trim(), ']')
104+
) {
105+
questions.value = data.content
106+
await nextTick()
107+
}
108+
}
109+
}
110+
}
111+
}
112+
}
113+
} finally {
114+
loading.value = false
115+
emits('loadingOver')
116+
}
117+
}
118+
119+
function stop() {
120+
stopFlag.value = true
121+
loading.value = false
122+
emits('stop')
123+
}
124+
125+
onBeforeUnmount(() => {
126+
stop()
127+
})
128+
129+
defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })
130+
</script>
131+
132+
<template>
133+
<div v-if="computedQuestions.length > 0 || loading" class="recommend-questions">
134+
<div v-if="loading">
135+
<el-button style="min-width: unset" type="primary" link loading />
136+
</div>
137+
<div v-else class="question-grid-input">
138+
<div
139+
v-for="(question, index) in computedQuestions"
140+
:key="index"
141+
class="question"
142+
:class="{ disabled: disabled }"
143+
@click="clickQuestion(question)"
144+
>
145+
{{ question }}
146+
</div>
147+
</div>
148+
</div>
149+
<div v-else class="recommend-questions-error">
150+
{{ $t('qa.retrieve_error') }}
151+
</div>
152+
</template>
153+
154+
<style scoped lang="less">
155+
.recommend-questions {
156+
width: 100%;
157+
font-size: 14px;
158+
font-weight: 500;
159+
line-height: 22px;
160+
display: flex;
161+
flex-direction: column;
162+
gap: 4px;
163+
164+
.continue-ask {
165+
color: rgba(100, 106, 115, 1);
166+
font-weight: 400;
167+
}
168+
169+
.question-grid-input {
170+
display: grid;
171+
grid-gap: 12px;
172+
grid-template-columns: repeat(1, calc(100% - 6px));
173+
}
174+
175+
.question-grid {
176+
display: grid;
177+
grid-gap: 12px;
178+
grid-template-columns: repeat(2, calc(50% - 6px));
179+
}
180+
181+
.question {
182+
font-weight: 400;
183+
cursor: pointer;
184+
background: rgba(245, 246, 247, 1);
185+
min-height: 32px;
186+
border-radius: 6px;
187+
padding: 5px 12px;
188+
line-height: 22px;
189+
&:hover {
190+
background: rgba(31, 35, 41, 0.1);
191+
}
192+
&.disabled {
193+
cursor: not-allowed;
194+
background: rgba(245, 246, 247, 1);
195+
}
196+
}
197+
}
198+
199+
.recommend-questions-error {
200+
font-size: 12px;
201+
font-weight: 500;
202+
color: rgba(100, 106, 115, 1);
203+
margin-top: 70px;
204+
display: flex;
205+
align-items: center;
206+
justify-content: center;
207+
width: 100%;
208+
}
209+
</style>

frontend/src/views/chat/answer/ChartAnswer.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ const sendMessage = async () => {
194194
case 'chart':
195195
_currentChat.value.records[index.value].chart = data.content
196196
break
197+
case 'datasource':
198+
if (!_currentChat.value.datasource) {
199+
_currentChat.value.datasource = data.id
200+
}
201+
_currentChat.value.records[index.value].chart = data.content
202+
break
197203
case 'finish':
198204
emits('finish', currentRecord.id)
199205
break

frontend/src/views/chat/index.vue

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<template>
22
<el-icon
3+
v-if="assistantStore.assistant && !assistantStore.pageEmbedded && assistantStore.type != 4"
34
class="show-history_icon"
45
:class="{ 'embedded-history-hidden': embeddedHistoryHidden }"
5-
v-if="assistantStore.assistant && !assistantStore.pageEmbedded && assistantStore.type != 4"
66
size="20"
77
@click="showFloatPopover"
88
>
@@ -374,13 +374,12 @@
374374
</span>
375375
</template>
376376
</div>
377-
<div v-if="computedMessages.length > 0" class="quick_question">
377+
<div v-if="computedMessages.length > 0 && currentChat.datasource" class="quick_question">
378378
<quick-question
379379
ref="quickQuestionRef"
380380
:datasource-id="currentChat.datasource"
381381
:current-chat="currentChat"
382382
:record-id="computedMessages[0].record?.id"
383-
:questions="computedMessages[0].recommended_question"
384383
:disabled="isTyping"
385384
:first-chat="true"
386385
@quick-ask="quickAsk"
@@ -701,9 +700,7 @@ const quickQuestionRef = ref()
701700

702701
function onChatCreated(chat: ChatInfo) {
703702
if (chat.records.length === 1 && !chat.records[0].recommended_question) {
704-
nextTick(() => {
705-
quickQuestionRef.value.getRecommendQuestions()
706-
})
703+
// do nothing
707704
}
708705
}
709706

0 commit comments

Comments
 (0)