Skip to content

Commit c6210ee

Browse files
feat: Data Q&A page
1 parent c3c8507 commit c6210ee

File tree

2 files changed

+357
-5
lines changed

2 files changed

+357
-5
lines changed

frontend/src/router/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const router = createRouter({
2424
path: 'index',
2525
name: 'chat',
2626
component: chat,
27-
meta: { title: 'ChatBI', icon: 'chat'}
27+
meta: { title: 'Data Q&A', icon: 'chat'}
2828
}
2929
]
3030
},
@@ -37,7 +37,7 @@ const router = createRouter({
3737
path: 'index',
3838
name: 'ds',
3939
component: ds,
40-
meta: { title: 'Datasource', icon: 'ds' }
40+
meta: { title: 'Data Connections', icon: 'ds' }
4141
}
4242
]
4343
},
@@ -63,7 +63,7 @@ const router = createRouter({
6363
path: 'index',
6464
name: 'setting',
6565
component: setting,
66-
meta: { title: 'Setting', icon: 'setting' }
66+
meta: { title: 'Settings', icon: 'setting' }
6767
}
6868
]
6969
},

frontend/src/views/chat/index.vue

Lines changed: 354 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,355 @@
11
<template>
2-
<h2>this is chatbi page</h2>
3-
</template>
2+
<div class="chat-container">
3+
<!-- Sidebar for chat history -->
4+
<div class="chat-sidebar">
5+
<div class="sidebar-header">
6+
<el-button type="primary" class="new-chat" @click="createNewChat">
7+
<el-icon><Plus /></el-icon>
8+
New Conversation
9+
</el-button>
10+
<div class="search-box">
11+
<el-input
12+
v-model="searchKeyword"
13+
placeholder="Search conversation history"
14+
:prefix-icon="Search"
15+
/>
16+
</div>
17+
</div>
18+
19+
<div class="history-list">
20+
<div
21+
v-for="chat in filteredHistory"
22+
:key="chat.id"
23+
class="history-item"
24+
:class="{ active: currentChatId === chat.id }"
25+
@click="switchChat(chat.id)"
26+
>
27+
<span class="chat-title">{{ chat.title }}</span>
28+
<el-dropdown trigger="hover" @command="handleCommand($event, chat.id)">
29+
<span class="more-actions">
30+
<el-icon><More /></el-icon>
31+
</span>
32+
<template #dropdown>
33+
<el-dropdown-menu>
34+
<el-dropdown-item command="rename">Rename</el-dropdown-item>
35+
<el-dropdown-item command="delete" divided>Delete</el-dropdown-item>
36+
</el-dropdown-menu>
37+
</template>
38+
</el-dropdown>
39+
</div>
40+
</div>
41+
</div>
42+
43+
<!-- Main chat area -->
44+
<div class="chat-main">
45+
<div class="chat-messages" ref="messagesRef">
46+
<template v-if="currentChat?.messages?.length">
47+
<div
48+
v-for="(msg, index) in currentChat.messages"
49+
:key="index"
50+
class="message"
51+
:class="msg.role"
52+
>
53+
<div class="message-content">{{ msg.content }}</div>
54+
</div>
55+
</template>
56+
<div v-else class="empty-state">
57+
<p>Welcome to SQLBot</p>
58+
<p class="sub-text">You can ask questions in natural language, for example:</p>
59+
<div class="examples">
60+
<div class="example-item">View this month's sales growth compared to last month</div>
61+
<div class="example-item">Analyze sales staff performance by region</div>
62+
<div class="example-item">Calculate the relationship between customer purchase frequency and average order value</div>
63+
</div>
64+
</div>
65+
</div>
66+
67+
<div class="chat-input">
68+
<div class="input-wrapper">
69+
<el-input
70+
v-model="inputMessage"
71+
type="textarea"
72+
:rows="1"
73+
:autosize="{ minRows: 1, maxRows: 8 }"
74+
placeholder="Press Enter to send, Ctrl + Enter for new line"
75+
@keydown.enter.exact.prevent="sendMessage"
76+
@keydown.ctrl.enter.exact.prevent="handleCtrlEnter"
77+
/>
78+
<div class="input-actions">
79+
<el-button circle type="primary" class="send-btn" @click="sendMessage">
80+
<el-icon><Position /></el-icon>
81+
</el-button>
82+
</div>
83+
</div>
84+
</div>
85+
</div>
86+
</div>
87+
</template>
88+
89+
<script setup lang="ts">
90+
import { ref, computed, nextTick } from 'vue'
91+
import { Plus, Search, More, Position } from '@element-plus/icons-vue'
92+
93+
interface ChatMessage {
94+
role: 'user' | 'assistant'
95+
content: string
96+
}
97+
98+
interface Chat {
99+
id: string
100+
title: string
101+
messages: ChatMessage[]
102+
}
103+
104+
const chatHistory = ref<Chat[]>([])
105+
const currentChatId = ref('')
106+
const searchKeyword = ref('')
107+
const inputMessage = ref('')
108+
109+
const filteredHistory = computed(() => {
110+
return chatHistory.value.filter(chat =>
111+
chat.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
112+
)
113+
})
114+
115+
const currentChat = computed(() => {
116+
return chatHistory.value.find(chat => chat.id === currentChatId.value)
117+
})
118+
119+
const createNewChat = () => {
120+
const newChat: Chat = {
121+
id: Date.now().toString(),
122+
title: `New Chat ${chatHistory.value.length + 1}`,
123+
messages: []
124+
}
125+
chatHistory.value.unshift(newChat)
126+
currentChatId.value = newChat.id
127+
}
128+
129+
const switchChat = (chatId: string) => {
130+
currentChatId.value = chatId
131+
}
132+
133+
const sendMessage = () => {
134+
if (!inputMessage.value.trim()) return
135+
136+
const chat = currentChat.value
137+
if (!chat) return
138+
139+
chat.messages.push({
140+
role: 'user',
141+
content: inputMessage.value
142+
})
143+
144+
145+
inputMessage.value = ''
146+
}
147+
148+
const handleCommand = (command: string, chatId: string) => {
149+
switch (command) {
150+
case 'rename':
151+
break
152+
case 'delete':
153+
chatHistory.value = chatHistory.value.filter(chat => chat.id !== chatId)
154+
if (currentChatId.value === chatId) {
155+
currentChatId.value = chatHistory.value[0]?.id || ''
156+
}
157+
break
158+
}
159+
}
160+
161+
const handleCtrlEnter = (e: KeyboardEvent) => {
162+
const textarea = e.target as HTMLTextAreaElement
163+
const start = textarea.selectionStart
164+
const end = textarea.selectionEnd
165+
const value = textarea.value
166+
167+
inputMessage.value = value.substring(0, start) + '\n' + value.substring(end)
168+
169+
nextTick(() => {
170+
textarea.selectionStart = textarea.selectionEnd = start + 1
171+
})
172+
}
173+
</script>
174+
175+
<style lang="less" scoped>
176+
.chat-container {
177+
height: 100%;
178+
display: flex;
179+
background-color: var(--white);
180+
border-radius: var(--border-radius);
181+
box-shadow: var(--shadow);
182+
overflow: hidden;
183+
184+
.chat-sidebar {
185+
width: 260px;
186+
border-right: 1px solid #e6e6e6;
187+
display: flex;
188+
flex-direction: column;
189+
190+
.sidebar-header {
191+
padding: 16px;
192+
border-bottom: 1px solid #e6e6e6;
193+
194+
.new-chat {
195+
width: 100%;
196+
margin-bottom: 12px;
197+
}
198+
199+
.search-box {
200+
:deep(.el-input__wrapper) {
201+
background-color: #f5f5f5;
202+
}
203+
}
204+
}
205+
206+
.history-list {
207+
flex: 1;
208+
overflow-y: auto;
209+
padding: 8px;
210+
211+
.history-item {
212+
display: flex;
213+
align-items: center;
214+
justify-content: space-between;
215+
padding: 8px 12px;
216+
margin-bottom: 4px;
217+
border-radius: 6px;
218+
cursor: pointer;
219+
220+
&:hover {
221+
background-color: #f5f5f5;
222+
}
223+
224+
&.active {
225+
background-color: #e6f4ff;
226+
}
227+
228+
.chat-title {
229+
flex: 1;
230+
overflow: hidden;
231+
text-overflow: ellipsis;
232+
white-space: nowrap;
233+
}
234+
235+
.more-actions {
236+
opacity: 0;
237+
padding: 4px;
238+
}
239+
240+
&:hover .more-actions {
241+
opacity: 1;
242+
}
243+
}
244+
}
245+
}
246+
247+
.chat-main {
248+
flex: 1;
249+
display: flex;
250+
flex-direction: column;
251+
252+
.chat-messages {
253+
flex: 1;
254+
overflow-y: auto;
255+
padding: 24px;
256+
257+
.message {
258+
margin-bottom: 24px;
259+
260+
&.user {
261+
text-align: right;
262+
.message-content {
263+
background-color: #e6f4ff;
264+
}
265+
}
266+
267+
&.assistant .message-content {
268+
background-color: #f5f5f5;
269+
}
270+
271+
.message-content {
272+
display: inline-block;
273+
padding: 12px 16px;
274+
border-radius: 8px;
275+
max-width: 80%;
276+
}
277+
}
278+
279+
.empty-state {
280+
text-align: center;
281+
color: #666;
282+
padding: 40px 0;
283+
284+
.sub-text {
285+
margin: 16px 0;
286+
}
287+
288+
.examples {
289+
.example-item {
290+
background-color: #f5f5f5;
291+
padding: 12px;
292+
margin: 8px auto;
293+
max-width: 400px;
294+
border-radius: 8px;
295+
cursor: pointer;
296+
297+
&:hover {
298+
background-color: #e6f4ff;
299+
}
300+
}
301+
}
302+
}
303+
}
304+
305+
.chat-input {
306+
padding: 16px 24px;
307+
border-top: 1px solid var(--el-border-color-lighter);
308+
background: #fff;
309+
310+
.input-wrapper {
311+
position: relative;
312+
border-radius: 12px;
313+
border: 1px solid var(--el-border-color);
314+
background: #f5f5f5;
315+
transition: all 0.3s;
316+
317+
&:hover, &:focus-within {
318+
border-color: var(--el-border-color-darker);
319+
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
320+
}
321+
322+
:deep(.el-textarea__inner) {
323+
padding: 16px;
324+
font-size: 14px;
325+
// line-height: 1.6;
326+
// min-height: 80px;
327+
resize: none;
328+
border: none;
329+
background: transparent;
330+
box-shadow: none !important;
331+
332+
&:focus {
333+
box-shadow: none !important;
334+
}
335+
}
336+
337+
:deep(.el-textarea__wrapper) {
338+
box-shadow: none !important;
339+
padding: 0;
340+
background: transparent;
341+
}
342+
.input-actions {
343+
display: flex;
344+
align-items: center;
345+
justify-content: end;
346+
height: 36px;
347+
padding: 8px;
348+
}
349+
}
350+
351+
352+
}
353+
}
354+
}
355+
</style>

0 commit comments

Comments
 (0)