Skip to content

Commit

Permalink
feat(component): ✨ 新增AI选项卡,封装提及框功能
Browse files Browse the repository at this point in the history
提供hooks和策略模式两种方式来实现多种特殊符号唤起提及框
  • Loading branch information
nongyehong committed Jan 1, 2025
1 parent 99a8859 commit 1d0f723
Show file tree
Hide file tree
Showing 17 changed files with 736 additions and 102 deletions.
Binary file added public/AI/QW.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/AI/deepseek.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/AI/openai.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/icon.js

Large diffs are not rendered by default.

102 changes: 84 additions & 18 deletions src/components/rightBox/MsgInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@
v-if="isEntering"
@click.stop="messageInputDom.focus()"
class="absolute select-none top-8px left-6px w-fit text-(12px #777)">
聊点什么吧...
输入 / 唤起 AI 助手
</span>
</n-scrollbar>
</ContextMenu>

<!-- @提及框 -->
<div v-if="ait && activeItem.type === RoomTypeEnum.GROUP && personList.length > 0" class="ait">
<div v-if="ait && activeItem.type === RoomTypeEnum.GROUP && personList.length > 0" class="ait-options">

Check warning on line 28 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L28

Added line #L28 was not covered by tests
<n-virtual-list
id="image-chat-msgInput"
ref="virtualListInst"
id="image-chat-ait"
ref="virtualListInst-ait"

Check warning on line 31 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L30-L31

Added lines #L30 - L31 were not covered by tests
style="max-height: 180px"
:item-size="36"
:items="personList"
Expand All @@ -49,14 +49,49 @@
fallback-src="/logo.png"
:render-placeholder="() => null"
:intersection-observer-options="{
root: '#image-chat-msgInput'
root: '#image-chat-ait'
}" />
<span> {{ item.name }}</span>
</n-flex>
</template>
</n-virtual-list>
</div>

<!-- / 提及框 -->
<div
v-if="aiDialogVisible && activeItem.type === RoomTypeEnum.GROUP && groupedAIModels.length > 0"
class="AI-options">
<n-virtual-list
ref="virtualListInst-AI"
style="max-height: 180px"
:item-size="36"
:items="groupedAIModels"
v-model:selectedKey="selectedAIKey">
<template #default="{ item }">
<n-flex
@mouseover="() => (selectedAIKey = item.uid)"
:class="{ active: selectedAIKey === item.uid }"
@click="handleAI(item)"
align="center"
class="AI-item">
<n-flex align="center" justify="space-between" class="w-full pr-6px">
<n-flex align="center">
<img class="size-18px object-contain" :src="item.avatar" alt="" />
<p class="text-(14px [--chat-text-color])">{{ item.name }}</p>
</n-flex>

Check warning on line 81 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L60-L81

Added lines #L60 - L81 were not covered by tests

<n-flex align="center" :size="6">
<div class="ml-6px p-[4px_8px] size-fit bg-[--bate-bg] rounded-6px text-(11px [--bate-color] center)">

Check warning on line 84 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L83-L84

Added lines #L83 - L84 were not covered by tests
Beta
</div>
<n-tag size="small" class="text-10px" :bordered="false" type="success">128k</n-tag>
</n-flex>
</n-flex>
</n-flex>

Check warning on line 90 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L86-L90

Added lines #L86 - L90 were not covered by tests
</template>
</n-virtual-list>
</div>

Check warning on line 93 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L92-L93

Added lines #L92 - L93 were not covered by tests

<!-- 发送按钮 -->
<n-flex align="center" justify="space-between" :size="12">
<n-config-provider :theme="lightTheme">
Expand Down Expand Up @@ -139,8 +174,10 @@ const arrow = ref(false)
/** 输入框dom元素 */
const messageInputDom = ref()
const activeItem = ref(inject('activeItem') as MockItem)
/** 虚拟列表 */
const virtualListInst = ref<VirtualListInst>()
/** ait 虚拟列表 */
const virtualListInstAit = useTemplateRef<VirtualListInst>('virtualListInst-ait')

Check warning on line 178 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L178

Added line #L178 was not covered by tests
/** AI 虚拟列表 */
const virtualListInstAI = useTemplateRef<VirtualListInst>('virtualListInst-AI')

Check warning on line 180 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L180

