feat: AI dialog box, left mouse button menu (#2005)
This commit is contained in:
parent
2a63cd6bea
commit
de85895ad6
@ -47,7 +47,8 @@
|
|||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-codemirror": "^6.1.1",
|
"vue-codemirror": "^6.1.1",
|
||||||
"vue-i18n": "^9.13.1",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-router": "^4.2.4"
|
"vue-router": "^4.2.4",
|
||||||
|
"vue3-menus": "^1.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.3.2",
|
"@rushstack/eslint-patch": "^1.3.2",
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<img v-if="application.avatar" :src="application.avatar" height="32px" width="32px" />
|
<img v-if="application.avatar" :src="application.avatar" height="32px" width="32px" />
|
||||||
<LogoIcon v-else height="32px" width="32px" />
|
<LogoIcon v-else height="32px" width="32px" />
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content" @click.stop @mouseup="openControl">
|
||||||
<el-card shadow="always" class="dialog-card mb-8">
|
<el-card shadow="always" class="dialog-card mb-8">
|
||||||
<MdRenderer
|
<MdRenderer
|
||||||
v-if="
|
v-if="
|
||||||
@ -56,6 +56,7 @@ import MdRenderer from '@/components/markdown/MdRenderer.vue'
|
|||||||
import OperationButton from '@/components/ai-chat/component/operation-button/index.vue'
|
import OperationButton from '@/components/ai-chat/component/operation-button/index.vue'
|
||||||
import { type chatType } from '@/api/type/application'
|
import { type chatType } from '@/api/type/application'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import bus from '@/bus'
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
chatRecord: chatType
|
chatRecord: chatType
|
||||||
application: any
|
application: any
|
||||||
@ -79,6 +80,13 @@ const chatMessage = (question: string, type: 'old' | 'new', other_params_data?:
|
|||||||
const add_answer_text_list = (answer_text_list: Array<any>) => {
|
const add_answer_text_list = (answer_text_list: Array<any>) => {
|
||||||
answer_text_list.push({ content: '' })
|
answer_text_list.push({ content: '' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openControl = (event: any) => {
|
||||||
|
if (props.type !== 'log') {
|
||||||
|
bus.emit('open-control', event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const answer_text_list = computed(() => {
|
const answer_text_list = computed(() => {
|
||||||
return props.chatRecord.answer_text_list.map((item) => {
|
return props.chatRecord.answer_text_list.map((item) => {
|
||||||
if (typeof item == 'string') {
|
if (typeof item == 'string') {
|
||||||
|
|||||||
@ -182,6 +182,7 @@ import { MsgAlert } from '@/utils/message'
|
|||||||
import { type chatType } from '@/api/type/application'
|
import { type chatType } from '@/api/type/application'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { getImgUrl } from '@/utils/utils'
|
import { getImgUrl } from '@/utils/utils'
|
||||||
|
import bus from '@/bus'
|
||||||
import 'recorder-core/src/engine/mp3'
|
import 'recorder-core/src/engine/mp3'
|
||||||
|
|
||||||
import 'recorder-core/src/engine/mp3-engine'
|
import 'recorder-core/src/engine/mp3-engine'
|
||||||
@ -542,6 +543,9 @@ function mouseleave() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
bus.on('chat-input', (message: string) => {
|
||||||
|
inputValue.value = message
|
||||||
|
})
|
||||||
if (question) {
|
if (question) {
|
||||||
inputValue.value = decodeURIComponent(question.trim())
|
inputValue.value = decodeURIComponent(question.trim())
|
||||||
sendChatHandle()
|
sendChatHandle()
|
||||||
|
|||||||
81
ui/src/components/ai-chat/component/control/index.vue
Normal file
81
ui/src/components/ai-chat/component/control/index.vue
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<vue3-menus v-model:open="isOpen" :event="eventVal" :menus="menus" hasIcon>
|
||||||
|
<template #icon="{ menu }"
|
||||||
|
><AppIcon v-if="menu.icon" :iconName="menu.icon"></AppIcon
|
||||||
|
></template>
|
||||||
|
<template #label="{ menu }"> {{ menu.label }}</template>
|
||||||
|
</vue3-menus>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Vue3Menus } from 'vue3-menus'
|
||||||
|
import { MsgSuccess } from '@/utils/message'
|
||||||
|
import AppIcon from '@/components/icons/AppIcon.vue'
|
||||||
|
import bus from '@/bus'
|
||||||
|
import { ref, nextTick, onMounted } from 'vue'
|
||||||
|
const isOpen = ref<boolean>(false)
|
||||||
|
const eventVal = ref({})
|
||||||
|
function getSelection() {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (selection && selection.anchorNode == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const text = selection?.anchorNode?.textContent
|
||||||
|
return text && text.substring(selection.anchorOffset, selection.focusOffset)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 打开控制台
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
const openControl = (event: any) => {
|
||||||
|
const c = getSelection()
|
||||||
|
isOpen.value = false
|
||||||
|
if (c) {
|
||||||
|
nextTick(() => {
|
||||||
|
eventVal.value = event
|
||||||
|
isOpen.value = true
|
||||||
|
})
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const menus = ref([
|
||||||
|
{
|
||||||
|
label: '复制',
|
||||||
|
icon: 'app-copy',
|
||||||
|
click: () => {
|
||||||
|
const selectionText = getSelection()
|
||||||
|
if (selectionText) {
|
||||||
|
clearSelectedText()
|
||||||
|
navigator.clipboard.writeText(selectionText).then(() => {
|
||||||
|
MsgSuccess('复制成功')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '引用',
|
||||||
|
icon: 'app-quote',
|
||||||
|
click: () => {
|
||||||
|
bus.emit('chat-input', getSelection())
|
||||||
|
clearSelectedText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
/**
|
||||||
|
* 清除选中文本
|
||||||
|
*/
|
||||||
|
const clearSelectedText = () => {
|
||||||
|
if (window.getSelection) {
|
||||||
|
var selection = window.getSelection()
|
||||||
|
if (selection) {
|
||||||
|
selection.removeAllRanges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
bus.on('open-control', openControl)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
@ -46,6 +46,7 @@
|
|||||||
>
|
>
|
||||||
<template #operateBefore> <slot name="operateBefore" /> </template>
|
<template #operateBefore> <slot name="operateBefore" /> </template>
|
||||||
</ChatInputOperate>
|
</ChatInputOperate>
|
||||||
|
<Control></Control>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -63,6 +64,7 @@ import QuestionContent from '@/components/ai-chat/component/question-content/ind
|
|||||||
import ChatInputOperate from '@/components/ai-chat/component/chat-input-operate/index.vue'
|
import ChatInputOperate from '@/components/ai-chat/component/chat-input-operate/index.vue'
|
||||||
import PrologueContent from '@/components/ai-chat/component/prologue-content/index.vue'
|
import PrologueContent from '@/components/ai-chat/component/prologue-content/index.vue'
|
||||||
import UserForm from '@/components/ai-chat/component/user-form/index.vue'
|
import UserForm from '@/components/ai-chat/component/user-form/index.vue'
|
||||||
|
import Control from '@/components/ai-chat/component/control/index.vue'
|
||||||
defineOptions({ name: 'AiChat' })
|
defineOptions({ name: 'AiChat' })
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -1374,4 +1374,25 @@ export const iconMap: any = {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'app-quote': {
|
||||||
|
iconReader: () => {
|
||||||
|
return h('i', [
|
||||||
|
h(
|
||||||
|
'svg',
|
||||||
|
{
|
||||||
|
style: { height: '100%', width: '100%' },
|
||||||
|
viewBox: '0 0 1024 1024',
|
||||||
|
version: '1.1',
|
||||||
|
xmlns: 'http://www.w3.org/2000/svg'
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('path', {
|
||||||
|
d: 'M800.768 477.184c-14.336 0-30.72 2.048-45.056 4.096 18.432-51.2 77.824-188.416 237.568-315.392 36.864-28.672-20.48-86.016-59.392-57.344-155.648 116.736-356.352 317.44-356.352 573.44v20.48c0 122.88 100.352 223.232 223.232 223.232S1024 825.344 1024 702.464c0-124.928-100.352-225.28-223.232-225.28zM223.232 477.184c-14.336 0-30.72 2.048-45.056 4.096 18.432-51.2 77.824-188.416 237.568-315.392 36.864-28.672-20.48-86.016-59.392-57.344C200.704 225.28 0 425.984 0 681.984v20.48c0 122.88 100.352 223.232 223.232 223.232s223.232-100.352 223.232-223.232c0-124.928-100.352-225.28-223.232-225.28z',
|
||||||
|
fill: 'currentColor'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user