Skip to content

Commit c4bb484

Browse files
committed
refactor: refresh editor ui
1 parent 555fa4b commit c4bb484

37 files changed

+945
-697
lines changed

.cursor/mcp.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"mcpServers": {
3+
"eslint": {
4+
"command": "npx",
5+
"args": ["@eslint/mcp@latest"],
6+
"env": {}
7+
}
8+
}
9+
}

app/components.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ declare module 'vue' {
6868
AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger']
6969
AutoCertFormAutoCertForm: typeof import('./src/components/AutoCertForm/AutoCertForm.vue')['default']
7070
AutoCertFormDNSChallenge: typeof import('./src/components/AutoCertForm/DNSChallenge.vue')['default']
71+
BaseEditorBaseEditor: typeof import('./src/components/BaseEditor/BaseEditor.vue')['default']
7172
BreadcrumbBreadcrumb: typeof import('./src/components/Breadcrumb/Breadcrumb.vue')['default']
7273
CertInfoCertInfo: typeof import('./src/components/CertInfo/CertInfo.vue')['default']
7374
ChartAreaChart: typeof import('./src/components/Chart/AreaChart.vue')['default']
@@ -78,6 +79,7 @@ declare module 'vue' {
7879
ChatGPTChatMessageInput: typeof import('./src/components/ChatGPT/ChatMessageInput.vue')['default']
7980
ChatGPTChatMessageList: typeof import('./src/components/ChatGPT/ChatMessageList.vue')['default']
8081
CodeEditorCodeEditor: typeof import('./src/components/CodeEditor/CodeEditor.vue')['default']
82+
CommonEditorCommonEditor: typeof import('./src/components/CommonEditor/CommonEditor.vue')['default']
8183
ConfigHistoryConfigHistory: typeof import('./src/components/ConfigHistory/ConfigHistory.vue')['default']
8284
ConfigHistoryDiffViewer: typeof import('./src/components/ConfigHistory/DiffViewer.vue')['default']
8385
EnvGroupTabsEnvGroupTabs: typeof import('./src/components/EnvGroupTabs/EnvGroupTabs.vue')['default']
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<script setup lang="ts">
2+
import { LoadingOutlined } from '@ant-design/icons-vue'
3+
4+
// Generic editor layout with left and right panels
5+
interface BaseEditorProps {
6+
colRightClass?: string
7+
loading?: boolean
8+
}
9+
10+
const props = withDefaults(defineProps<BaseEditorProps>(), {
11+
colRightClass: 'col-right',
12+
})
13+
14+
const indicator = h(LoadingOutlined, {
15+
style: {
16+
fontSize: '32px',
17+
},
18+
spin: true,
19+
})
20+
21+
const route = useRoute()
22+
const loading = computed(() =>
23+
props.loading || (import.meta.env.DEV && route.query.loading === 'true'),
24+
)
25+
</script>
26+
27+
<template>
28+
<ASpin class="h-full base-editor-spin" :spinning="loading" :indicator="indicator">
29+
<ARow :gutter="{ xs: 0, sm: 16 }">
30+
<ACol
31+
:xs="24"
32+
:sm="24"
33+
:md="24"
34+
:lg="16"
35+
:xl="17"
36+
>
37+
<!-- Left panel content (main editor) -->
38+
<slot name="left" />
39+
</ACol>
40+
41+
<ACol
42+
:class="props.colRightClass"
43+
:xs="24"
44+
:sm="24"
45+
:md="24"
46+
:lg="8"
47+
:xl="7"
48+
>
49+
<!-- Right panel content (settings/configuration) -->
50+
<slot name="right" />
51+
</ACol>
52+
</ARow>
53+
</ASpin>
54+
</template>
55+
56+
<style lang="less" scoped>
57+
.col-right {
58+
position: sticky;
59+
top: 78px;
60+
}
61+
62+
:deep(.ant-card) {
63+
box-shadow: unset;
64+
}
65+
66+
:deep(.card-body) {
67+
max-height: calc(100vh - 260px);
68+
overflow-y: scroll;
69+
padding: 0;
70+
}
71+
72+
:deep(.ant-spin) {
73+
background: rgba(255, 255, 255, 0.8);
74+
backdrop-filter: blur(10px);
75+
-webkit-backdrop-filter: blur(10px);
76+
max-height: 100% !important;
77+
border-radius: 8px;
78+
}
79+
</style>
80+
81+
<style lang="less">
82+
.dark {
83+
.base-editor-spin {
84+
background: rgba(30, 30, 30, 0.8);
85+
}
86+
}
87+
</style>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import BaseEditor from './BaseEditor.vue'
2+
3+
export { BaseEditor }
4+
export default BaseEditor

app/src/components/CertInfo/CertInfo.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,7 @@ const isValid = computed(() => dayjs().isAfter(props.cert?.not_before) && dayjs(
4444
</template>
4545

4646
<style lang="less" scoped>
47-
47+
:deep(.ant-card-body) {
48+
padding: 12px !important;
49+
}
4850
</style>

app/src/components/ChatGPT/ChatGPT.vue

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import Icon from '@ant-design/icons-vue'
3+
import { useElementVisibility } from '@vueuse/core'
34
import { storeToRefs } from 'pinia'
45
import ChatGPT_logo from '@/assets/svg/ChatGPT_logo.svg?component'
56
import { useSettingsStore } from '@/pinia'
@@ -16,7 +17,7 @@ const { language: current } = storeToRefs(useSettingsStore())
1617
1718
// Use ChatGPT store
1819
const chatGPTStore = useChatGPTStore()
19-
const { messageListRef, loading, shouldShowStartButton } = storeToRefs(chatGPTStore)
20+
const { messageContainerRef, loading, shouldShowStartButton } = storeToRefs(chatGPTStore)
2021
2122
// Initialize messages when path changes
2223
watch(() => props.path, async () => {
@@ -28,6 +29,14 @@ watch(() => props.path, async () => {
2829
async function handleSend() {
2930
await chatGPTStore.send(props.content, current.value)
3031
}
32+
33+
const isVisible = useElementVisibility(messageContainerRef)
34+
35+
watch(isVisible, visible => {
36+
if (visible) {
37+
chatGPTStore.scrollToBottom()
38+
}
39+
}, { immediate: true })
3140
</script>
3241

3342
<template>
@@ -49,17 +58,20 @@ async function handleSend() {
4958

5059
<div
5160
v-else
52-
class="chatgpt-container"
61+
ref="messageContainerRef"
62+
class="message-container"
5363
>
54-
<ChatMessageList ref="messageListRef" />
64+
<ChatMessageList />
5565

5666
<ChatMessageInput />
5767
</div>
5868
</template>
5969

6070
<style lang="less" scoped>
61-
.chatgpt-container {
71+
.message-container {
6272
margin: 0 auto;
6373
max-width: 800px;
74+
max-height: calc(100vh - 260px);
75+
overflow-y: auto;
6476
}
6577
</style>

app/src/components/ChatGPT/ChatMessageInput.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const messagesLength = computed(() => messages.value?.length ?? 0)
5959
backdrop-filter: blur(10px);
6060
-webkit-backdrop-filter: blur(10px);
6161
padding: 16px;
62-
border-top: 1px solid rgba(255, 255, 255, 0.2);
62+
border-radius: 0 0 8px 8px;
6363
6464
.control-btn {
6565
display: flex;
@@ -76,7 +76,6 @@ const messagesLength = computed(() => messages.value?.length ?? 0)
7676
.dark {
7777
.input-msg {
7878
background: rgba(30, 30, 30, 0.8);
79-
border-top: 1px solid rgba(255, 255, 255, 0.1);
8079
}
8180
}
8281
</style>

app/src/components/ChatGPT/ChatMessageList.vue

Lines changed: 4 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,57 +6,6 @@ import ChatMessage from './ChatMessage.vue'
66
const chatGPTStore = useChatGPTStore()
77
const { messages, editingIdx, editValue, loading } = storeToRefs(chatGPTStore)
88
9-
const messageListRef = useTemplateRef('messageList')
10-
let scrollTimeoutId: number | null = null
11-
12-
function scrollToBottom() {
13-
// Debounce scroll operations for better performance
14-
if (scrollTimeoutId) {
15-
clearTimeout(scrollTimeoutId)
16-
}
17-
18-
scrollTimeoutId = window.setTimeout(() => {
19-
requestAnimationFrame(() => {
20-
if (messageListRef.value) {
21-
let element = messageListRef.value.parentElement
22-
while (element) {
23-
const style = window.getComputedStyle(element)
24-
if (style.overflowY === 'auto' || style.overflowY === 'scroll') {
25-
element.scrollTo({
26-
top: element.scrollHeight,
27-
behavior: 'smooth',
28-
})
29-
return
30-
}
31-
element = element.parentElement
32-
}
33-
}
34-
})
35-
}, 50) // 50ms debounce
36-
}
37-
38-
// Watch for messages changes and auto scroll - with debouncing
39-
watch(() => messages.value, () => {
40-
scrollToBottom()
41-
}, { deep: true, flush: 'post' })
42-
43-
// Auto scroll when messages are loaded
44-
onMounted(() => {
45-
scrollToBottom()
46-
})
47-
48-
// Clean up on unmount
49-
onUnmounted(() => {
50-
if (scrollTimeoutId) {
51-
clearTimeout(scrollTimeoutId)
52-
}
53-
})
54-
55-
// Expose scroll function for parent component
56-
defineExpose({
57-
scrollToBottom,
58-
})
59-
609
function handleEdit(index: number) {
6110
chatGPTStore.startEdit(index)
6211
}
@@ -77,7 +26,7 @@ async function handleRegenerate(index: number) {
7726
</script>
7827

7928
<template>
80-
<div ref="messageList" class="message-list-container">
29+
<div class="message-list-container">
8130
<AList
8231
class="chatgpt-log"
8332
item-layout="horizontal"
@@ -102,6 +51,9 @@ async function handleRegenerate(index: number) {
10251

10352
<style lang="less" scoped>
10453
.message-list-container {
54+
overflow-y: auto;
55+
height: 100%;
56+
10557
.chatgpt-log {
10658
:deep(.ant-list-item) {
10759
padding: 0 12px;

app/src/components/ChatGPT/chatgpt.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { ChatMessageList } from '.'
21
import type { ChatComplicationMessage } from '@/api/openai'
32
import { defineStore } from 'pinia'
43
import { computed, nextTick, ref } from 'vue'
@@ -9,7 +8,7 @@ export const useChatGPTStore = defineStore('chatgpt', () => {
98
// State
109
const path = ref<string>('') // Path to the chat record file
1110
const messages = ref<ChatComplicationMessage[]>([])
12-
const messageListRef = ref<InstanceType<typeof ChatMessageList>>()
11+
const messageContainerRef = ref<HTMLDivElement>()
1312
const loading = ref(false)
1413
const editingIdx = ref(-1)
1514
const editValue = ref('')
@@ -154,7 +153,10 @@ export const useChatGPTStore = defineStore('chatgpt', () => {
154153

155154
// scroll to bottom
156155
function scrollToBottom() {
157-
messageListRef.value?.scrollToBottom()
156+
messageContainerRef.value?.scrollTo({
157+
top: messageContainerRef.value.scrollHeight,
158+
behavior: 'smooth',
159+
})
158160
}
159161

160162
// Set streaming message index
@@ -234,6 +236,9 @@ export const useChatGPTStore = defineStore('chatgpt', () => {
234236
await request()
235237
}
236238

239+
watch(messages, () => {
240+
scrollToBottom()
241+
}, { immediate: true })
237242
// Return all state, getters, and actions
238243
return {
239244
// State
@@ -242,7 +247,7 @@ export const useChatGPTStore = defineStore('chatgpt', () => {
242247
editingIdx,
243248
editValue,
244249
askBuffer,
245-
messageListRef,
250+
messageContainerRef,
246251
streamingMessageIndex,
247252

248253
// Getters
@@ -271,5 +276,6 @@ export const useChatGPTStore = defineStore('chatgpt', () => {
271276
request,
272277
send,
273278
regenerate,
279+
scrollToBottom,
274280
}
275281
})

app/src/components/NgxConfigEditor/NgxConfigEditor.vue

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,19 @@ const activeKey = ref(['3'])
4545
>
4646
<NgxUpstream />
4747
</ACollapsePanel>
48-
<ACollapsePanel
49-
key="3"
50-
header="Server"
51-
>
52-
<NgxServer :context>
53-
<template
54-
v-for="(_, key) in $slots"
55-
:key="key"
56-
#[key]="slotProps"
57-
>
58-
<slot
59-
:name="key"
60-
v-bind="slotProps"
61-
/>
62-
</template>
63-
</NgxServer>
64-
</ACollapsePanel>
6548
</ACollapse>
49+
<NgxServer :context>
50+
<template
51+
v-for="(_, key) in $slots"
52+
:key="key"
53+
#[key]="slotProps"
54+
>
55+
<slot
56+
:name="key"
57+
v-bind="slotProps"
58+
/>
59+
</template>
60+
</NgxServer>
6661
</div>
6762
</template>
6863

0 commit comments

Comments
 (0)