Skip to content

Commit 810c088

Browse files
author
Marvin Zhang
committed
feat: add ChatMessageAction component and enhance message handling
- Introduced ChatMessageAction component for improved rendering of action messages. - Updated ChatMessage to utilize filtered contents for better message display. - Enhanced ChatConsole to ensure proper handling of message content during streaming. - Refactored type definitions in llm.d.ts for better clarity and flexibility in message content management. - Adjusted minimum width in ChatSidebar for improved layout consistency.
1 parent 47d6812 commit 810c088

File tree

6 files changed

+308
-185
lines changed

6 files changed

+308
-185
lines changed

src/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import ChatConsole from './ui/chat/ChatConsole.vue';
2828
import ChatHistory from './ui/chat/ChatHistory.vue';
2929
import ChatInput from './ui/chat/ChatInput.vue';
3030
import ChatMessage from './ui/chat/ChatMessage.vue';
31+
import ChatMessageAction from './ui/chat/ChatMessageAction.vue';
3132
import ChatMessageList from './ui/chat/ChatMessageList.vue';
3233
import ChatSidebar from './ui/chat/ChatSidebar.vue';
3334
import CheckboxTree from './ui/checkbox/CheckboxTree.vue';
@@ -270,6 +271,7 @@ export {
270271
ChatHistory as ClChatHistory,
271272
ChatInput as ClChatInput,
272273
ChatMessage as ClChatMessage,
274+
ChatMessageAction as ClChatMessageAction,
273275
ChatMessageList as ClChatMessageList,
274276
ChatSidebar as ClChatSidebar,
275277
CheckboxTree as ClCheckboxTree,

src/components/ui/chat/ChatConsole.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ const sendStreamingRequest = async (
453453
if (chunk.type === 'text') {
454454
const newContent: ChatMessageContent = {
455455
...chunk,
456+
content: chunk.content || '',
456457
isStreaming: true,
457458
};
458459
currentMessage.contents.push(newContent);

src/components/ui/chat/ChatMessage.vue

Lines changed: 17 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,6 @@ const props = defineProps<{
1111
message: ChatMessage;
1212
}>();
1313
14-
// Define interfaces for content items
15-
interface BaseContentItem {
16-
type: string;
17-
content: string;
18-
isStreaming?: boolean;
19-
}
20-
21-
interface TextContentItem extends BaseContentItem {
22-
type: 'text';
23-
}
24-
25-
interface ActionContentItem extends BaseContentItem {
26-
type: 'action';
27-
action: string;
28-
action_status: string;
29-
action_id?: string;
30-
}
31-
32-
type ContentItem = TextContentItem | ActionContentItem;
33-
3414
const md = markdownit({
3515
html: true,
3616
linkify: true,
@@ -81,79 +61,9 @@ watch(
8161
{ immediate: true }
8262
);
8363
84-
// Function to get action status icon
85-
const getActionStatusIcon = (status: string) => {
86-
switch (status) {
87-
case 'pending':
88-
return ['fas', 'circle-notch'];
89-
case 'success':
90-
return ['fas', 'check-circle'];
91-
case 'failed':
92-
return ['fas', 'times-circle'];
93-
default:
94-
return ['fas', 'circle-notch'];
95-
}
96-
};
97-
98-
// Function to get action status color
99-
const getActionStatusColor = (status: string) => {
100-
switch (status) {
101-
case 'pending':
102-
return 'var(--el-color-warning)';
103-
case 'success':
104-
return 'var(--el-color-success)';
105-
case 'failed':
106-
return 'var(--el-color-danger)';
107-
default:
108-
return 'var(--el-color-info)';
109-
}
110-
};
111-
112-
// Function to format action name for display
113-
const formatActionName = (action: string) => {
114-
return action
115-
.replace(/[_:]/g, ' ')
116-
.replace(/\b\w/g, char => char.toUpperCase());
117-
};
118-
119-
// Compute content items from message
120-
const contentItems = computed((): ContentItem[] => {
121-
const items: ContentItem[] = [];
122-
123-
// First check for structured content items
124-
if (props.message.contents && props.message.contents.length > 0) {
125-
// Use the pre-structured content items from the message
126-
props.message.contents.forEach(content => {
127-
if (content.type === 'action') {
128-
items.push({
129-
type: 'action',
130-
content: content.content || '',
131-
action: content.action || '',
132-
action_status: content.action_status || 'pending',
133-
action_id: content.key,
134-
isStreaming: false,
135-
} as ActionContentItem);
136-
} else {
137-
items.push({
138-
type: 'text',
139-
content: content.content || '',
140-
isStreaming: false,
141-
} as TextContentItem);
142-
}
143-
});
144-
return items;
145-
}
146-
147-
// Add the main text content if it exists
148-
if (props.message.content) {
149-
items.push({
150-
type: 'text',
151-
content: props.message.content,
152-
isStreaming: props.message.isStreaming,
153-
} as TextContentItem);
154-
}
155-
156-
return items;
64+
const filteredContents = computed<ChatMessageContent[]>(() => {
65+
const { message } = props;
66+
return message.contents?.filter(content => !content.hidden) || [];
15767
});
15868
15969
defineOptions({ name: 'ClChatMessage' });
@@ -165,42 +75,17 @@ defineOptions({ name: 'ClChatMessage' });
16575
<template v-if="message.content">
16676
<div v-html="renderMarkdown(message.content)"></div>
16777
</template>
78+
16879
<!-- Iterate through content items in order -->
16980
<div v-else class="content-items">
170-
<template v-for="(content, index) in message.contents" :key="index">
81+
<template v-for="(content, index) in filteredContents" :key="index">
17182
<!-- Action content -->
172-
<div
83+
<cl-chat-message-action
17384
v-if="content.type === 'action'"
174-
class="action-item"
175-
:class="`action-status-${content.action_status}`"
176-
>
177-
<div class="action-header">
178-
<cl-icon
179-
:icon="getActionStatusIcon(content.action_status!)"
180-
:style="{
181-
color: getActionStatusColor(content.action_status!),
182-
}"
183-
class="action-icon"
184-
:class="{
185-
spinning: content.action_status === 'pending',
186-
}"
187-
/>
188-
<span class="action-name">{{
189-
formatActionName(content.action!)
190-
}}</span>
191-
<span
192-
class="action-status"
193-
:style="{
194-
color: getActionStatusColor(content.action_status!),
195-
}"
196-
>
197-
{{ content.action_status }}
198-
</span>
199-
</div>
200-
<div v-if="content.content" class="action-content">
201-
{{ content.content }}
202-
</div>
203-
</div>
85+
:action="content.action!"
86+
:action-status="content.action_status!"
87+
:content="content.content"
88+
/>
20489

20590
<!-- Text content -->
20691
<div v-else-if="content.type === 'text'" class="text-content">
@@ -467,6 +352,7 @@ defineOptions({ name: 'ClChatMessage' });
467352
.typing-text {
468353
display: inline-block;
469354
color: var(--el-color-primary);
355+
animation: fade 3s infinite;
470356
}
471357
472358
@keyframes blink {
@@ -479,64 +365,13 @@ defineOptions({ name: 'ClChatMessage' });
479365
}
480366
}
481367
482-
/* Action styles */
483-
.action-item {
484-
padding: 8px 12px;
485-
border-radius: 6px;
486-
background-color: var(--el-fill-color-light);
487-
border-left: 3px solid var(--el-color-info);
488-
}
489-
490-
.action-item.action-status-pending {
491-
border-left-color: var(--el-color-warning);
492-
}
493-
494-
.action-item.action-status-success {
495-
border-left-color: var(--el-color-success);
496-
}
497-
498-
.action-item.action-status-failed {
499-
border-left-color: var(--el-color-danger);
500-
}
501-
502-
.action-header {
503-
display: flex;
504-
align-items: center;
505-
gap: 8px;
506-
margin-bottom: 4px;
507-
}
508-
509-
.action-icon {
510-
font-size: 14px;
511-
}
512-
513-
.spinning {
514-
animation: spin 1s linear infinite;
515-
}
516-
517-
.action-name {
518-
flex: 1;
519-
font-weight: 500;
520-
color: var(--el-text-color-primary);
521-
}
522-
523-
.action-status {
524-
font-size: 12px;
525-
text-transform: capitalize;
526-
}
527-
528-
.action-content {
529-
font-size: 13px;
530-
color: var(--el-text-color-secondary);
531-
margin-left: 24px;
532-
}
533-
534-
@keyframes spin {
535-
0% {
536-
transform: rotate(0deg);
537-
}
368+
@keyframes fade {
369+
0%,
538370
100% {
539-
transform: rotate(360deg);
371+
opacity: 1;
372+
}
373+
50% {
374+
opacity: 0.7;
540375
}
541376
}
542377
</style>

0 commit comments

Comments
 (0)