|
|
@@ -1,6 +1,8 @@
|
|
|
<script setup lang="ts">
|
|
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
|
import AppIcon from '@/components/common/AppIcon.vue'
|
|
|
+import MessageFailureNotice from '@/components/messages/MessageFailureNotice.vue'
|
|
|
+import { messageFailure } from '@/utils/messageFailure'
|
|
|
import AgentAvatar from '@/components/common/AgentAvatar.vue'
|
|
|
import EmptyState from '@/components/common/EmptyState.vue'
|
|
|
import TypingIndicator from '@/components/common/TypingIndicator.vue'
|
|
|
@@ -258,6 +260,7 @@ defineExpose({ scrollToMessage, scrollToAnchor, scrollToBottom, isFollowingBotto
|
|
|
class="message"
|
|
|
:class="[`message--${message.role}`, {
|
|
|
'message--failed': message.status === 'failed',
|
|
|
+ 'message--failure-notice': messageFailure(message)?.replacesContent,
|
|
|
'message--tool-only': message.role === 'assistant' && !message.content && !message.reasoning && Boolean(message.tools?.length),
|
|
|
'message--assistant-internal': message.role === 'assistant' && !message.content.trim(),
|
|
|
'message--assistant-anonymous': message.role === 'assistant' && !showAssistantIdentity,
|
|
|
@@ -265,7 +268,8 @@ defineExpose({ scrollToMessage, scrollToAnchor, scrollToBottom, isFollowingBotto
|
|
|
>
|
|
|
<AgentAvatar v-if="message.role !== 'user' && (message.role !== 'assistant' || showAssistantIdentity)" class="message__avatar" :name="message.author || message.profile || (message.role === 'assistant' ? '夭' : '系')" :avatar="message.profile ? agentAvatars[message.profile] || '' : ''" :size="27" :state="avatarState(message)" />
|
|
|
<div class="message__body">
|
|
|
- <template v-if="message.timelineKind === 'delegation-complete'">
|
|
|
+ <MessageFailureNotice v-if="messageFailure(message)?.replacesContent && !message.attachments?.length" :failure="messageFailure(message)!" />
|
|
|
+ <template v-else-if="message.timelineKind === 'delegation-complete'">
|
|
|
<details class="delegation-event">
|
|
|
<summary><AppIcon name="groups" :size="14" /><span><strong>子任务已完成</strong><small>{{ delegationSummary(message.timelineMetadata) }}</small></span></summary>
|
|
|
<MarkdownContent process-content v-if="message.content" :content="message.content" />
|
|
|
@@ -294,7 +298,7 @@ defineExpose({ scrollToMessage, scrollToAnchor, scrollToBottom, isFollowingBotto
|
|
|
<strong><AppIcon v-if="message.isRemoteAgent" class="message__remote-agent" name="globe" :size="12" />{{ message.role === 'user' ? '你' : message.author || message.profile || (message.role === 'assistant' ? '机器人' : '系统') }}</strong>
|
|
|
<span v-if="message.metadata" class="message__execution">{{ message.metadata }}</span>
|
|
|
<time>{{ formatTime(message.createdAt) }}</time>
|
|
|
- <span v-if="message.status && !['settled', 'streaming'].includes(message.status)">{{ { preparing: '准备中', attached: '附件已就绪', pending: '等待回执', accepted: '已接收', streaming: '生成中', settled: '已完成', failed: '发送失败', 'unknown-receipt': '回执未知' }[message.status] }}</span>
|
|
|
+ <span v-if="message.status && !['settled', 'streaming'].includes(message.status)">{{ { preparing: '准备中', attached: '附件已就绪', pending: '等待回执', accepted: '已接收', streaming: '生成中', settled: '已完成', failed: message.role === 'user' ? '发送失败' : '回复中断', 'unknown-receipt': '回执未知' }[message.status] }}</span>
|
|
|
</div>
|
|
|
|
|
|
<details v-if="message.reasoning" class="message__reasoning">
|
|
|
@@ -310,7 +314,7 @@ defineExpose({ scrollToMessage, scrollToAnchor, scrollToBottom, isFollowingBotto
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
- <div class="message__content">
|
|
|
+ <div v-if="!messageFailure(message)?.replacesContent" class="message__content">
|
|
|
<MarkdownContent
|
|
|
:file-profile="message.profile"
|
|
|
:content="displayContentForMessage(message.role, message.content)"
|
|
|
@@ -349,7 +353,7 @@ defineExpose({ scrollToMessage, scrollToAnchor, scrollToBottom, isFollowingBotto
|
|
|
<ToolTrace v-for="tool in message.tools" :key="tool.id" :tool="tool" />
|
|
|
</div>
|
|
|
|
|
|
- <p v-if="message.error" class="message__error"><AppIcon name="alert" :size="13" />{{ message.error }}</p>
|
|
|
+ <MessageFailureNotice v-if="messageFailure(message)" :failure="messageFailure(message)!" />
|
|
|
|
|
|
<div v-if="hasConversationActions(message)" class="message__actions">
|
|
|
<time v-if="message.role === 'assistant' && !showAssistantIdentity && message.createdAt" class="message__action-time">{{ formatTime(message.createdAt) }}</time>
|
|
|
@@ -431,7 +435,7 @@ defineExpose({ scrollToMessage, scrollToAnchor, scrollToBottom, isFollowingBotto
|
|
|
.fork-divider { display: flex; align-items: center; gap: 12px; margin: 8px 0 28px; color: var(--text-muted); font-size: 11px; }.fork-divider::before, .fork-divider::after { height: 1px; flex: 1; background: var(--line); content: ''; }.fork-divider > span { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface-raised); white-space: nowrap; }.fork-divider strong { max-width: 180px; overflow: hidden; color: var(--text-secondary); text-overflow: ellipsis; }
|
|
|
.message__attachments { display: flex; margin-top: 8px; flex-wrap: wrap; gap: 7px; }.message__attachments button { display: flex; min-width: 90px; max-width: 210px; min-height: 42px; align-items: center; gap: 7px; padding: 5px 8px; overflow: hidden; border: 1px solid var(--line); border-radius: 9px; background: var(--surface); color: var(--text-secondary); cursor: pointer; }.message__attachments button:hover { border-color: var(--line-strong); }.message__attachments img { width: 48px; height: 48px; margin: -5px 0 -5px -8px; object-fit: cover; }.message__attachments button > span { display: grid; place-items: center; width: 25px; height: 25px; border-radius: 7px; background: var(--surface-soft); }.message__attachments strong { min-width: 0; overflow: hidden; font-size: 11px; font-weight: 520; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
.message--user .message__attachments { margin: 0 0 9px; }.message--user .message__attachments button { width: 100%; max-width: none; min-height: 50px; padding: 7px 9px; border: 0; border-radius: 11px; background: rgba(255,255,255,.48); color: var(--text-primary); }.message--user .message__attachments button > span { width: 28px; height: 28px; background: rgba(255,255,255,.65); }.message--user .message__attachments strong { font-size: 12px; font-weight: 560; }.dark .message--user .message__attachments button { background: rgba(255,255,255,.08); }.message--user .message__attachments button.message__attachment--image { display: block; width: auto; min-width: 0; max-width: min(360px, 100%); min-height: 0; padding: 0; border: 0; border-radius: 8px; background: transparent; }.message--user .message__attachments button.message__attachment--image img { display: block; width: auto; height: auto; max-width: min(360px, 100%); max-height: 420px; margin: 0; border-radius: inherit; object-fit: contain; }
|
|
|
-.message__tools { margin-top: 9px; }.message__error { display: flex; align-items: center; gap: 5px; margin: 7px 0 0; color: var(--danger); font-size: 10px; }
|
|
|
+.message__tools { margin-top: 9px; }
|
|
|
.message__actions { display: flex; position: absolute; left: -3px; top: 100%; gap: 2px; padding-top: 2px; opacity: 0; transition: opacity 120ms ease; }.message:hover .message__actions, .message:focus-within .message__actions { opacity: 1; }.message--user .message__actions { right: 0; left: auto; }.message__actions button { display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 7px; background: transparent; color: var(--text-muted); cursor: pointer; }.message__actions button:hover { background: var(--surface-soft); color: var(--text-primary); }.message__actions .message-action--copied { color: var(--success); }.message__actions .message-action--copy-failed { color: var(--danger); }
|
|
|
.message--assistant-anonymous .message__actions { align-items: center; }.message__action-time { min-width: 36px; padding: 0 4px 0 3px; color: var(--text-muted); font-size: 10px; font-variant-numeric: tabular-nums; }
|
|
|
.message--failed .message__body { border-color: color-mix(in srgb, var(--danger) 35%, var(--line)); }
|
|
|
@@ -444,6 +448,10 @@ defineExpose({ scrollToMessage, scrollToAnchor, scrollToBottom, isFollowingBotto
|
|
|
.message-skeletons { width: min(760px, 100%); margin: 0 auto; padding: 35px 24px; }.message-skeletons div { display: flex; gap: 10px; margin: 0 0 25px; }.message-skeletons div.user { justify-content: flex-end; }.message-skeletons i { width: 27px; height: 27px; border-radius: 9px; background: var(--surface-hover); }.message-skeletons span { width: min(480px, 70%); height: 64px; border-radius: 12px; background: linear-gradient(90deg, var(--surface-soft), var(--surface-hover), var(--surface-soft)); background-size: 200% 100%; animation: shimmer 1.5s linear infinite; }.message-skeletons .user i { display: none; }.message-skeletons .user span { width: 44%; height: 46px; }
|
|
|
@keyframes shimmer { to { background-position: -200% 0; } }
|
|
|
|
|
|
+.message--failure-notice { justify-content: flex-start; }
|
|
|
+.message--failure-notice .message__avatar { display: none; }
|
|
|
+.message--failure-notice .message__body { width: min(460px, 100%); max-width: 100%; padding: 0; border: 0; background: transparent; text-align: left; }
|
|
|
+
|
|
|
@media (max-width: 768px) {
|
|
|
.timeline-header { min-height: 54px; padding: 8px 12px; }.timeline-state { font-size: 0; }.timeline-state .status-dot { width: 7px; height: 7px; }
|
|
|
.new-chat-empty { gap: 15px; padding-block: 9vh; }.new-chat-empty__logo { width: min(150px, 42vw); }.new-chat-empty p { font-size: 16px; }
|