Skip to content

Commit d7d28b7

Browse files
committed
feat: chat list sidebar
1 parent 7907751 commit d7d28b7

File tree

10 files changed

+511
-187
lines changed

10 files changed

+511
-187
lines changed

backend/apps/chat/task/llm.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,8 @@ def run_task(llm_service: LLMService, in_chat: bool = True):
630630
brief = rename_chat(session=llm_service.session,
631631
rename_object=RenameChat(id=llm_service.get_record().chat_id,
632632
brief=llm_service.chat_question.question.strip()[:20]))
633-
yield orjson.dumps({'type': 'brief', 'brief': brief}).decode() + '\n\n'
633+
if in_chat:
634+
yield orjson.dumps({'type': 'brief', 'brief': brief}).decode() + '\n\n'
634635

635636
# select datasource if datasource is none
636637
if not llm_service.ds:
Lines changed: 4 additions & 0 deletions
Loading

frontend/src/i18n/en.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"chart_selected": "Selected {0}"
4949
},
5050
"qa": {
51-
"New Conversation": "New Conversation",
51+
"new_chat": "New Chat",
5252
"title": "Smart Query",
5353
"placeholder": "Enter your question",
5454
"ask": "Ask",
@@ -243,4 +243,4 @@
243243
"back_community": "Restore to Community Edition",
244244
"confirm_tips": "Are you sure to restore to Community Edition?"
245245
}
246-
}
246+
}

