-
![]()
-
{{ initial }}
+
{{ agentName }}
{{ welcomeMessage }}
@@ -112,12 +99,6 @@ watch(
border-radius: var(--radius-panel);
}
-.agent-welcome__avatar img {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
.agent-welcome__name {
margin: var(--space-3) 0 0;
font-size: 16px;
diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowChatPage.vue b/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowChatPage.vue
index 3197deb5..63d80af8 100644
--- a/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowChatPage.vue
+++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/WorkflowChatPage.vue
@@ -15,7 +15,7 @@ import type {
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
-import { ChatTimeline } from '@easyflow/common-ui';
+import { ChatTimeline, defaultAssistantAvatar } from '@easyflow/common-ui';
import {
ArrowLeft,
@@ -931,8 +931,10 @@ function executionTraceText(
diff --git a/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatAssistantAvatar.vue b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatAssistantAvatar.vue
new file mode 100644
index 00000000..72e3b923
--- /dev/null
+++ b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatAssistantAvatar.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
diff --git a/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatTimeline.vue b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatTimeline.vue
index df05ec22..30fc60c2 100644
--- a/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatTimeline.vue
+++ b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatTimeline.vue
@@ -9,14 +9,17 @@ import type {
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
+import ChatAssistantAvatar from './ChatAssistantAvatar.vue';
import ChatTimelineItem from './ChatTimelineItem.vue';
const props = defineProps<{
approvalLoading?: boolean;
+ assistantAvatar?: string;
copyable?: (item: ChatTimelineMessageItem) => boolean;
copyAction?: (item: ChatTimelineMessageItem) => boolean | Promise;
documentLoader?: ChatDocumentLoader;
emptyText?: string;
+ emptyTitle?: string;
imageLoader?: ChatImageLoader;
items: ChatTimelineItemType[];
regenerable?: (item: ChatTimelineMessageItem) => boolean;
@@ -143,7 +146,16 @@ watch(
@scroll.passive="handleTimelineScroll"
>
-
+
+
+
+
+ {{ emptyTitle }}
+
{{ emptyText || '开始对话' }}
@@ -158,6 +170,7 @@ watch(
diff --git a/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatTimelineItem.vue b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatTimelineItem.vue
index 54afbc46..2a3b6194 100644
--- a/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatTimelineItem.vue
+++ b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/ChatTimelineItem.vue
@@ -11,6 +11,7 @@ import type {
import { computed, ref } from 'vue';
import ChatThinkingBlock from '../chat-thinking/ChatThinkingBlock.vue';
+import ChatAssistantAvatar from './ChatAssistantAvatar.vue';
import ChatDocumentAttachments from './ChatDocumentAttachments.vue';
import ChatErrorNotice from './ChatErrorNotice.vue';
import ChatImageAttachments from './ChatImageAttachments.vue';
@@ -23,6 +24,7 @@ import ChatToolCard from './ChatToolCard.vue';
const props = defineProps<{
approvalLoading?: boolean;
assistantActionsVisible?: boolean;
+ assistantAvatar?: string;
copyable?: boolean;
copyAction?: (item: ChatTimelineMessageItem) => boolean | Promise;
documentLoader?: ChatDocumentLoader;
@@ -56,6 +58,18 @@ const alignmentClass = computed(() => {
return 'is-assistant';
});
+const showAssistantAvatar = computed(
+ () =>
+ messageItem.value?.role === 'assistant' && Boolean(props.assistantAvatar),
+);
+
+const showStatusAvatar = computed(
+ () =>
+ props.item.type === 'status' &&
+ props.item.presentation !== 'separator' &&
+ Boolean(props.assistantAvatar),
+);
+
const variantCurrent = computed(() => {
const item = messageItem.value;
return Number(item?.selectedVariantIndex || item?.variantIndex || 1);
@@ -158,68 +172,82 @@ function handleCopyAction() {
-
-
+
+
+
-
-
+
+
+
+
+
+
+
-
-
-
+
-
-
+
{
+ return {
+ id: 'assistant-message',
+ parts: [{ content: '已完成', id: 'text-1', type: 'text' }],
+ role: 'assistant',
+ type: 'message',
+ };
+}
+
+describe('chat timeline assistant avatar', () => {
+ it('renders the configured assistant identity in the empty state', () => {
+ const wrapper = mount(ChatTimeline, {
+ props: {
+ assistantAvatar: '/assistant.svg',
+ emptyTitle: '数据查询',
+ emptyText: '输入问题开始运行',
+ items: [],
+ },
+ });
+
+ expect(wrapper.find('.chat-timeline__empty-title').text()).toBe('数据查询');
+ expect(
+ wrapper.find('.chat-timeline__empty-icon img').attributes('src'),
+ ).toBe('/assistant.svg');
+ });
+
+ it('renders the assistant avatar only next to assistant messages', () => {
+ const wrapper = mount(ChatTimeline, {
+ props: {
+ assistantAvatar: '/assistant.svg',
+ items: [
+ {
+ id: 'user-message',
+ parts: [{ content: '你好', id: 'text-2', type: 'text' }],
+ role: 'user',
+ type: 'message',
+ },
+ assistantMessage(),
+ ],
+ },
+ });
+
+ expect(
+ wrapper.findAll('.chat-timeline-item__assistant-avatar'),
+ ).toHaveLength(1);
+ expect(
+ wrapper
+ .find('.chat-timeline-item__assistant-avatar img')
+ .attributes('src'),
+ ).toBe('/assistant.svg');
+ });
+
+ it('renders the assistant avatar beside inline running statuses', () => {
+ const wrapper = mount(ChatTimeline, {
+ props: {
+ assistantAvatar: '/assistant.svg',
+ items: [
+ {
+ id: 'running-status',
+ label: '正在运行 · 大模型',
+ status: 'running',
+ statusKey: 'workflow-running',
+ type: 'status',
+ },
+ ],
+ },
+ });
+
+ expect(
+ wrapper.findAll(
+ '.chat-timeline-item__status-row .chat-timeline-item__assistant-avatar',
+ ),
+ ).toHaveLength(1);
+ expect(
+ wrapper.find('.chat-timeline-item__status-row img').attributes('src'),
+ ).toBe('/assistant.svg');
+ });
+
+ it('keeps separator statuses unchanged', () => {
+ const wrapper = mount(ChatTimeline, {
+ props: {
+ assistantAvatar: '/assistant.svg',
+ items: [
+ {
+ id: 'separator-status',
+ label: '今天',
+ presentation: 'separator',
+ status: 'done',
+ statusKey: 'date-separator',
+ type: 'status',
+ },
+ ],
+ },
+ });
+
+ expect(
+ wrapper.find('.chat-timeline-item__status-row').classes(),
+ ).not.toContain('has-assistant-avatar');
+ expect(
+ wrapper.findAll('.chat-timeline-item__assistant-avatar'),
+ ).toHaveLength(0);
+ });
+
+ it('falls back to the bundled avatar when a custom image cannot load', async () => {
+ const wrapper = mount(ChatTimeline, {
+ props: {
+ assistantAvatar: '/broken-avatar.svg',
+ items: [assistantMessage()],
+ },
+ });
+
+ await wrapper
+ .find('.chat-timeline-item__assistant-avatar img')
+ .trigger('error');
+
+ expect(
+ wrapper
+ .find('.chat-timeline-item__assistant-avatar img')
+ .attributes('src'),
+ ).toBe(defaultAssistantAvatar);
+ });
+});
diff --git a/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/assets/default-assistant-avatar.png b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/assets/default-assistant-avatar.png
new file mode 100644
index 00000000..b8196f59
Binary files /dev/null and b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/assets/default-assistant-avatar.png differ
diff --git a/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/assistantAvatar.ts b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/assistantAvatar.ts
new file mode 100644
index 00000000..82626487
--- /dev/null
+++ b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/assistantAvatar.ts
@@ -0,0 +1,2 @@
+/** EasyFlow 聊天助手的默认头像。 */
+export { default as defaultAssistantAvatar } from './assets/default-assistant-avatar.png';
diff --git a/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/index.ts b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/index.ts
index 1a996e34..d3ea7cc9 100644
--- a/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/index.ts
+++ b/easyflow-ui-admin/packages/effects/common-ui/src/components/chat-timeline/index.ts
@@ -1,4 +1,6 @@
+export { defaultAssistantAvatar } from './assistantAvatar';
export { ChatTimelineBuilder } from './builder';
+export { default as ChatAssistantAvatar } from './ChatAssistantAvatar.vue';
export { default as ChatDocumentAttachments } from './ChatDocumentAttachments.vue';
export { default as ChatErrorNotice } from './ChatErrorNotice.vue';
export { default as ChatImageAttachments } from './ChatImageAttachments.vue';