Chat Component - Custom Thinking Process
The GenuiChat component supports a custom thinking-process display component so users can see the AI's real-time response status.
Basic Usage
Use the thinkComponent prop to customize the thinking-process display component.
<template>
<GenuiChat :url="url" model="deepseek-v3.2" :thinkComponent="CustomThinkComponent" />
</template>
<script setup lang="ts">
import { GenuiChat } from '@opentiny/genui-sdk-vue';
import CustomThinkComponent from './components/custom-think-component.vue';
const url = 'https://your-chat-backend/api';
</script>
Received Props
The custom thinking component receives the following props:
message: IChatMessage
The full message object for this turn of the conversation:
interface IChatMessage {
role: 'user' | 'assistant';
content: string;
messages?: IMessageItem[];
}
interface IMessageItem {
type: string;
content: string;
[customKey: string]: any;
}
emitter: INotificationEventEmitter
Used to listen for notification events during streaming responses:
interface INotificationEventEmitter {
on(eventName: 'notification', callback: (payload: INotificationPayload) => void, once?: boolean): void;
off(eventName: 'notification', callback: (payload: INotificationPayload) => void): void;
once(eventName: 'notification', callback: (payload: INotificationPayload) => void): void;
}
type INotificationPayload =
| {
type: 'markdown' | 'schema-card' | 'done';
delta: IStreamDelta;
chatMessage: IChatMessage;
}
| {
type: 'tool';
delta: IStreamDelta;
chatMessage: IChatMessage;
toolCallData: IMessageItem & {
type: 'tool';
};
};
interface IStreamDelta {
content?: string;
tool_calls?: Array<{
id: string;
function: {
name: string;
arguments: string;
};
}>;
tool_calls_result?: Array<{
id: string;
function: {
arguments: any;
result: any;
};
}>;
}
Payload Details
1. type: 'markdown'
Triggered when the AI is generating Markdown content:
{
type: 'markdown';
delta: IStreamDelta; // incremental data, includes the content field
chatMessage: IChatMessage; // full message object
}
Use case: When the goal is to generate a card, Markdown can be shown as the thinking process.
2. type: 'schema-card'
Triggered when the AI is generating a Schema Card (UI component):
{
type: 'schema-card';
delta: IStreamDelta;
chatMessage: IChatMessage;
}
Use case: Show prompts such as "Generating card...".
3. type: 'tool'
Triggered when the AI is invoking a tool:
{
type: 'tool';
delta: IStreamDelta;
chatMessage: IChatMessage;
toolCallData: IToolMessageItem; // tool call data
}
toolCallData includes the following fields:
name: string- Tool namestatus: 'running' | 'success' | 'failed' | 'cancelled'- Tool call statuscontent: string- Tool call arguments and result (JSON string)
Use case: Display tool call status, e.g. "Calling getWeather...", "Called getWeather...".
4. type: 'done'
Triggered when the streaming response completes:
{
type: 'done';
delta: IStreamDelta;
chatMessage: IChatMessage;
}
Use case: Hide loading state and clean up temporary data.
Example Implementation
The following is a thinking component implementation based on production code:
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import type { INotificationPayload, IThinkComponentProps } from '@opentiny/genui-sdk-vue';
const props = defineProps<IThinkComponentProps>();
const loadingText = ref('Responding...');
const toolStatusTextMap = new Map<string, { text: string }>([
['running', { text: 'Calling' }],
['success', { text: 'Called' }],
['failed', { text: 'Call failed' }],
['cancelled', { text: 'Cancelled' }],
]);
const handleNotification = (payload: INotificationPayload) => {
// Clear loading text when the response completes
if (payload.type === 'done') {
loadingText.value = '';
return;
}
// Generating a card
if (payload.type === 'schema-card') {
loadingText.value = 'Generating card...';
return;
}
// If thinking result display is enabled, show a generic responding state
if (props.showThinkingResult) {
loadingText.value = 'Responding...';
return;
}
// Tool call status
if (payload.type === 'tool') {
const { toolCallData } = payload;
loadingText.value = `${toolStatusTextMap.get(toolCallData.status)?.text} ${toolCallData.name}...`;
return;
}
// Markdown content generation
// type === 'markdown'
const lastMessage = payload.chatMessage.messages[payload.chatMessage.messages.length - 1];
if (lastMessage) {
loadingText.value = `${lastMessage.content}...`;
}
};
onMounted(() => {
props.emitter.on('notification', handleNotification);
});
onBeforeUnmount(() => {
props.emitter.off('notification', handleNotification);
});
</script>
<template>
<div v-if="loadingText" class="loading-container" type="loading-text">{{ loadingText }}</div>
</template>
<style scoped lang="less">
.loading-container[type='loading-text'] {
margin: 10px 0;
background: linear-gradient(90deg, #666 0%, #666 45%, #999 50%, #999 55%, #666 60%, #666 100%);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: text-shimmer 6s linear infinite;
}
</style>
Code Explanation
Event Handling Logic
-
doneevent: When streaming completes, clearloadingTextand hide the component. -
schema-cardevent: When a UI component is being generated, show "Generating card...". -
showThinkingResultistrue: If thinking result display is enabled, always show "Responding..." instead of specific tool call or content generation status. -
toolevent: Display text based on tool call status (running, success, failed, cancelled), e.g. "Calling getWeather...". -
markdownevent: Show a preview of the last message content, e.g. "Hello...".