frontend/src/i18n/zh-CN.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"chart_selected": "已选{0}"
5252
},
5353
"qa": {
54-
"New Conversation": "开启新问数",
54+
"new_chat": "新建对话",
5555
"title": "智能问数",
5656
"placeholder": "请输入您的问题",
5757
"ask": "提问",
@@ -65,7 +65,12 @@
6565
"view_more": "查看更多",
6666
"selected_datasource": "已选择数据源",
6767
"empty_datasource": "数据源为空,请新建后再开启智能问数!",
68-
"datasource_not_exist": "数据源不存在"
68+
"datasource_not_exist": "数据源不存在",
69+
"guess_u_ask": "猜你想问:",
70+
"continue_to_ask": "继续提问:",
71+
"data_analysis": "数据预测",
72+
"data_predict": "数据分析",
73+
"chat_search": "搜索"
6974
},
7075
"ds": {
7176
"title": "数据源",
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
<script setup lang="ts">
2+
import { Search } from '@element-plus/icons-vue'
3+
import ChatList from '@/views/chat/ChatList.vue'
4+
import { useI18n } from 'vue-i18n'
5+
import { computed, ref } from 'vue'
6+
import { Chat, chatApi, ChatInfo } from '@/api/chat.ts'
7+
import { filter, includes } from 'lodash-es'
8+
import ChatCreator from '@/views/chat/ChatCreator.vue'
9+
import { useAssistantStore } from '@/stores/assistant'
10+
import icon_sidebar_outlined from '@/assets/svg/icon_sidebar_outlined.svg'
11+
import icon_new_chat_outlined from '@/assets/svg/icon_new_chat_outlined.svg'
12+
13+
const props = withDefaults(
14+
defineProps<{
15+
inPopover?: boolean
16+
chatList?: Array<ChatInfo>
17+
currentChatId?: number
18+
currentChat?: ChatInfo
19+
loading?: boolean
20+
}>(),
21+
{
22+
chatList: () => [],
23+
currentChatId: undefined,
24+
currentChat: () => new ChatInfo(),
25+
loading: false,
26+
inPopover: false,
27+
}
28+
)
29+
30+
const emits = defineEmits([
31+
'goEmpty',
32+
'onChatCreated',
33+
'onClickHistory',
34+
'onChatDeleted',
35+
'onChatRenamed',
36+
'onClickSideBarBtn',
37+
'update:loading',
38+
'update:chatList',
39+
'update:currentChat',
40+
'update:currentChatId',
41+
])
42+
43+
const assistantStore = useAssistantStore()
44+
const isAssistant = computed(() => assistantStore.getAssistant)
45+
46+
const search = ref<string>()
47+
48+
const _currentChatId = computed({
49+
get() {
50+
return props.currentChatId
51+
},
52+
set(v) {
53+
emits('update:currentChatId', v)
54+
},
55+
})
56+
const _currentChat = computed({
57+
get() {
58+
return props.currentChat
59+
},
60+
set(v) {
61+
emits('update:currentChat', v)
62+
},
63+
})
64+
65+
const _chatList = computed({
66+
get() {
67+
return props.chatList
68+
},
69+
set(v) {
70+
emits('update:chatList', v)
71+
},
72+
})
73+
74+
const computedChatList = computed<Array<ChatInfo>>(() => {
75+
if (search.value && search.value.length > 0) {
76+
return filter(_chatList.value, (c) => includes(c.brief, search.value))
77+
} else {
78+
return _chatList.value
79+
}
80+
})
81+
82+
const _loading = computed({
83+
get() {
84+
return props.loading
85+
},
86+
set(v) {
87+
emits('update:loading', v)
88+
},
89+
})
90+
91+
const { t } = useI18n()
92+
93+
function onClickSideBarBtn() {
94+
emits('onClickSideBarBtn')
95+
}
96+
97+
function onChatCreated(chat: ChatInfo) {
98+
_chatList.value.unshift(chat)
99+
_currentChatId.value = chat.id
100+
_currentChat.value = chat
101+
emits('onChatCreated', chat)
102+
}
103+
104+
const chatCreatorRef = ref()
105+
106+
function goEmpty() {
107+
_currentChat.value = new ChatInfo()
108+
_currentChatId.value = undefined
109+
emits('goEmpty')
110+
}
111+
112+
const createNewChat = async () => {
113+
goEmpty()
114+
if (isAssistant.value) {
115+
const assistantChat = await assistantStore.setChat()
116+
if (assistantChat) {
117+
onChatCreated(assistantChat)
118+
}
119+
return
120+
} else {
121+
chatCreatorRef.value?.showDs()
122+
}
123+
}
124+
125+
function onClickHistory(chat: Chat) {
126+
_currentChat.value = new ChatInfo(chat)
127+
if (chat !== undefined && chat.id !== undefined && !_loading.value) {
128+
_currentChatId.value = chat.id
129+
_loading.value = true
130+
chatApi
131+
.get(chat.id)
132+
.then((res) => {
133+
const info = chatApi.toChatInfo(res)
134+
if (info) {
135+
_currentChat.value = info
136+
137+
// scrollToBottom()
138+
emits('onClickHistory', info)
139+
}
140+
})
141+
.finally(() => {
142+
_loading.value = false
143+
})
144+
}
145+
}
146+
147+
function onChatDeleted(id: number) {
148+
for (let i = 0; i < _chatList.value.length; i++) {
149+
if (_chatList.value[i].id === id) {
150+
_chatList.value.splice(i, 1)
151+
break
152+
}
153+
}
154+
if (id === _currentChatId.value) {
155+
goEmpty()
156+
}
157+
emits('onChatDeleted', id)
158+
}
159+
160+
function onChatRenamed(chat: Chat) {
161+
_chatList.value.forEach((c: Chat) => {
162+
if (c.id === chat.id) {
163+
c.brief = chat.brief
164+
}
165+
})
166+
if (_currentChat.value.id === chat.id) {
167+
_currentChat.value.brief = chat.brief
168+
}
169+
emits('onChatRenamed', chat)
170+
}
171+
</script>
172+
173+
<template>
174+
<el-container class="chat-container-right-container">
175+
<el-header class="chat-list-header" :class="{ 'in-popover': inPopover }">
176+
<div v-if="!inPopover" class="title">
177+
<div>{{ t('qa.title') }}</div>
178+
<el-button link type="primary" class="icon-btn" @click="onClickSideBarBtn">
179+
<el-icon>
180+
<icon_sidebar_outlined />
181+
</el-icon>
182+
</el-button>
183+
</div>
184+
<el-button class="btn" type="primary" @click="createNewChat">
185+
<el-icon style="margin-right: 6px">
186+
<icon_new_chat_outlined />
187+
</el-icon>
188+
{{ t('qa.new_chat') }}
189+
</el-button>
190+
<el-input
191+
v-model="search"
192+
:prefix-icon="Search"
193+
class="search"
194+
name="quick-search"
195+
autocomplete="off"
196+
:placeholder="t('qa.chat_search')"
197+
/>
198+
</el-header>
199+
<el-main class="chat-list">
200+
<ChatList
201+
v-model:loading="_loading"
202+
:current-chat-id="_currentChatId"
203+
:chat-list="computedChatList"
204+
@chat-selected="onClickHistory"
205+
@chat-deleted="onChatDeleted"
206+
@chat-renamed="onChatRenamed"
207+
/>
208+
</el-main>
209+
210+
<ChatCreator v-if="!isAssistant" ref="chatCreatorRef" @on-chat-created="onChatCreated" />
211+
</el-container>
212+
</template>
213+
214+
<style scoped lang="less">
215+
.chat-container-right-container {
216+
background: rgba(245, 246, 247, 1);
217+
218+
height: 100%;
219+
220+
.icon-btn {
221+
min-width: unset;
222+
width: 26px;
223+
height: 26px;
224+
font-size: 18px;
225+
226+
&:hover {
227+
background: rgba(31, 35, 41, 0.1);
228+
}
229+
}
230+
231+
.chat-list-header {
232+
--ed-header-padding: 16px;
233+
--ed-header-height: calc(16px + 24px + 16px + 40px + 16px + 32px + 16px);
234+
235+
&.in-popover {
236+
--ed-header-height: calc(16px + 40px + 16px + 32px + 16px);
237+
}
238+
239+
display: flex;
240+
align-items: center;
241+
justify-content: center;
242+
flex-direction: column;
243+
gap: 16px;
244+
245+
.title {
246+
height: 24px;
247+
width: 100%;
248+
display: flex;
249+
flex-direction: row;
250+
align-items: center;
251+
justify-content: space-between;
252+
font-weight: 500;
253+
}
254+
255+
.btn {
256+
width: 100%;
257+
height: 40px;
258+
259+
font-size: 16px;
260+
font-weight: 500;
261+
262+
--ed-button-text-color: rgba(28, 186, 144, 1);
263+
--ed-button-bg-color: rgba(28, 186, 144, 0.1);
264+
--ed-button-border-color: rgba(164, 227, 211, 1);
265+
--ed-button-hover-bg-color: rgba(210, 241, 233, 1);
266+
--ed-button-hover-text-color: rgba(28, 186, 144, 1);
267+
--ed-button-hover-border-color: rgba(28, 186, 144, 1);
268+
--ed-button-active-bg-color: rgba(164, 227, 211, 1);
269+
--ed-button-active-border-color: rgba(28, 186, 144, 1);
270+
}
271+
272+
.search {
273+
height: 32px;
274+
width: 100%;
275+
}
276+
}
277+
278+
.chat-list {
279+
padding: 0 0 20px 0;
280+
}
281+
}
282+
</style>

frontend/src/views/chat/ChatRecordFirst.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
<script setup lang="ts">
2-
import ChatBlock from './ChatBlock.vue'
32
import { ChatInfo, type ChatMessage } from '@/api/chat.ts'
43
import { computed } from 'vue'
5-
import { useI18n } from 'vue-i18n'
64
7-
const { t } = useI18n()
85
const props = defineProps<{
96
msg: ChatMessage
107
currentChat: ChatInfo
@@ -16,7 +13,10 @@ const show = computed(() => {
1613
</script>
1714

1815
<template>
19-
<ChatBlock v-if="show">
16+
<span v-if="show">
17+
<slot name="footer"></slot>
18+
</span>
19+
<!-- <ChatBlock v-if="show">
2020
<div class="welcome-content">
2121
<div class="ds-select-row">
2222
<div>{{ t('qa.selected_datasource') }}:</div>
@@ -28,7 +28,7 @@ const show = computed(() => {
2828
<template #footer>
2929
<slot name="footer"></slot>
3030
</template>
31-
</ChatBlock>
31+
</ChatBlock>-->
3232
</template>
3333

3434
<style scoped lang="less">

0 commit comments

Comments
 (0)