|
@@ -2,6 +2,7 @@
|
|
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
|
import AppIcon from '@/components/common/AppIcon.vue'
|
|
import AppIcon from '@/components/common/AppIcon.vue'
|
|
|
import AgentAvatar from '@/components/common/AgentAvatar.vue'
|
|
import AgentAvatar from '@/components/common/AgentAvatar.vue'
|
|
|
|
|
+import TeamAvatar from '@/components/common/TeamAvatar.vue'
|
|
|
import EmptyState from '@/components/common/EmptyState.vue'
|
|
import EmptyState from '@/components/common/EmptyState.vue'
|
|
|
import TypingIndicator from '@/components/common/TypingIndicator.vue'
|
|
import TypingIndicator from '@/components/common/TypingIndicator.vue'
|
|
|
import InteractionCard from '../messages/InteractionCard.vue'
|
|
import InteractionCard from '../messages/InteractionCard.vue'
|
|
@@ -16,11 +17,20 @@ import { copyTextToClipboard } from '@/utils/clipboard'
|
|
|
import { displayContentForMessage } from '@/utils/messageDisplay'
|
|
import { displayContentForMessage } from '@/utils/messageDisplay'
|
|
|
import { formatMessageTime } from '@/utils/messageTime'
|
|
import { formatMessageTime } from '@/utils/messageTime'
|
|
|
|
|
|
|
|
|
|
+type AvatarState = 'idle' | 'working' | 'waiting' | 'loading' | 'success' | 'failure' | 'notifying'
|
|
|
|
|
+type HeaderAvatarMember = { name: string; avatar?: string; state?: AvatarState; activityKey?: number }
|
|
|
|
|
+
|
|
|
const props = withDefaults(defineProps<{
|
|
const props = withDefaults(defineProps<{
|
|
|
messages: UiMessage[]
|
|
messages: UiMessage[]
|
|
|
identity: string
|
|
identity: string
|
|
|
title?: string
|
|
title?: string
|
|
|
subtitle?: string
|
|
subtitle?: string
|
|
|
|
|
+ headerAvatarName?: string
|
|
|
|
|
+ headerAvatar?: string
|
|
|
|
|
+ headerAvatarKind?: 'agent' | 'team'
|
|
|
|
|
+ headerAvatarMembers?: HeaderAvatarMember[]
|
|
|
|
|
+ headerAvatarState?: AvatarState
|
|
|
|
|
+ headerAvatarActivityKey?: string | number
|
|
|
emptyTitle?: string
|
|
emptyTitle?: string
|
|
|
emptyDescription?: string
|
|
emptyDescription?: string
|
|
|
emptyLogo?: boolean
|
|
emptyLogo?: boolean
|
|
@@ -39,13 +49,18 @@ const props = withDefaults(defineProps<{
|
|
|
approvalAgentName?: string
|
|
approvalAgentName?: string
|
|
|
mentionNames?: string[]
|
|
mentionNames?: string[]
|
|
|
agentAvatars?: Record<string, string>
|
|
agentAvatars?: Record<string, string>
|
|
|
- agentStates?: Record<string, 'idle' | 'working' | 'waiting' | 'loading' | 'success' | 'failure' | 'notifying'>
|
|
|
|
|
|
|
+ agentStates?: Record<string, AvatarState>
|
|
|
thinking?: boolean
|
|
thinking?: boolean
|
|
|
allowBranch?: boolean
|
|
allowBranch?: boolean
|
|
|
readOnly?: boolean
|
|
readOnly?: boolean
|
|
|
}>(), {
|
|
}>(), {
|
|
|
title: '',
|
|
title: '',
|
|
|
subtitle: '',
|
|
subtitle: '',
|
|
|
|
|
+ headerAvatarName: '',
|
|
|
|
|
+ headerAvatar: '',
|
|
|
|
|
+ headerAvatarKind: 'agent',
|
|
|
|
|
+ headerAvatarMembers: () => [],
|
|
|
|
|
+ headerAvatarState: 'idle',
|
|
|
emptyTitle: '开始一段新对话',
|
|
emptyTitle: '开始一段新对话',
|
|
|
emptyDescription: '从下方输入框发送消息,或选择左侧的历史会话。',
|
|
emptyDescription: '从下方输入框发送消息,或选择左侧的历史会话。',
|
|
|
emptyLogo: false,
|
|
emptyLogo: false,
|
|
@@ -298,7 +313,11 @@ defineExpose({ scrollToMessage, scrollToAnchor, scrollToBottom, isFollowingBotto
|
|
|
<section class="timeline-frame" :class="{ 'timeline-frame--transparent': transparentHeader }">
|
|
<section class="timeline-frame" :class="{ 'timeline-frame--transparent': transparentHeader }">
|
|
|
<header v-if="title || transparentHeader" class="timeline-header" :class="{ 'timeline-header--transparent': transparentHeader }">
|
|
<header v-if="title || transparentHeader" class="timeline-header" :class="{ 'timeline-header--transparent': transparentHeader }">
|
|
|
<slot name="header-leading" />
|
|
<slot name="header-leading" />
|
|
|
- <div v-if="!transparentHeader"><h2>{{ title }}</h2><p v-if="subtitle">{{ subtitle }}</p></div>
|
|
|
|
|
|
|
+ <div v-if="!transparentHeader" class="timeline-identity">
|
|
|
|
|
+ <TeamAvatar v-if="headerAvatarName && headerAvatarKind === 'team'" :name="headerAvatarName" :avatar="headerAvatar" :members="headerAvatarMembers" :size="34" />
|
|
|
|
|
+ <AgentAvatar v-else-if="headerAvatarName" class="timeline-header__avatar" :name="headerAvatarName" :avatar="headerAvatar" :state="headerAvatarState" :activity-key="headerAvatarActivityKey" :size="34" />
|
|
|
|
|
+ <div class="timeline-heading"><h2>{{ title }}</h2><p v-if="subtitle">{{ subtitle }}</p></div>
|
|
|
|
|
+ </div>
|
|
|
<div v-if="!transparentHeader" class="timeline-state" :title="connected ? (synced ? '实时连接与历史均已同步' : '实时连接已就绪,历史同步中') : '实时连接未就绪'">
|
|
<div v-if="!transparentHeader" class="timeline-state" :title="connected ? (synced ? '实时连接与历史均已同步' : '实时连接已就绪,历史同步中') : '实时连接未就绪'">
|
|
|
<span class="status-dot" :class="connected ? (synced ? 'status-dot--online' : 'status-dot--working') : ''" />
|
|
<span class="status-dot" :class="connected ? (synced ? 'status-dot--online' : 'status-dot--working') : ''" />
|
|
|
{{ connected ? (synced ? '已同步' : '同步中') : '离线' }}
|
|
{{ connected ? (synced ? '已同步' : '同步中') : '离线' }}
|
|
@@ -497,7 +516,7 @@ defineExpose({ scrollToMessage, scrollToAnchor, scrollToBottom, isFollowingBotto
|
|
|
.timeline-header { display: flex; z-index: 6; min-height: 62px; align-items: center; gap: 13px; padding: 10px 18px; border-bottom: 1px solid var(--line); background: color-mix(in srgb, var(--canvas) 92%, transparent); backdrop-filter: blur(16px); }
|
|
.timeline-header { display: flex; z-index: 6; min-height: 62px; align-items: center; gap: 13px; padding: 10px 18px; border-bottom: 1px solid var(--line); background: color-mix(in srgb, var(--canvas) 92%, transparent); backdrop-filter: blur(16px); }
|
|
|
.timeline-header--transparent { position: absolute; top: 0; right: 0; left: 0; min-height: 0; justify-content: flex-end; padding: 11px 14px; border: 0; background: transparent; backdrop-filter: none; pointer-events: none; }
|
|
.timeline-header--transparent { position: absolute; top: 0; right: 0; left: 0; min-height: 0; justify-content: flex-end; padding: 11px 14px; border: 0; background: transparent; backdrop-filter: none; pointer-events: none; }
|
|
|
.timeline-header--transparent > * { pointer-events: auto; }
|
|
.timeline-header--transparent > * { pointer-events: auto; }
|
|
|
-.timeline-header > div:first-child { min-width: 0; flex: 1; }.timeline-header h2 { overflow: hidden; margin: 0; font-size: 14px; font-weight: 630; letter-spacing: -.02em; text-overflow: ellipsis; white-space: nowrap; }.timeline-header p { overflow: hidden; margin: 3px 0 0; color: var(--text-muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
|
|
+.timeline-identity { display: flex; min-width: 0; flex: 1; align-items: center; gap: 10px; }.timeline-heading { min-width: 0; }.timeline-header h2 { overflow: hidden; margin: 0; font-size: 14px; font-weight: 630; letter-spacing: -.02em; text-overflow: ellipsis; white-space: nowrap; }.timeline-header p { overflow: hidden; margin: 3px 0 0; color: var(--text-muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
.timeline-state { display: flex; align-items: center; gap: 6px; color: var(--text-muted); font-size: 9px; }
|
|
.timeline-state { display: flex; align-items: center; gap: 6px; color: var(--text-muted); font-size: 9px; }
|
|
|
.timeline { min-height: 0; flex: 1; overflow-y: auto; overflow-anchor: none; overscroll-behavior: contain; background: var(--conversation-canvas); }
|
|
.timeline { min-height: 0; flex: 1; overflow-y: auto; overflow-anchor: none; overscroll-behavior: contain; background: var(--conversation-canvas); }
|
|
|
.message-stack { width: min(780px, 100%); min-height: 100%; margin: 0 auto; padding: 24px 24px 36px; }
|
|
.message-stack { width: min(780px, 100%); min-height: 100%; margin: 0 auto; padding: 24px 24px 36px; }
|