mirror of
https://github.com/jumpserver/lina.git
synced 2025-06-23 21:59:11 +00:00
perf: add insert event (#5052)
* perf: add insert event perf: receive insert code perf: add content perf: update code perf: save panel to LocalStorage * perf: only terminal add insert action --------- Co-authored-by: Eric <xplzv@126.com>
This commit is contained in:
parent
8f17fa82a2
commit
d72671d2c9
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="chat-action">
|
<div v-if="hasPrompt" class="chat-action">
|
||||||
<Select2
|
<Select2
|
||||||
v-model="select.value"
|
v-model="select.value"
|
||||||
:disabled="isLoading || isSelectDisabled"
|
:disabled="isLoading || isSelectDisabled"
|
||||||
@ -36,6 +36,10 @@ export default {
|
|||||||
expanded: {
|
expanded: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
hasPrompt: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
<!-- eslint-disable-next-line -->
|
<!-- eslint-disable-next-line -->
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<p>
|
<p>
|
||||||
<MessageText :message="item.reasoning" />
|
<MessageText :message="item.reasoning" @insert-code="handleInsertCode" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -41,8 +41,7 @@
|
|||||||
<span v-if="isServerError" class="error">
|
<span v-if="isServerError" class="error">
|
||||||
{{ isServerError }}
|
{{ isServerError }}
|
||||||
</span>
|
</span>
|
||||||
<MessageText :message="item.result" />
|
<MessageText :message="item.result" :is-terminal="isTerminal" @insert-code="handleInsertCode" /></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
@ -93,6 +92,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isTerminal: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -142,6 +145,9 @@ export default {
|
|||||||
if (value === 'copy') {
|
if (value === 'copy') {
|
||||||
copy(this.item.result.content)
|
copy(this.item.result.content)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
handleInsertCode(code) {
|
||||||
|
this.$emit('insert-code', code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isTerminal: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -45,10 +49,10 @@ export default {
|
|||||||
this.init()
|
this.init()
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
this.addCopyEvents()
|
this.addEvents()
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.removeCopyEvents()
|
this.removeEvents()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
@ -69,26 +73,64 @@ export default {
|
|||||||
this.markdown.use(mdKatex, { blockClass: 'katexmath-block rounded-md', errorColor: ' #cc0000' })
|
this.markdown.use(mdKatex, { blockClass: 'katexmath-block rounded-md', errorColor: ' #cc0000' })
|
||||||
},
|
},
|
||||||
highlightBlock(str, lang) {
|
highlightBlock(str, lang) {
|
||||||
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${'Copy'}</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`
|
let insertSpanHmtl = `<span class="code-block-header__insert">${this.$t('insert')}</span>`
|
||||||
|
if (!this.isTerminal) {
|
||||||
|
insertSpanHmtl = ''
|
||||||
|
}
|
||||||
|
return `<pre class="code-block-wrapper">
|
||||||
|
<div class="code-block-header">
|
||||||
|
<span class="code-block-header__lang">${lang}</span>
|
||||||
|
<span class="code-block-header__actions">
|
||||||
|
${insertSpanHmtl}
|
||||||
|
<span class="code-block-header__copy">${'Copy'}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<code class="hljs code-block-body ${lang}">${str}</code></pre>`
|
||||||
},
|
},
|
||||||
addCopyEvents() {
|
addEvents() {
|
||||||
const copyBtn = document.querySelectorAll('.code-block-header__copy')
|
this.addBtnClickEvents('.code-block-header__copy', this.handlerClickCopy)
|
||||||
copyBtn.forEach((btn) => {
|
this.addBtnClickEvents('.code-block-header__insert', this.handlerClickInsert)
|
||||||
btn.addEventListener('click', () => {
|
},
|
||||||
const code = btn.parentElement?.nextElementSibling?.textContent
|
|
||||||
if (code) {
|
handlerClickCopy(event) {
|
||||||
copy(code)
|
const wrapper = event.target.closest('.code-block-wrapper')
|
||||||
}
|
if (wrapper) {
|
||||||
|
// 查找里面的 code 元素
|
||||||
|
const codeElement = wrapper.querySelector('code.code-block-body')
|
||||||
|
if (codeElement) {
|
||||||
|
const codeText = codeElement.textContent
|
||||||
|
copy(codeText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handlerClickInsert(event) {
|
||||||
|
const wrapper = event.target.closest('.code-block-wrapper')
|
||||||
|
if (wrapper) {
|
||||||
|
// 查找里面的 code 元素
|
||||||
|
const codeElement = wrapper.querySelector('code.code-block-body')
|
||||||
|
if (codeElement) {
|
||||||
|
const codeText = codeElement.textContent
|
||||||
|
this.$emit('insert-code', codeText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addBtnClickEvents(selector, callback) {
|
||||||
|
const buttons = this.$refs.textRef.querySelectorAll(selector)
|
||||||
|
buttons.forEach((btn) => {
|
||||||
|
btn.addEventListener('click', callback)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeBtnClickEvent(selector) {
|
||||||
|
const buttons = this.$refs.textRef.querySelectorAll(selector)
|
||||||
|
buttons.forEach((btn) => {
|
||||||
|
btn.removeEventListener('click', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
removeCopyEvents() {
|
removeEvents() {
|
||||||
if (this.$refs.textRef) {
|
if (this.$refs.textRef) {
|
||||||
const copyBtn = this.$refs.textRef.querySelectorAll('.code-block-header__copy')
|
this.removeBtnClickEvent('.code-block-header__copy')
|
||||||
copyBtn.forEach((btn) => {
|
this.addBtnClickEvents('.code-block-header__insert')
|
||||||
btn.removeEventListener('click', () => {
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,6 +140,7 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.markdown-body {
|
.markdown-body {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
max-width: 300px;;
|
||||||
|
|
||||||
&::v-deep p {
|
&::v-deep p {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
@ -115,26 +158,46 @@ export default {
|
|||||||
|
|
||||||
&::v-deep .code-block-wrapper {
|
&::v-deep .code-block-wrapper {
|
||||||
background: #1F2329;
|
background: #1F2329;
|
||||||
padding: 2px 6px;
|
padding: 0;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.code-block-body {
|
.code-block-body {
|
||||||
padding: 5px 10px 0;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
;
|
|
||||||
|
|
||||||
.code-block-header {
|
.code-block-header {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #353946;
|
background: #353946;
|
||||||
color: #c2d1e1;
|
color: #c2d1e1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.code-block-header__copy {
|
.code-block-header__actions {
|
||||||
float: right;
|
display: flex;
|
||||||
cursor: pointer;
|
gap: 8px;
|
||||||
|
|
||||||
&:hover {
|
.code-block-header__copy {
|
||||||
color: #6e747b;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #6e747b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-header__insert {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #6e747b;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,6 +241,7 @@ export default {
|
|||||||
0% {
|
0% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ChatMessage v-for="(item, index) in activeChat.chats" :key="index" :item="item" />
|
<ChatMessage v-for="(item, index) in activeChat.chats" :key="index" :item="item" :is-terminal="isTerminal" @insert-code="insertCode" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
<el-button
|
<el-button
|
||||||
@ -28,7 +28,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
@click="onStopHandle"
|
@click="onStopHandle"
|
||||||
>{{ $tc('Stop') }}</el-button>
|
>{{ $tc('Stop') }}</el-button>
|
||||||
<ChatInput ref="chatInput" :expanded="expanded" @send="onSendHandle" @select-prompt="onSelectPromptHandle" />
|
<ChatInput ref="chatInput" :expanded="expanded" :has-prompt="!isTerminal" @send="onSendHandle" @select-prompt="onSelectPromptHandle" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -68,7 +68,10 @@ export default {
|
|||||||
prompt: '',
|
prompt: '',
|
||||||
conversationId: '',
|
conversationId: '',
|
||||||
showIntroduction: false,
|
showIntroduction: false,
|
||||||
introduction: []
|
introduction: [],
|
||||||
|
terminalContext: null,
|
||||||
|
isTerminal: false,
|
||||||
|
sessionChat: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -100,6 +103,9 @@ export default {
|
|||||||
this.showIntroduction = true
|
this.showIntroduction = true
|
||||||
this.conversationId = ''
|
this.conversationId = ''
|
||||||
this.$refs.chatInput.select.value = ''
|
this.$refs.chatInput.select.value = ''
|
||||||
|
if (this.terminalContext) {
|
||||||
|
this.prompt = this.terminalContext.content || ''
|
||||||
|
}
|
||||||
const chat = {
|
const chat = {
|
||||||
message: {
|
message: {
|
||||||
content: this.$t('ChatHello'),
|
content: this.$t('ChatHello'),
|
||||||
@ -150,6 +156,32 @@ export default {
|
|||||||
addMessageToActiveChat(data)
|
addMessageToActiveChat(data)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
},
|
},
|
||||||
|
onTerminalContext(terminalContext) {
|
||||||
|
const originSessionId = this.terminalContext?.sessionId
|
||||||
|
const newSessionId = terminalContext.sessionId || ''
|
||||||
|
if (originSessionId) {
|
||||||
|
this.saveSessionChat(originSessionId)
|
||||||
|
}
|
||||||
|
this.terminalContext = terminalContext
|
||||||
|
this.isTerminal = true
|
||||||
|
this.prompt = terminalContext.content || ''
|
||||||
|
if (originSessionId !== newSessionId) {
|
||||||
|
if (this.sessionChat[newSessionId]) {
|
||||||
|
clearChats()
|
||||||
|
for (const chat of this.sessionChat[newSessionId]) {
|
||||||
|
addChatMessageById(chat)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.onNewChat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveSessionChat(sessionId) {
|
||||||
|
if (this.terminalContext) {
|
||||||
|
this.sessionChat[sessionId] = JSON.parse(JSON.stringify(this.activeChat.chats))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onSendHandle(value) {
|
onSendHandle(value) {
|
||||||
this.showIntroduction = false
|
this.showIntroduction = false
|
||||||
this.socket = ws || {}
|
this.socket = ws || {}
|
||||||
@ -204,6 +236,15 @@ export default {
|
|||||||
sendIntroduction(item) {
|
sendIntroduction(item) {
|
||||||
this.showIntroduction = false
|
this.showIntroduction = false
|
||||||
this.onSendHandle(item.content)
|
this.onSendHandle(item.content)
|
||||||
|
},
|
||||||
|
insertCode(code) {
|
||||||
|
this.sendPostMessage({
|
||||||
|
name: 'INSERT_TERMINAL_CODE',
|
||||||
|
data: code.replace(/^[\s\r\n]+|[\s\r\n]+$/g, '')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sendPostMessage(data) {
|
||||||
|
window.parent.postMessage(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,9 @@ import Chat from './components/ChitChat/index.vue'
|
|||||||
import { getInputFocus } from './useChat.js'
|
import { getInputFocus } from './useChat.js'
|
||||||
import { ws } from '@/utils/socket'
|
import { ws } from '@/utils/socket'
|
||||||
import DrawerPanel from '@/components/Apps/DrawerPanel/index.vue'
|
import DrawerPanel from '@/components/Apps/DrawerPanel/index.vue'
|
||||||
|
import { ObjectLocalStorage } from '@/utils/common'
|
||||||
|
|
||||||
|
const aiPannelLocalStorage = new ObjectLocalStorage('ai_panel_settings')
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
DrawerPanel,
|
DrawerPanel,
|
||||||
@ -76,12 +78,15 @@ export default {
|
|||||||
robotUrl: require('@/assets/img/robot-assistant.png'),
|
robotUrl: require('@/assets/img/robot-assistant.png'),
|
||||||
height: '400px',
|
height: '400px',
|
||||||
expanded: false,
|
expanded: false,
|
||||||
clientOffset: {}
|
clientOffset: {},
|
||||||
|
currentTerminalContent: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
const expanded = aiPannelLocalStorage.get('expanded')
|
||||||
|
this.updateExpandedState(expanded)
|
||||||
this.handlePostMessage()
|
this.handlePostMessage()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -90,6 +95,16 @@ export default {
|
|||||||
if (event.data === 'show-chat-panel') {
|
if (event.data === 'show-chat-panel') {
|
||||||
this.$refs.drawer.show = true
|
this.$refs.drawer.show = true
|
||||||
this.initWebSocket()
|
this.initWebSocket()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const msg = event.data
|
||||||
|
switch (msg.name) {
|
||||||
|
case 'current_terminal_content':
|
||||||
|
// {content: '...', terminalId: '',sessionId: '',viewId: '',viewName: ''}
|
||||||
|
this.$log.debug('current_terminal_content', msg)
|
||||||
|
this.currentTerminalContent = msg.data
|
||||||
|
this.$refs.component?.onTerminalContext(msg.data)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -113,12 +128,20 @@ export default {
|
|||||||
this.$refs.drawer.show = false
|
this.$refs.drawer.show = false
|
||||||
},
|
},
|
||||||
expandFull() {
|
expandFull() {
|
||||||
this.height = '100%'
|
this.updateExpandedState(true)
|
||||||
this.expanded = true
|
this.save_pannel_settings()
|
||||||
},
|
},
|
||||||
compress() {
|
compress() {
|
||||||
this.height = '400px'
|
this.updateExpandedState(false)
|
||||||
this.expanded = false
|
this.save_pannel_settings()
|
||||||
|
},
|
||||||
|
save_pannel_settings() {
|
||||||
|
aiPannelLocalStorage.set('expanded', this.expanded)
|
||||||
|
console.log('AI panel settings saved:', this.expanded)
|
||||||
|
},
|
||||||
|
updateExpandedState(expanded) {
|
||||||
|
this.expanded = expanded
|
||||||
|
this.height = expanded ? '100%' : '400px'
|
||||||
},
|
},
|
||||||
onNewChat() {
|
onNewChat() {
|
||||||
this.active = 'chat'
|
this.active = 'chat'
|
||||||
|
Loading…
Reference in New Issue
Block a user