Added line #L180 was not covered by tests
/** 是否处于输入状态 */
const isEntering = computed(() => {
return msgInput.value === ''
Expand All @@ -161,15 +198,19 @@ const recordSelectionRange = () => (lastEditRange = getEditorRange())
const {
inputKeyDown,
handleAit,
handleAI,

Check warning on line 201 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L201

Added line #L201 was not covered by tests
handleInput,
send,
personList,
disabledSend,
ait,
aiDialogVisible,
selectedAIKey,

Check warning on line 208 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L207-L208

Added lines #L207 - L208 were not covered by tests
msgInput,
chatKey,
menuList,
selectedAitKey
selectedAitKey,
groupedAIModels

Check warning on line 213 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L212-L213

Added lines #L212 - L213 were not covered by tests
} = useMsgInput(messageInputDom)
/** 当切换聊天对象时,重新获取焦点 */
Expand All @@ -184,19 +225,34 @@ watch(activeItem, () => {
watch(personList, (newList) => {
if (newList.length > 0) {
/** 先设置滚动条滚动到第一个 */
virtualListInst.value?.scrollTo({ key: newList[0].uid })
virtualListInstAit.value?.scrollTo({ key: newList[0].uid })

Check warning on line 228 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L228

Added line #L228 was not covered by tests
selectedAitKey.value = newList[0].uid
}
})
/** 当AI列表发生变化的时候始终select第一个 */
watch(groupedAIModels, (newList) => {
if (newList.length > 0) {

Check warning on line 235 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L234-L235

Added lines #L234 - L235 were not covered by tests
/** 先设置滚动条滚动到第一个 */
virtualListInstAI.value?.scrollTo({ key: newList[0].uid })
selectedAIKey.value = newList[0].uid
}
})

Check warning on line 240 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L237-L240

Added lines #L237 - L240 were not covered by tests
/** 处理键盘上下键切换提及项 */
const handleAitKeyChange = (direction: 1 | -1) => {
const currentIndex = personList.value.findIndex((item) => item.uid === selectedAitKey.value)
const newIndex = Math.max(0, Math.min(currentIndex + direction, personList.value.length - 1))
selectedAitKey.value = personList.value[newIndex].uid
const handleAitKeyChange = (
direction: 1 | -1,
list: Ref<any[]>,
virtualListInst: VirtualListInst,
key: Ref<number | string>
) => {
const currentIndex = list.value.findIndex((item) => item.uid === key.value)
const newIndex = Math.max(0, Math.min(currentIndex + direction, list.value.length - 1))
key.value = list.value[newIndex].uid

Check warning on line 251 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L243-L251

Added lines #L243 - L251 were not covered by tests
// 获取新选中项在列表中的索引,并滚动到该位置(使用key来进行定位)
virtualListInst.value?.scrollTo({ index: newIndex })
virtualListInst?.scrollTo({ index: newIndex })

Check warning on line 253 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L253

Added line #L253 was not covered by tests
}
const closeMenu = (event: any) => {
/** 需要判断点击如果不是.context-menu类的元素的时候,menu才会关闭 */
if (!event.target.matches('#message-input, #message-input *')) {
Expand All @@ -205,20 +261,30 @@ const closeMenu = (event: any) => {
}
onMounted(async () => {
onKeyStroke('Enter', (e) => {
onKeyStroke('Enter', () => {

Check warning on line 264 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L264

Added line #L264 was not covered by tests
if (ait.value && selectedAitKey.value > -1) {
e.preventDefault()
const item = personList.value.find((item) => item.uid === selectedAitKey.value) as CacheUserItem
handleAit(item)
} else if (aiDialogVisible.value && Number(selectedAIKey.value) > -1) {
const item = groupedAIModels.value.find((item) => item.uid === selectedAIKey.value)
handleAI(item)

Check warning on line 270 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L268-L270

Added lines #L268 - L270 were not covered by tests
}
})
onKeyStroke('ArrowUp', (e) => {
e.preventDefault()
handleAitKeyChange(-1)
if (ait.value) {
handleAitKeyChange(-1, personList, virtualListInstAit.value!, selectedAitKey)
} else if (aiDialogVisible.value) {
handleAitKeyChange(-1, groupedAIModels, virtualListInstAI.value!, selectedAIKey)
}

Check warning on line 279 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L275-L279

Added lines #L275 - L279 were not covered by tests
})
onKeyStroke('ArrowDown', (e) => {
e.preventDefault()
handleAitKeyChange(1)
if (ait.value) {
handleAitKeyChange(1, personList, virtualListInstAit.value!, selectedAitKey)
} else if (aiDialogVisible.value) {
handleAitKeyChange(1, groupedAIModels, virtualListInstAI.value!, selectedAIKey)
}

Check warning on line 287 in src/components/rightBox/MsgInput.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/MsgInput.vue#L283-L287

Added lines #L283 - L287 were not covered by tests
})
// TODO: 暂时已经关闭了独立窗口聊天功能
emit('aloneWin')
Expand Down
35 changes: 24 additions & 11 deletions src/components/rightBox/chatBox/ChatHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
class="size-20px color-#13987f select-none outline-none">
<use href="#auth"></use>
</svg>
<n-flex v-else align="center">
<n-badge :color="activeItem.activeStatus === OnlineEnum.ONLINE ? '#1ab292' : '#909090'" dot />
<n-flex v-else-if="activeItem.type === RoomTypeEnum.SINGLE" align="center">
<n-badge :color="isOnline ? '#1ab292' : '#909090'" dot />

Check warning on line 18 in src/components/rightBox/chatBox/ChatHeader.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/chatBox/ChatHeader.vue#L17-L18

Added lines #L17 - L18 were not covered by tests
<p class="text-(12px [--text-color])">
{{ activeItem.activeStatus === OnlineEnum.ONLINE ? '在线' : '离线' }}
{{ isOnline ? '在线' : '离线' }}

Check warning on line 20 in src/components/rightBox/chatBox/ChatHeader.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/chatBox/ChatHeader.vue#L20

Added line #L20 was not covered by tests
</p>
</n-flex>
</n-flex>
Expand Down Expand Up @@ -190,7 +190,7 @@
<script setup lang="ts">
import { IsAllUserEnum, SessionItem, UserItem } from '@/services/types.ts'
import { useDisplayMedia } from '@vueuse/core'
import { EventEnum, RoomActEnum } from '@/enums'
import { EventEnum, MittEnum, RoomActEnum } from '@/enums'

Check warning on line 193 in src/components/rightBox/chatBox/ChatHeader.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/chatBox/ChatHeader.vue#L193

Added line #L193 was not covered by tests
import { emit } from '@tauri-apps/api/event'
import { type } from '@tauri-apps/plugin-os'
import { useChatStore } from '@/stores/chat.ts'
Expand All @@ -201,8 +201,13 @@ import { AvatarUtils } from '@/utils/avatarUtils'
import { OnlineEnum } from '@/enums'
import { useTauriListener } from '@/hooks/useTauriListener'
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import { RoomTypeEnum } from '@/enums'
import { useMitt } from '@/hooks/useMitt.ts'

Check warning on line 205 in src/components/rightBox/chatBox/ChatHeader.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/chatBox/ChatHeader.vue#L204-L205

Added lines #L204 - L205 were not covered by tests
const appWindow = WebviewWindow.getCurrent()
const { activeItem } = defineProps<{
activeItem: SessionItem
}>()
const { addListener } = useTauriListener()
// 使用useDisplayMedia获取屏幕共享的媒体流
const { stream, start, stop } = useDisplayMedia()
Expand All @@ -216,9 +221,13 @@ const modalShow = ref(false)
const sidebarShow = ref(false)
const showLoading = ref(true)
const isLoading = ref(false)
const { activeItem } = defineProps<{
activeItem: SessionItem
}>()
const isOnline = computed(() => {
if (activeItem.type === RoomTypeEnum.GROUP) return false

Check warning on line 225 in src/components/rightBox/chatBox/ChatHeader.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/chatBox/ChatHeader.vue#L224-L225

Added lines #L224 - L225 were not covered by tests
const contact = contactStore.contactsList.find((item) => item.uid === activeItem.friendId)

Check warning on line 227 in src/components/rightBox/chatBox/ChatHeader.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/chatBox/ChatHeader.vue#L227

Added line #L227 was not covered by tests
return contact?.activeStatus === OnlineEnum.ONLINE
})

Check warning on line 230 in src/components/rightBox/chatBox/ChatHeader.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/chatBox/ChatHeader.vue#L229-L230

Added lines #L229 - L230 were not covered by tests
const groupUserList = computed(() => groupStore.userList)
const messageOptions = computed(() => chatStore.currentMessageOptions)
const userList = computed(() => {
Expand Down Expand Up @@ -320,11 +329,15 @@ const handleDelete = (label: RoomActEnum) => {
}
const handleConfirm = () => {
if (optionsType.value === RoomActEnum.DELETE_FRIEND) {
// TODO: 这里需要获取到用户的uid
contactStore.onDeleteContact(1111)
if (optionsType.value === RoomActEnum.DELETE_FRIEND && activeItem.friendId) {
contactStore.onDeleteContact(activeItem.friendId).then(() => {
modalShow.value = false
sidebarShow.value = false
window.$message.success('已删除好友')

Check warning on line 336 in src/components/rightBox/chatBox/ChatHeader.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/chatBox/ChatHeader.vue#L332-L336

Added lines #L332 - L336 were not covered by tests
// 删除当前的会话
useMitt.emit(MittEnum.DELETE_SESSION, activeItem.roomId)
})

Check warning on line 339 in src/components/rightBox/chatBox/ChatHeader.vue

View check run for this annotation

Codecov / codecov/patch

src/components/rightBox/chatBox/ChatHeader.vue#L338-L339

Added lines #L338 - L339 were not covered by tests
}
modalShow.value = false
}
const handleClick = () => {
Expand Down
15 changes: 13 additions & 2 deletions src/enums/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ export enum MittEnum {
/** @ AT */
AT = 'at',
/** 重新编辑 */
RE_EDIT = 'reEdit'
RE_EDIT = 'reEdit',
/** 删除会话 */
DELETE_SESSION = 'deleteSession'
}

/** 主题类型 */
Expand Down Expand Up @@ -144,7 +146,9 @@ export enum MsgEnum {
/** 艾特 */
AIT,
/** 回复 */
REPLY
REPLY,
/** AI */
AI
}

/**
Expand Down Expand Up @@ -321,3 +325,10 @@ export enum MessageStatusEnum {
SUCCESS = 'success',
FAILED = 'failed'
}

/** 触发类型枚举 */
export const enum TriggerEnum {
MENTION = '@',
AI = '/',
TOPIC = '#'
}
Loading

0 comments on commit 1d0f723

Please sign in to comment.