Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(component): ✨ 新增AI选项卡,封装提及框功能 #133

Merged
merged 1 commit into from
Jan 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
/** 输入框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 {
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(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 @@
}

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 { 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 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 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
Loading