You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

223 lines
4.9 KiB

<script setup lang="ts">
import type { AgentMessage } from "~/composables/useAgentChat";
const props = defineProps<{
message: AgentMessage;
isLoading: boolean;
isLast: boolean;
loggedIn: boolean;
}>();
const emit = defineEmits<{
approve: [toolCallId: string, approved: boolean];
feedback: [messageId: string, feedback: "like" | "dislike"];
edit: [messageId: string, content: string];
regenerate: [];
}>();
const showActions = ref(false);
const isHovering = ref(false);
const isUser = computed(() => props.message.role === "user");
const isAssistant = computed(() => props.message.role === "assistant");
const showLoading = computed(
() => isAssistant.value && props.isLoading && props.isLast && !props.message.parts?.length,
);
const showFeedback = computed(
() => isAssistant.value && !props.isLoading && !!props.message.parts?.length && props.loggedIn,
);
function handleFeedback(type: "like" | "dislike") {
emit("feedback", props.message.id, type);
}
function handleEdit() {
emit("edit", props.message.id, props.message.content);
}
</script>
<template>
<div
class="agent-message"
:class="{ 'is-user': isUser, 'is-assistant': isAssistant }"
@mouseenter="isHovering = true"
@mouseleave="isHovering = false"
>
<!-- User message -->
<div v-if="isUser" class="user-message">
<div class="user-bubble">
{{ message.content }}
</div>
<div class="message-actions" :class="{ visible: isHovering }">
<button class="action-btn" title="编辑" @click="handleEdit">
<Icon name="lucide:pencil" />
</button>
</div>
</div>
<!-- Assistant message -->
<div v-else class="assistant-message">
<div class="assistant-avatar">
<Icon name="lucide:sparkles" />
</div>
<div class="assistant-body">
<div v-if="showLoading" class="assistant-loading">
<span class="loading-dot" />
<span class="loading-dot" />
<span class="loading-dot" />
</div>
<AgentMessageParts
v-else-if="message.parts?.length"
:parts="message.parts"
:is-loading="isLoading"
@approve="(id, approved) => emit('approve', id, approved)"
/>
<AgentMarkdown
v-else-if="message.content"
:text="message.content"
/>
<div v-if="showFeedback" class="assistant-footer" :class="{ visible: isHovering }">
<AgentFeedbackButtons
:feedback="message.feedback"
@like="handleFeedback('like')"
@dislike="handleFeedback('dislike')"
/>
<button
v-if="isLast && !isLoading"
class="action-btn"
title="重新生成"
@click="emit('regenerate')"
>
<Icon name="lucide:refresh-cw" />
</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.agent-message {
padding: 4px 0;
}
/* User message */
.user-message {
display: flex;
justify-content: flex-end;
align-items: flex-start;
gap: 8px;
}
.user-bubble {
max-width: 75%;
padding: 10px 16px;
background: var(--color-surface-soft);
border: 1px solid var(--color-hairline);
border-radius: 18px 18px 4px 18px;
font-size: 15px;
line-height: 1.6;
color: var(--color-ink);
white-space: pre-wrap;
word-break: break-word;
}
.message-actions {
display: flex;
align-items: center;
gap: 4px;
opacity: 0;
transition: opacity 0.15s;
margin-top: 4px;
}
.message-actions.visible {
opacity: 1;
}
/* Assistant message */
.assistant-message {
display: flex;
gap: 12px;
align-items: flex-start;
}
.assistant-avatar {
flex-shrink: 0;
width: 32px;
height: 32px;
border-radius: 8px;
background: var(--color-surface-dark);
color: var(--color-on-dark);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
.assistant-body {
flex: 1;
min-width: 0;
padding-top: 4px;
}
.assistant-loading {
display: flex;
gap: 4px;
padding: 8px 0;
}
.loading-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--color-muted);
animation: bounce 1.4s infinite ease-in-out both;
}
.loading-dot:nth-child(1) { animation-delay: -0.32s; }
.loading-dot:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
40% { transform: scale(1); opacity: 1; }
}
.assistant-footer {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
opacity: 0;
transition: opacity 0.15s;
}
.assistant-footer.visible {
opacity: 1;
}
.action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: none;
background: none;
border-radius: 6px;
cursor: pointer;
color: var(--color-muted);
font-size: 14px;
transition: all 0.15s;
}
.action-btn:hover {
background: var(--color-surface-soft);
color: var(--color-body);
}
</style>