Files
miniai/src/components/MessageList.vue
T

57 lines
2.0 KiB
Vue

<template>
<div class="list">
<div v-for="m in messages" :key="m.id" class="row" :class="m.role">
<div class="avatar" :class="m.role">
<AssistantAvatar v-if="m.role === 'assistant'" />
<template v-else>🧑</template>
</div>
<div class="col">
<div class="meta">{{ m.role === "user" ? "你" : "小鑫" }}</div>
<MessageBubble :msg="m" @approve="(p: any) => $emit('approve', p)" @reject="(p: any) => $emit('reject', p)" />
<div v-if="m.loading" class="typing"><span></span><span></span><span></span></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import MessageBubble from "./MessageBubble.vue";
import AssistantAvatar from "./AssistantAvatar.vue";
interface Msg {
id: number;
role: "user" | "assistant" | "system";
content: string;
loading?: boolean;
approval?: any;
tools?: any[];
}
defineProps<{ messages: Msg[] }>();
defineEmits<{ approve: [payload: any]; reject: [payload: any] }>();
</script>
<style scoped>
.list { display: flex; flex-direction: column; gap: 18px; }
.row { display: flex; gap: 12px; align-items: flex-start; }
.row.user { flex-direction: row-reverse; }
.avatar {
width: 36px; height: 36px; border-radius: 10px; flex-shrink: 0;
display: grid; place-items: center; font-size: 18px;
background: var(--surface-2); border: 1px solid var(--border);
}
.avatar.assistant { background: transparent; border: none; overflow: hidden; padding: 0; }
.col { display: flex; flex-direction: column; gap: 4px; max-width: 78%; }
.row.user .col { align-items: flex-end; }
.meta { font-size: 11px; color: var(--text-dim); padding: 0 4px; }
.typing { display: flex; gap: 4px; padding: 6px 4px; }
.typing span {
width: 7px; height: 7px; border-radius: 50%; background: var(--text-dim);
animation: blink 1.2s infinite ease-in-out;
}
.typing span:nth-child(2) { animation-delay: 0.2s; }
.typing span:nth-child(3) { animation-delay: 0.4s; }
@keyframes blink { 0%, 60%, 100% { opacity: 0.25; } 30% { opacity: 1; } }
</style>