|
@@ -12,7 +12,7 @@
|
|
|
<template #title>
|
|
<template #title>
|
|
|
<div class="ai-chat-title">
|
|
<div class="ai-chat-title">
|
|
|
<a-avatar :size="36" class="ai-chat-title__avatar">
|
|
<a-avatar :size="36" class="ai-chat-title__avatar">
|
|
|
- <img src="https://lepao-api.xxoo365.top/uploads/9aca0644-f1f8-4773-b25e-4cd5d268cf0e.png" alt="小妍助理" draggable="false" />
|
|
|
|
|
|
|
+ <img src="@/assets/img/kefu.png" alt="小妍助理" draggable="false" />
|
|
|
</a-avatar>
|
|
</a-avatar>
|
|
|
<div class="ai-chat-title__copy">
|
|
<div class="ai-chat-title__copy">
|
|
|
<div class="ai-chat-title__name">小妍助理</div>
|
|
<div class="ai-chat-title__name">小妍助理</div>
|
|
@@ -55,7 +55,10 @@
|
|
|
@click="openConversation(item.id)"
|
|
@click="openConversation(item.id)"
|
|
|
>
|
|
>
|
|
|
<span class="conversation-item__top">
|
|
<span class="conversation-item__top">
|
|
|
- <span class="conversation-item__title">{{ item.title || '新对话' }}</span>
|
|
|
|
|
|
|
+ <span class="conversation-item__title">
|
|
|
|
|
+ <span v-if="item.channel === 'wechat'" class="channel-badge channel-badge--wechat"><icon-wechat /></span>
|
|
|
|
|
+ {{ item.title || '新对话' }}
|
|
|
|
|
+ </span>
|
|
|
<span v-if="unreadMap[item.id]" class="conversation-item__dot"></span>
|
|
<span v-if="unreadMap[item.id]" class="conversation-item__dot"></span>
|
|
|
</span>
|
|
</span>
|
|
|
<span class="conversation-item__preview">{{ item.last_message_preview || '尚未开始' }}</span>
|
|
<span class="conversation-item__preview">{{ item.last_message_preview || '尚未开始' }}</span>
|
|
@@ -98,11 +101,14 @@
|
|
|
|
|
|
|
|
<div class="chat-toolbar__identity">
|
|
<div class="chat-toolbar__identity">
|
|
|
<a-avatar :size="34" class="chat-toolbar__avatar">
|
|
<a-avatar :size="34" class="chat-toolbar__avatar">
|
|
|
- <img src="@/assets/img/ai.svg" alt="小妍助理" draggable="false" />
|
|
|
|
|
|
|
+ <img src="@/assets/img/ai.svg" alt="小妍助理" draggable="false" v-if="currentConversation?.channel !== 'wechat'"/>
|
|
|
|
|
+ <img src="@/assets/img/wechat.svg" alt="微信聊天" draggable="false" v-else />
|
|
|
</a-avatar>
|
|
</a-avatar>
|
|
|
<div class="chat-toolbar__text">
|
|
<div class="chat-toolbar__text">
|
|
|
- <strong>{{ currentConversation?.title || '新对话' }}</strong>
|
|
|
|
|
- <!-- <span>{{ currentConversation?.conversation_no ? `NO.${currentConversation.conversation_no}` : '准备开始' }}</span> -->
|
|
|
|
|
|
|
+ <strong>
|
|
|
|
|
+ {{ currentConversation?.title || '新对话' }}
|
|
|
|
|
+ </strong>
|
|
|
|
|
+ <span v-if="currentConversation?.readonly">微信会话仅可在网页查看,请在微信内回复</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -123,12 +129,45 @@
|
|
|
|
|
|
|
|
<template v-else>
|
|
<template v-else>
|
|
|
<div v-if="messagesLoading" class="message-loading">
|
|
<div v-if="messagesLoading" class="message-loading">
|
|
|
- <a-spin />
|
|
|
|
|
|
|
+ <div class="message-loading__hero">
|
|
|
|
|
+ <a-avatar :size="42" class="message-loading__avatar">
|
|
|
|
|
+ <img src="@/assets/img/kefu.png" alt="小妍助理" draggable="false" />
|
|
|
|
|
+ </a-avatar>
|
|
|
|
|
+ <div class="message-loading__copy">
|
|
|
|
|
+ <strong>正在切换会话</strong>
|
|
|
|
|
+ <span>为你整理最近的消息</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="message-loading__pulse"></span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="message-loading__thread" aria-hidden="true">
|
|
|
|
|
+ <div class="message-loading__row">
|
|
|
|
|
+ <span class="message-loading__dot"></span>
|
|
|
|
|
+ <div class="message-loading__bubble message-loading__bubble--assistant">
|
|
|
|
|
+ <i></i>
|
|
|
|
|
+ <i></i>
|
|
|
|
|
+ <i></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="message-loading__row message-loading__row--user">
|
|
|
|
|
+ <div class="message-loading__bubble message-loading__bubble--user">
|
|
|
|
|
+ <i></i>
|
|
|
|
|
+ <i></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="message-loading__dot"></span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="message-loading__row">
|
|
|
|
|
+ <span class="message-loading__dot"></span>
|
|
|
|
|
+ <div class="message-loading__bubble message-loading__bubble--assistant message-loading__bubble--short">
|
|
|
|
|
+ <i></i>
|
|
|
|
|
+ <i></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div v-if="!messagesLoading && !messages.length" class="welcome-card">
|
|
<div v-if="!messagesLoading && !messages.length" class="welcome-card">
|
|
|
<a-avatar :size="52" class="welcome-card__avatar">
|
|
<a-avatar :size="52" class="welcome-card__avatar">
|
|
|
- <img src="https://lepao-api.xxoo365.top/uploads/9aca0644-f1f8-4773-b25e-4cd5d268cf0e.png" alt="小妍助理" draggable="false" />
|
|
|
|
|
|
|
+ <img src="@/assets/img/kefu.png" alt="小妍助理" draggable="false" />
|
|
|
</a-avatar>
|
|
</a-avatar>
|
|
|
<div>
|
|
<div>
|
|
|
<div class="welcome-card__title">我是小妍助理</div>
|
|
<div class="welcome-card__title">我是小妍助理</div>
|
|
@@ -155,7 +194,7 @@
|
|
|
:class="`message-row--${message.role}`"
|
|
:class="`message-row--${message.role}`"
|
|
|
>
|
|
>
|
|
|
<a-avatar :size="36" class="message-avatar">
|
|
<a-avatar :size="36" class="message-avatar">
|
|
|
- <img v-if="message.role === 'assistant'" src="https://lepao-api.xxoo365.top/uploads/9aca0644-f1f8-4773-b25e-4cd5d268cf0e.png" alt="小妍助理" draggable="false" />
|
|
|
|
|
|
|
+ <img v-if="message.role === 'assistant'" src="@/assets/img/kefu.png" alt="小妍助理" draggable="false" />
|
|
|
<img v-else-if="message.role === 'user' && avatar" :src="avatar" draggable="false" />
|
|
<img v-else-if="message.role === 'user' && avatar" :src="avatar" draggable="false" />
|
|
|
<icon-user v-else-if="message.role === 'user'" />
|
|
<icon-user v-else-if="message.role === 'user'" />
|
|
|
<icon-info-circle v-else />
|
|
<icon-info-circle v-else />
|
|
@@ -207,7 +246,12 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="composer-wrap">
|
|
|
|
|
|
|
+ <div v-if="currentConversation?.readonly" class="readonly-bar">
|
|
|
|
|
+ <span class="channel-badge channel-badge--wechat"><icon-wechat /></span>
|
|
|
|
|
+ 该会话来自微信,网页端仅支持查看
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else class="composer-wrap">
|
|
|
<div class="composer">
|
|
<div class="composer">
|
|
|
<input ref="fileInput" type="file" accept="image/png,image/jpeg,image/jpg" multiple hidden @change="handleFileInput" />
|
|
<input ref="fileInput" type="file" accept="image/png,image/jpeg,image/jpg" multiple hidden @change="handleFileInput" />
|
|
|
<a-textarea
|
|
<a-textarea
|
|
@@ -257,6 +301,10 @@ const props = defineProps({
|
|
|
visible: {
|
|
visible: {
|
|
|
type: Boolean,
|
|
type: Boolean,
|
|
|
default: false
|
|
default: false
|
|
|
|
|
+ },
|
|
|
|
|
+ action: {
|
|
|
|
|
+ type: Object,
|
|
|
|
|
+ default: null
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
const emit = defineEmits(['close', 'unread-change'])
|
|
const emit = defineEmits(['close', 'unread-change'])
|
|
@@ -288,12 +336,14 @@ const fileInput = ref(null)
|
|
|
const unreadMap = ref({})
|
|
const unreadMap = ref({})
|
|
|
let conversationTimer = null
|
|
let conversationTimer = null
|
|
|
let messageTimer = null
|
|
let messageTimer = null
|
|
|
|
|
+let messagesRequestSeq = 0
|
|
|
|
|
|
|
|
const visible = computed(() => props.visible)
|
|
const visible = computed(() => props.visible)
|
|
|
const drawerWidth = computed(() => (isMobile.value ? '100%' : '940px'))
|
|
const drawerWidth = computed(() => (isMobile.value ? '100%' : '940px'))
|
|
|
const currentConversation = computed(() => conversations.value.find(item => item.id === currentConversationId.value))
|
|
const currentConversation = computed(() => conversations.value.find(item => item.id === currentConversationId.value))
|
|
|
const canDeleteCurrentConversation = computed(() => canShowConversationActions(currentConversation.value))
|
|
const canDeleteCurrentConversation = computed(() => canShowConversationActions(currentConversation.value))
|
|
|
-const canSend = computed(() => currentConversationId.value && (input.value.trim() || uploadingImages.value.length > 0))
|
|
|
|
|
|
|
+const currentReadonly = computed(() => Boolean(currentConversation.value?.readonly || currentConversation.value?.channel === 'wechat'))
|
|
|
|
|
+const canSend = computed(() => currentConversationId.value && !currentReadonly.value && (input.value.trim() || uploadingImages.value.length > 0))
|
|
|
const unreadTotal = computed(() => Object.values(unreadMap.value).reduce((sum, value) => sum + Number(value || 0), 0))
|
|
const unreadTotal = computed(() => Object.values(unreadMap.value).reduce((sum, value) => sum + Number(value || 0), 0))
|
|
|
const seenKey = computed(() => `aiChatSeen:${userStore.uuid || 'guest'}`)
|
|
const seenKey = computed(() => `aiChatSeen:${userStore.uuid || 'guest'}`)
|
|
|
const avatar = computed(() => {
|
|
const avatar = computed(() => {
|
|
@@ -407,29 +457,36 @@ const loadConversations = async () => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const loadMessages = async () => {
|
|
|
|
|
- if (!currentConversationId.value) return
|
|
|
|
|
- if (isLocalConversation(currentConversationId.value)) {
|
|
|
|
|
|
|
+const loadMessages = async (conversationId = currentConversationId.value) => {
|
|
|
|
|
+ if (!conversationId) return
|
|
|
|
|
+ const requestSeq = ++messagesRequestSeq
|
|
|
|
|
+ messages.value = []
|
|
|
|
|
+ if (isLocalConversation(conversationId)) {
|
|
|
messages.value = []
|
|
messages.value = []
|
|
|
messagesLoading.value = false
|
|
messagesLoading.value = false
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
messagesLoading.value = true
|
|
messagesLoading.value = true
|
|
|
try {
|
|
try {
|
|
|
- const res = await listAIChatMessages({ conversation_id: currentConversationId.value, current: 1, pagesize: 80 })
|
|
|
|
|
|
|
+ const res = await listAIChatMessages({ conversation_id: conversationId, current: 1, pagesize: 80 })
|
|
|
|
|
+ if (requestSeq !== messagesRequestSeq || currentConversationId.value !== conversationId) return
|
|
|
if (!res || res.code !== 0) return Message.error(res?.msg || '获取消息失败')
|
|
if (!res || res.code !== 0) return Message.error(res?.msg || '获取消息失败')
|
|
|
messages.value = res.data || []
|
|
messages.value = res.data || []
|
|
|
markCurrentSeen()
|
|
markCurrentSeen()
|
|
|
scrollToBottom()
|
|
scrollToBottom()
|
|
|
} finally {
|
|
} finally {
|
|
|
- messagesLoading.value = false
|
|
|
|
|
|
|
+ if (requestSeq === messagesRequestSeq && currentConversationId.value === conversationId) {
|
|
|
|
|
+ messagesLoading.value = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const pollNewMessages = async () => {
|
|
const pollNewMessages = async () => {
|
|
|
if (!currentConversationId.value || !visible.value || isLocalConversation(currentConversationId.value)) return
|
|
if (!currentConversationId.value || !visible.value || isLocalConversation(currentConversationId.value)) return
|
|
|
|
|
+ const conversationId = currentConversationId.value
|
|
|
const lastId = getLastRemoteMessageId()
|
|
const lastId = getLastRemoteMessageId()
|
|
|
- const res = await listAIChatMessages({ conversation_id: currentConversationId.value, after_id: lastId })
|
|
|
|
|
|
|
+ const res = await listAIChatMessages({ conversation_id: conversationId, after_id: lastId })
|
|
|
|
|
+ if (currentConversationId.value !== conversationId) return
|
|
|
if (!res || res.code !== 0) return
|
|
if (!res || res.code !== 0) return
|
|
|
const incoming = res.data || []
|
|
const incoming = res.data || []
|
|
|
if (!incoming.length) return
|
|
if (!incoming.length) return
|
|
@@ -445,13 +502,21 @@ const pollNewMessages = async () => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const openConversation = async (id) => {
|
|
const openConversation = async (id) => {
|
|
|
|
|
+ if (!id) return
|
|
|
|
|
+ if (currentConversationId.value === id) {
|
|
|
|
|
+ showConversationPanel.value = false
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
currentConversationId.value = id
|
|
currentConversationId.value = id
|
|
|
|
|
+ messagesRequestSeq += 1
|
|
|
|
|
+ messages.value = []
|
|
|
|
|
+ messagesLoading.value = !isLocalConversation(id)
|
|
|
showConversationPanel.value = false
|
|
showConversationPanel.value = false
|
|
|
- await loadMessages()
|
|
|
|
|
|
|
+ await loadMessages(id)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const createConversationAndOpen = async () => {
|
|
const createConversationAndOpen = async () => {
|
|
|
- const blank = conversations.value.find(item => item.local || Number(item.last_message_time || 0) === 0)
|
|
|
|
|
|
|
+ const blank = conversations.value.find(item => (item.local || Number(item.last_message_time || 0) === 0) && item.channel !== 'wechat' && !item.readonly)
|
|
|
if (blank) {
|
|
if (blank) {
|
|
|
await openConversation(blank.id)
|
|
await openConversation(blank.id)
|
|
|
return
|
|
return
|
|
@@ -474,6 +539,15 @@ const createConversationAndOpen = async () => {
|
|
|
scrollToBottom()
|
|
scrollToBottom()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const openDraftConversation = async (draft = '') => {
|
|
|
|
|
+ await createConversationAndOpen()
|
|
|
|
|
+ if (currentReadonly.value) await createConversationAndOpen()
|
|
|
|
|
+ input.value = String(draft || '')
|
|
|
|
|
+ uploadingImages.value = []
|
|
|
|
|
+ await nextTick()
|
|
|
|
|
+ scrollToBottom()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const clearCurrentConversationIfNeeded = (id) => {
|
|
const clearCurrentConversationIfNeeded = (id) => {
|
|
|
if (currentConversationId.value !== id) return
|
|
if (currentConversationId.value !== id) return
|
|
|
currentConversationId.value = null
|
|
currentConversationId.value = null
|
|
@@ -554,6 +628,10 @@ const upsertConversation = (conversation, previousId = null) => {
|
|
|
|
|
|
|
|
const sendMessage = async (overrideContent = '') => {
|
|
const sendMessage = async (overrideContent = '') => {
|
|
|
const presetContent = String(overrideContent || '').trim()
|
|
const presetContent = String(overrideContent || '').trim()
|
|
|
|
|
+ if (currentReadonly.value) {
|
|
|
|
|
+ Message.info('微信会话仅支持查看,请在微信内继续对话')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
if ((!presetContent && !canSend.value) || sending.value) return
|
|
if ((!presetContent && !canSend.value) || sending.value) return
|
|
|
const previousConversationId = currentConversationId.value
|
|
const previousConversationId = currentConversationId.value
|
|
|
const content = presetContent || input.value.trim()
|
|
const content = presetContent || input.value.trim()
|
|
@@ -672,6 +750,13 @@ watch(() => props.visible, async (value) => {
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+watch(() => props.action, async (action) => {
|
|
|
|
|
+ if (!action) return
|
|
|
|
|
+ if (action.newConversation || action.draft) {
|
|
|
|
|
+ await openDraftConversation(action.draft || '')
|
|
|
|
|
+ }
|
|
|
|
|
+}, { deep: true })
|
|
|
|
|
+
|
|
|
watch(unreadTotal, value => emit('unread-change', value))
|
|
watch(unreadTotal, value => emit('unread-change', value))
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
@@ -761,7 +846,10 @@ onUnmounted(() => {
|
|
|
min-height: 0;
|
|
min-height: 0;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
margin: 0;
|
|
margin: 0;
|
|
|
- background: linear-gradient(135deg, #f7f9ff 0%, #ffffff 45%, #f6fbf8 100%);
|
|
|
|
|
|
|
+ background:
|
|
|
|
|
+ radial-gradient(circle at 18% 12%, rgba(43, 179, 169, 0.14), transparent 30%),
|
|
|
|
|
+ radial-gradient(circle at 90% 6%, rgba(116, 89, 255, 0.12), transparent 28%),
|
|
|
|
|
+ linear-gradient(135deg, #f7f9ff 0%, #ffffff 48%, #f3fbf8 100%);
|
|
|
border-top: 1px solid rgba(29, 33, 41, 0.08);
|
|
border-top: 1px solid rgba(29, 33, 41, 0.08);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -780,7 +868,7 @@ onUnmounted(() => {
|
|
|
min-height: 0;
|
|
min-height: 0;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
border-right: 1px solid rgba(29, 33, 41, 0.08);
|
|
border-right: 1px solid rgba(29, 33, 41, 0.08);
|
|
|
- background: rgba(255, 255, 255, 0.78);
|
|
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.84);
|
|
|
backdrop-filter: blur(18px);
|
|
backdrop-filter: blur(18px);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -790,6 +878,7 @@ onUnmounted(() => {
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
padding: 18px 16px 12px;
|
|
padding: 18px 16px 12px;
|
|
|
|
|
+ border-bottom: 1px solid rgba(29, 33, 41, 0.05);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.conversation-panel__label {
|
|
.conversation-panel__label {
|
|
@@ -854,18 +943,35 @@ onUnmounted(() => {
|
|
|
padding: 13px 14px;
|
|
padding: 13px 14px;
|
|
|
border: 1px solid rgba(29, 33, 41, 0.07);
|
|
border: 1px solid rgba(29, 33, 41, 0.07);
|
|
|
border-radius: 8px;
|
|
border-radius: 8px;
|
|
|
- background: rgba(255, 255, 255, 0.58);
|
|
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.72);
|
|
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.04);
|
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.04);
|
|
|
text-align: left;
|
|
text-align: left;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease, background 0.18s ease;
|
|
transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease, background 0.18s ease;
|
|
|
|
|
|
|
|
|
|
+ &::before {
|
|
|
|
|
+ content: '';
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ inset: 9px auto 9px 0;
|
|
|
|
|
+ width: 3px;
|
|
|
|
|
+ border-radius: 0 999px 999px 0;
|
|
|
|
|
+ background: linear-gradient(180deg, #2bb3a9, #7459ff);
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ transform: scaleY(0.45);
|
|
|
|
|
+ transition: opacity 0.18s ease, transform 0.18s ease;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
&:hover,
|
|
&:hover,
|
|
|
&--active {
|
|
&--active {
|
|
|
border-color: rgba(116, 89, 255, 0.36);
|
|
border-color: rgba(116, 89, 255, 0.36);
|
|
|
background: #ffffff;
|
|
background: #ffffff;
|
|
|
box-shadow: 0 12px 28px rgba(82, 71, 190, 0.12);
|
|
box-shadow: 0 12px 28px rgba(82, 71, 190, 0.12);
|
|
|
transform: translateY(-1px);
|
|
transform: translateY(-1px);
|
|
|
|
|
+
|
|
|
|
|
+ &::before {
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+ transform: scaleY(1);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
&--local {
|
|
&--local {
|
|
@@ -889,6 +995,9 @@ onUnmounted(() => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
&__title {
|
|
&__title {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
min-width: 0;
|
|
min-width: 0;
|
|
|
color: #1d2129;
|
|
color: #1d2129;
|
|
|
font-weight: 650;
|
|
font-weight: 650;
|
|
@@ -914,6 +1023,38 @@ onUnmounted(() => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.channel-badge {
|
|
|
|
|
+ display: inline-grid;
|
|
|
|
|
+ place-items: center;
|
|
|
|
|
+ width: 18px;
|
|
|
|
|
+ height: 18px;
|
|
|
|
|
+ flex: 0 0 auto;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+
|
|
|
|
|
+ &--wechat {
|
|
|
|
|
+ background: linear-gradient(135deg, #12b76a, #039855);
|
|
|
|
|
+ box-shadow: 0 6px 14px rgba(18, 183, 106, 0.22);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.readonly-bar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin: 0 18px 18px;
|
|
|
|
|
+ padding: 12px 14px;
|
|
|
|
|
+ border: 1px solid rgba(18, 183, 106, 0.22);
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ background: rgba(240, 253, 244, 0.92);
|
|
|
|
|
+ color: #027a48;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ box-shadow: 0 12px 28px rgba(3, 152, 85, 0.08);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.chat-main {
|
|
.chat-main {
|
|
|
position: relative;
|
|
position: relative;
|
|
|
display: grid;
|
|
display: grid;
|
|
@@ -921,6 +1062,10 @@ onUnmounted(() => {
|
|
|
min-width: 0;
|
|
min-width: 0;
|
|
|
min-height: 0;
|
|
min-height: 0;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
|
|
+ background:
|
|
|
|
|
+ linear-gradient(180deg, rgba(255, 255, 255, 0.32), rgba(255, 255, 255, 0.74)),
|
|
|
|
|
+ radial-gradient(circle at 24px 24px, rgba(116, 89, 255, 0.07) 0 1px, transparent 1px);
|
|
|
|
|
+ background-size: auto, 22px 22px;
|
|
|
|
|
|
|
|
&--dragging::after {
|
|
&--dragging::after {
|
|
|
content: '释放上传图片';
|
|
content: '释放上传图片';
|
|
@@ -946,7 +1091,7 @@ onUnmounted(() => {
|
|
|
min-height: 56px;
|
|
min-height: 56px;
|
|
|
padding: 10px 16px;
|
|
padding: 10px 16px;
|
|
|
border-bottom: 1px solid rgba(29, 33, 41, 0.08);
|
|
border-bottom: 1px solid rgba(29, 33, 41, 0.08);
|
|
|
- background: rgba(255, 255, 255, 0.62);
|
|
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.78);
|
|
|
backdrop-filter: blur(16px);
|
|
backdrop-filter: blur(16px);
|
|
|
|
|
|
|
|
&__identity {
|
|
&__identity {
|
|
@@ -959,7 +1104,8 @@ onUnmounted(() => {
|
|
|
|
|
|
|
|
&__avatar {
|
|
&__avatar {
|
|
|
flex: 0 0 auto;
|
|
flex: 0 0 auto;
|
|
|
- background: #eef4ff;
|
|
|
|
|
|
|
+ background: linear-gradient(135deg, #eef7ff, #edfdf7);
|
|
|
|
|
+ box-shadow: 0 8px 20px rgba(43, 179, 169, 0.12);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
&__text {
|
|
&__text {
|
|
@@ -1032,9 +1178,157 @@ onUnmounted(() => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.message-loading {
|
|
.message-loading {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ align-content: center;
|
|
|
|
|
+ gap: 22px;
|
|
|
|
|
+ min-height: 100%;
|
|
|
|
|
+ padding: 22px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__hero {
|
|
|
|
|
+ position: relative;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- justify-content: center;
|
|
|
|
|
- padding: 18px;
|
|
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ width: min(430px, 100%);
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ padding: 14px 16px;
|
|
|
|
|
+ border: 1px solid rgba(116, 89, 255, 0.14);
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.78);
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ 0 20px 46px rgba(36, 48, 94, 0.08),
|
|
|
|
|
+ inset 0 1px 0 rgba(255, 255, 255, 0.86);
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__hero::before {
|
|
|
|
|
+ content: '';
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ background: linear-gradient(110deg, transparent 0%, rgba(255, 255, 255, 0.7) 42%, transparent 70%);
|
|
|
|
|
+ transform: translateX(-100%);
|
|
|
|
|
+ animation: ai-chat-shimmer 1.9s ease-in-out infinite;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__avatar {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ z-index: 1;
|
|
|
|
|
+ flex: 0 0 auto;
|
|
|
|
|
+ background: linear-gradient(135deg, #eaf2ff, #edfbf7);
|
|
|
|
|
+ box-shadow: 0 10px 24px rgba(43, 179, 169, 0.16);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__copy {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ z-index: 1;
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: 3px;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__copy strong {
|
|
|
|
|
+ color: #1d2129;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 720;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__copy span {
|
|
|
|
|
+ color: #667085;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__pulse {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ z-index: 1;
|
|
|
|
|
+ width: 10px;
|
|
|
|
|
+ height: 10px;
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: #2bb3a9;
|
|
|
|
|
+ box-shadow: 0 0 0 0 rgba(43, 179, 169, 0.28);
|
|
|
|
|
+ animation: ai-chat-pulse 1.4s ease-out infinite;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__thread {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ width: min(560px, 100%);
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__row--user {
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__dot {
|
|
|
|
|
+ width: 34px;
|
|
|
|
|
+ height: 34px;
|
|
|
|
|
+ flex: 0 0 34px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background:
|
|
|
|
|
+ linear-gradient(135deg, rgba(234, 242, 255, 0.95), rgba(237, 251, 247, 0.95));
|
|
|
|
|
+ box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__bubble {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: 9px;
|
|
|
|
|
+ width: min(72%, 390px);
|
|
|
|
|
+ padding: 14px;
|
|
|
|
|
+ border: 1px solid rgba(29, 33, 41, 0.06);
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.76);
|
|
|
|
|
+ box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__bubble--user {
|
|
|
|
|
+ width: min(58%, 310px);
|
|
|
|
|
+ background: linear-gradient(135deg, rgba(116, 89, 255, 0.9), rgba(43, 179, 169, 0.86));
|
|
|
|
|
+
|
|
|
|
|
+ i {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.46);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__bubble--short {
|
|
|
|
|
+ width: min(52%, 280px);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__bubble i {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ height: 10px;
|
|
|
|
|
+ border-radius: 999px;
|
|
|
|
|
+ background:
|
|
|
|
|
+ linear-gradient(90deg, rgba(226, 232, 240, 0.76), rgba(245, 247, 251, 0.96), rgba(226, 232, 240, 0.76));
|
|
|
|
|
+ background-size: 220% 100%;
|
|
|
|
|
+ animation: ai-chat-skeleton 1.45s ease-in-out infinite;
|
|
|
|
|
+
|
|
|
|
|
+ &:nth-child(1) {
|
|
|
|
|
+ width: 92%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:nth-child(2) {
|
|
|
|
|
+ width: 74%;
|
|
|
|
|
+ animation-delay: 0.08s;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:nth-child(3) {
|
|
|
|
|
+ width: 48%;
|
|
|
|
|
+ animation-delay: 0.16s;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-loading__bubble--user i {
|
|
|
|
|
+ background:
|
|
|
|
|
+ linear-gradient(90deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.62), rgba(255, 255, 255, 0.3));
|
|
|
|
|
+ background-size: 220% 100%;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.empty-state,
|
|
.empty-state,
|
|
@@ -1113,6 +1407,7 @@ onUnmounted(() => {
|
|
|
align-items: flex-start;
|
|
align-items: flex-start;
|
|
|
gap: 11px;
|
|
gap: 11px;
|
|
|
margin-bottom: 18px;
|
|
margin-bottom: 18px;
|
|
|
|
|
+ animation: ai-chat-message-in 0.24s ease-out both;
|
|
|
|
|
|
|
|
&--user {
|
|
&--user {
|
|
|
flex-direction: row-reverse;
|
|
flex-direction: row-reverse;
|
|
@@ -1127,8 +1422,9 @@ onUnmounted(() => {
|
|
|
|
|
|
|
|
.message-bubble {
|
|
.message-bubble {
|
|
|
color: #ffffff;
|
|
color: #ffffff;
|
|
|
- background: linear-gradient(135deg, #6f63ff, #22a08f);
|
|
|
|
|
- box-shadow: 0 12px 28px rgba(82, 71, 190, 0.18);
|
|
|
|
|
|
|
+ border-color: rgba(255, 255, 255, 0.2);
|
|
|
|
|
+ background: linear-gradient(135deg, #6757f5, #1aa99a);
|
|
|
|
|
+ box-shadow: 0 16px 32px rgba(43, 86, 132, 0.2);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.message-content :deep(a) {
|
|
.message-content :deep(a) {
|
|
@@ -1180,8 +1476,10 @@ onUnmounted(() => {
|
|
|
padding: 12px 14px;
|
|
padding: 12px 14px;
|
|
|
border: 1px solid rgba(29, 33, 41, 0.07);
|
|
border: 1px solid rgba(29, 33, 41, 0.07);
|
|
|
border-radius: 14px;
|
|
border-radius: 14px;
|
|
|
- background: rgba(255, 255, 255, 0.86);
|
|
|
|
|
- box-shadow: 0 12px 28px rgba(15, 23, 42, 0.06);
|
|
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.92);
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ 0 14px 30px rgba(15, 23, 42, 0.06),
|
|
|
|
|
+ inset 0 1px 0 rgba(255, 255, 255, 0.78);
|
|
|
line-height: 1.72;
|
|
line-height: 1.72;
|
|
|
word-break: break-word;
|
|
word-break: break-word;
|
|
|
}
|
|
}
|
|
@@ -1315,7 +1613,7 @@ onUnmounted(() => {
|
|
|
|
|
|
|
|
.composer-wrap {
|
|
.composer-wrap {
|
|
|
padding: 12px 18px 20px;
|
|
padding: 12px 18px 20px;
|
|
|
- background: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(250, 252, 255, 0.96) 32%);
|
|
|
|
|
|
|
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(250, 252, 255, 0.98) 32%);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.composer {
|
|
.composer {
|
|
@@ -1324,19 +1622,19 @@ onUnmounted(() => {
|
|
|
gap: 8px;
|
|
gap: 8px;
|
|
|
min-height: 98px;
|
|
min-height: 98px;
|
|
|
padding: 12px 14px 10px;
|
|
padding: 12px 14px 10px;
|
|
|
- border: 1px solid rgba(143, 118, 255, 0.42);
|
|
|
|
|
- border-radius: 22px;
|
|
|
|
|
|
|
+ border: 1px solid rgba(103, 87, 245, 0.28);
|
|
|
|
|
+ border-radius: 18px;
|
|
|
background: rgba(255, 255, 255, 0.96);
|
|
background: rgba(255, 255, 255, 0.96);
|
|
|
box-shadow:
|
|
box-shadow:
|
|
|
- 0 18px 44px rgba(50, 42, 130, 0.1),
|
|
|
|
|
- 0 0 0 3px rgba(175, 147, 255, 0.08);
|
|
|
|
|
|
|
+ 0 18px 44px rgba(35, 48, 86, 0.1),
|
|
|
|
|
+ 0 0 0 3px rgba(43, 179, 169, 0.06);
|
|
|
transition: border-color 0.18s ease, box-shadow 0.18s ease;
|
|
transition: border-color 0.18s ease, box-shadow 0.18s ease;
|
|
|
|
|
|
|
|
&:focus-within {
|
|
&:focus-within {
|
|
|
- border-color: rgba(143, 118, 255, 0.72);
|
|
|
|
|
|
|
+ border-color: rgba(43, 179, 169, 0.5);
|
|
|
box-shadow:
|
|
box-shadow:
|
|
|
- 0 20px 48px rgba(50, 42, 130, 0.13),
|
|
|
|
|
- 0 0 0 4px rgba(175, 147, 255, 0.12);
|
|
|
|
|
|
|
+ 0 20px 48px rgba(35, 48, 86, 0.13),
|
|
|
|
|
+ 0 0 0 4px rgba(43, 179, 169, 0.12);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
:deep(.arco-textarea-wrapper) {
|
|
:deep(.arco-textarea-wrapper) {
|
|
@@ -1406,8 +1704,8 @@ onUnmounted(() => {
|
|
|
width: 32px;
|
|
width: 32px;
|
|
|
height: 32px;
|
|
height: 32px;
|
|
|
border: 0;
|
|
border: 0;
|
|
|
- background: linear-gradient(135deg, #c9a8ff, #8f7cff);
|
|
|
|
|
- box-shadow: 0 8px 18px rgba(143, 118, 255, 0.28);
|
|
|
|
|
|
|
+ background: linear-gradient(135deg, #6757f5, #20b7a7);
|
|
|
|
|
+ box-shadow: 0 10px 22px rgba(43, 179, 169, 0.28);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1435,6 +1733,56 @@ onUnmounted(() => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+@keyframes ai-chat-shimmer {
|
|
|
|
|
+ 0% {
|
|
|
|
|
+ transform: translateX(-100%);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 58%,
|
|
|
|
|
+ 100% {
|
|
|
|
|
+ transform: translateX(100%);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@keyframes ai-chat-skeleton {
|
|
|
|
|
+ 0% {
|
|
|
|
|
+ background-position: 120% 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 100% {
|
|
|
|
|
+ background-position: -120% 0;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@keyframes ai-chat-pulse {
|
|
|
|
|
+ 0% {
|
|
|
|
|
+ box-shadow: 0 0 0 0 rgba(43, 179, 169, 0.3);
|
|
|
|
|
+ transform: scale(0.92);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 70% {
|
|
|
|
|
+ box-shadow: 0 0 0 9px rgba(43, 179, 169, 0);
|
|
|
|
|
+ transform: scale(1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 100% {
|
|
|
|
|
+ box-shadow: 0 0 0 0 rgba(43, 179, 169, 0);
|
|
|
|
|
+ transform: scale(0.92);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@keyframes ai-chat-message-in {
|
|
|
|
|
+ from {
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ transform: translateY(5px);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ to {
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+ transform: translateY(0);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
@media (max-width: 768px) {
|
|
@media (max-width: 768px) {
|
|
|
.ai-chat-shell {
|
|
.ai-chat-shell {
|
|
|
grid-template-columns: 1fr;
|
|
grid-template-columns: 1fr;
|
|
@@ -1467,6 +1815,29 @@ onUnmounted(() => {
|
|
|
padding: 16px 12px 12px;
|
|
padding: 16px 12px 12px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ .message-loading {
|
|
|
|
|
+ gap: 18px;
|
|
|
|
|
+ padding: 14px 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .message-loading__hero {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ border-radius: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .message-loading__thread {
|
|
|
|
|
+ gap: 13px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .message-loading__bubble {
|
|
|
|
|
+ width: min(76%, 320px);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .message-loading__bubble--user {
|
|
|
|
|
+ width: min(68%, 280px);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.welcome-card {
|
|
.welcome-card {
|
|
|
min-height: 260px;
|
|
min-height: 260px;
|
|
|
padding: 0 6px;
|
|
padding: 0 6px;
|