@linker-design-plus/chat-next
v1.0.6
Published
AI Chat component for linker design
Downloads
396
Readme
sud# ChatNext 组件库
提供了一套用于构建聊天界面的 Vue 3 组件集合。
组件列表
- ChatSender 发送框
- ChatAttachments 附件列表
- ChatActionbar 消息操作栏
- ChatThinking 思考过程
- ChatLoading 加载状态
- ChatItem 聊天消息项
- ChatMarkdown Markdown 渲染
ChatSender 发送框
用于输入和发送聊天消息的组件。支持普通文本输入和富文本模板输入。
属性 (Props)
| 属性名 | 类型 | 默认值 | 说明 |
| ------------------- | ----------------------------------- | -------------------------------- | ------------------------------------------------------- |
| value / v-model | string | - | 输入框的值。支持语法糖 v-model 或 v-model:value |
| defaultValue | string | '' | 输入框的值。非受控属性 |
| loading | boolean | true | 发送按钮是否处于加载状态。为 false 时显示停止分析按钮 |
| allowEmpty | boolean | false | 是否允许发送空内容 |
| textareaProps | TextareaProps | { placeholder: '输入消息...' } | 输入框的属性,如 placeholder |
| templates | ITemplateNode[] | [] | 模板节点列表,提供后将开启富文本模板模式 |
| validate | () => boolean \| Promise<boolean> | - | 发送前的校验函数 |
事件 (Emits)
| 事件名 | 参数 | 说明 |
| ------------------- | ---------------- | ---------------------------- |
| send | (text: string) | 发送消息时触发 |
| stop | - | 点击停止按钮时触发 |
| update:value | (val: string) | 输入内容变化时触发 |
| update:modelValue | (val: string) | 输入内容变化时触发 (v-model) |
插槽 (Slots)
| 插槽名 | 说明 |
| --------------- | -------------------- |
| header | 输入框外标题区域扩展 |
| inner-header | 输入框内标题区域扩展 |
| input-prefix | 输入框前方区域 |
| footer-prefix | 输入框左下角区域扩展 |
| footer-suffix | 输入框右下角区域扩展 |
接口类型
export type TextareaProps = {
placeholder?: string;
};
export interface ITemplateNode {
type: 'text' | 'select' | 'input';
id: string; // unique id for template nodes
content?: string; // only for text
options?: { label: string; value: string }[] | string[]; // only for select
multiple?: boolean; // only for select
value?: string | string[]; // for select and input (string[] for multiple select)
placeholder?: string; // for input and select
}ChatAttachments 附件列表
用于展示上传的附件或文件列表。
属性 (Props)
| 属性名 | 类型 | 默认值 | 说明 |
| ------------ | ---------------------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
| items | IChatAttachment[] | [] | 附件列表数据 |
| overflow | 'wrap' \| 'scrollX' \| 'scrollY' | 'wrap' | 文件列表超出时样式 |
| removable | boolean | true | 是否显示删除按钮 |
| fieldNames | object | - | 自定义数据字段映射,包含 name, suffix, size, status, statusText, icon, url, type, uid |
事件 (Emits)
| 事件名 | 参数 | 说明 |
| -------- | ---------------------------------------- | ------------------ |
| remove | (item: IChatAttachment, index: number) | 点击删除按钮时触发 |
接口类型
export interface IChatAttachment {
uid?: string | number;
icon?: string;
suffix?: string;
name: string;
url?: string;
type?: string;
size?: number;
status?: 'success' | 'error' | 'pending';
statusText?: string;
[key: string]: any;
}ChatActionbar 消息操作栏
展示在聊天消息底部的操作按钮列表(如复制、重试、点赞、点踩)。
属性 (Props)
| 属性名 | 类型 | 默认值 | 说明 |
| --------- | ---------------- | ----------------------------------- | ---------------------- |
| actions | IChatActions[] | ['copy', 'replay', 'good', 'bad'] | 需要显示的操作项列表 |
| content | string | '' | 用于复制操作的文本内容 |
good和bad点赞点踩暂未实现
事件 (Emits)
| 事件名 | 参数 | 说明 |
| -------- | ------------------------ | ------------------ |
| action | (action: IChatActions) | 点击操作按钮时触发 |
接口类型
import type { Component } from 'vue';
export type IChatActionBuiltin = 'copy' | 'replay' | 'good' | 'bad';
export interface IChatActionConfig {
action: string;
text?: string;
icon?: string | Component;
}
export type IChatActions = IChatActionBuiltin | IChatActionConfig;ChatThinking 思考过程
展示 AI 思考过程的组件,支持展开/收起和不同的动画状态。
属性 (Props)
| 属性名 | 类型 | 默认值 | 说明 |
| ----------- | ---------------------------------------------- | --------------------------------- | ---------------------------------- |
| content | IChatThinkingContent | { text: '', title: '思考过程' } | 思考内容及标题 |
| layout | 'block' \| 'border' | 'block' | 布局方式,块级或带虚线边框 |
| status | 'pending' \| 'complete' \| 'stop' \| 'error' | 'pending' | 思考状态 |
| maxHeight | number | - | 展开内容的最大高度 |
| animation | 'circle' \| 'moving' \| 'gradient' | 'circle' | 思考中的动画类型 |
| collapsed | boolean | false | 是否折叠(支持 v-model:collapsed) |
事件 (Emits)
| 事件名 | 参数 | 说明 |
| ------------------ | ---------------- | ------------------ |
| update:collapsed | (val: boolean) | 折叠状态变化时触发 |
接口类型
export interface IChatThinkingContent {
title?: string;
text?: string;
}
export type IChatThinkingLayout = 'block' | 'border';
export type IChatThinkingStatus = 'pending' | 'complete' | 'stop' | 'error';
export type IChatThinkingAnimation = 'circle' | 'moving' | 'gradient';ChatLoading 加载状态
消息加载中的动画组件。
属性 (Props)
| 属性名 | 类型 | 默认值 | 说明 |
| ----------- | ----------------- | ------- | ------------------------------------ |
| animation | 'spin' \| 'dot' | 'dot' | 加载动画类型,支持旋转圆环或跳动小点 |
接口类型
export type IChatLoadingAnimation = 'spin' | 'dot';ChatItem 聊天消息项
完整的单条聊天消息组件,整合了头像、内容、加载状态、操作栏等。
属性 (Props)
| 属性名 | 类型 | 默认值 | 说明 |
| ---------- | ----------- | ------- | ------------ |
| chatItem | IChatItem | - | 消息项数据 |
| avatar | boolean | false | 是否显示头像 |
插槽 (Slots)
| 插槽名 | 说明 |
| ------------- | ------------------------------------------- |
| user-files | 用户角色时的文件展示区域 |
| loading | AI 角色 status='pending' 时的加载区域 |
| thinking | AI 角色 status='thinking' 时的思考区域 |
| information | AI 角色时的额外信息展示区域(位于内容上方) |
| actions | AI 角色时的操作栏区域 |
| card | AI 角色时的卡片展示区域(位于内容下方) |
| suggest | AI 角色时的建议区域 |
| user-extra | 用户角色底部的扩展区域 |
接口类型
export interface IChatItem {
id: string | number;
role: 'user' | 'assistant' | 'system';
content: string;
avatar?: string;
status?: 'pending' | 'thinking' | 'success' | 'error';
[key: string]: any;
}ChatMarkdown Markdown 渲染
用于安全地渲染包含代码块和高亮的 Markdown 文本。
属性 (Props)
| 属性名 | 类型 | 默认值 | 说明 |
| --------- | -------- | ------ | ------------------------ |
| content | string | '' | 要渲染的 Markdown 字符串 |
注意:此组件内置了 Highlight.js 语法高亮和 GitHub Markdown CSS 样式,并自带代码块一键复制功能。
demo 示例
<template>
<div class="chat-demo-container">
<h2>AI Chat Demo</h2>
<div class="chat-box">
<!-- 聊天消息列表区域 -->
<div class="chat-message-list">
<HChatItem
v-for="(msg, index) in messages"
:key="index"
:avatar="msg.role === 'assistant'"
:chat-item="msg"
>
<template #user-files>
<template v-if="msg.inputFiles && msg.inputFiles.length">
<div class="user-files-list">
<img
v-for="file in msg.inputFiles"
:key="file.url"
:src="file.url"
alt="file-image"
/>
</div>
</template>
</template>
<template #user-extra>
<template v-if="msg.userExtra">
<img :src="QuoteIconPNG" alt="icon-quote" @click="handleQuote(msg)" />
</template>
</template>
<template #loading>
<HChatLoading />
</template>
<template #thinking>
<HChatThinking
:animation="'circle'"
:layout="'border'"
status="pending"
:content="{ title: '思考中', text: msg.thinking }"
/>
</template>
<template #information>
<div class="information">
<div>随便你在这里</div>
<div>做什么</div>
</div>
</template>
<template #actions>
<HChatActionbar
:content="msg.content"
:actions="[
'copy',
'replay',
{
action: 'document',
text: '生成文档并编辑',
icon: IconBook,
},
]"
@action="handleAction"
/>
</template>
<template #card>
<div class="card-item">
<div class="card-item-title">
<span>生成文档并编辑</span>
</div>
</div>
</template>
<template #suggest>
<div class="suggest-list">
<div v-for="suggest in msg.suggest" :key="suggest" class="suggest-item">{{
suggest
}}</div>
</div>
</template>
</HChatItem>
</div>
<!-- 发送消息区域 -->
<div class="chat-sender-wrapper">
<HChatSender
v-model="inputValue"
:templates="templateNodes"
:textarea-props="{
placeholder: '你好...',
}"
@send="handleSendMessage"
>
<template #header>
<div class="header-out">
<span v-for="skill in skillList" :key="skill" class="skill-item">{{ skill }}</span>
</div>
</template>
<template #inner-header>
<HChatAttachments
overflow="scrollX"
:field-names="{
suffix: 'bak',
}"
:items="inputFiles"
@remove="handleRemoveFile"
/>
</template>
<template #input-prefix>
<span>我是技能插槽</span>
</template>
<template #footer-prefix>
<div class="footer-prefix-item">深度思考</div>
</template>
<template #footer-suffix>
<div>123</div>
</template>
</HChatSender>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { IconBook } from '@arco-design/web-vue/es/icon';
import {
HChatItem,
HChatSender,
HChatActionbar,
HChatLoading,
HChatThinking,
HChatAttachments,
} from '@linker-design-plus/chat-next';
import { useEventSource } from '@linker-design-plus/chat-next/eventSource';
import type {
IChatItem,
IChatActions,
IChatAttachment,
ITemplateNode,
} from '@linker-design-plus/chat-next';
import '@linker-design-plus/chat-next/dist/style.css';
import QuoteIconPNG from './assets/images/quote.png';
const { startEventSource, stopEventSource } = useEventSource();
const inputValue = ref('');
const templateNodes = ref<ITemplateNode[]>([
{ type: 'text', id: 't1', content: '帮我用 ' },
{ type: 'select', id: 's1', options: ['Vue', 'React', 'Angular'], value: 'Vue' },
{ type: 'text', id: 't2', content: ' 以及 ' },
{
type: 'select',
id: 's2',
multiple: true,
placeholder: '选择打包工具',
options: ['TypeScript', 'Vite', 'Webpack', 'Less'],
value: [],
},
{ type: 'text', id: 't3', content: ' 开发一个 ' },
{ type: 'input', id: 'i1', placeholder: '组件名称', value: '' },
{ type: 'text', id: 't4', content: ' 组件。' },
]);
const skillList = ref<string[]>([
'vue',
'react',
'angular',
'nodejs',
'mongodb',
'redis',
'mysql',
'postgresql',
'docker',
'kubernetes',
]);
const inputFiles = ref<IChatAttachment[]>([
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'testtesttesttesttesttesttesttesttesttesttesttesttesttest',
bak: 'png',
size: 100,
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
status: 'pending',
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
status: 'pending',
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
status: 'pending',
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
status: 'pending',
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
suffix: 'png',
size: 100,
status: 'pending',
icon: 'https://tdesign.gtimg.com/site/chat-avatar.png',
},
]);
const fullThinkingText =
'好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?好的,请问你需要什么类型的组件呢?';
// 模拟的聊天记录
const messages = ref<IChatItem[]>([
{
role: 'assistant',
content: '好的,请问你需要什么类型的组件呢?',
thinking: '',
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
status: 'thinking',
},
{
role: 'assistant',
content: `
# This is TDesign
## This is TDesign
### This is TDesign
#### This is TDesign
The point of reference-style links is not that they’re easier to write. The point is that with reference-style links, your document source is vastly more readable. Compare the above examples: using reference-style links, the paragraph itself is only 81 characters long; with inline-style links, it’s 176 characters; and as raw \`HTML\`, it’s 234 characters. In the raw \`HTML\`, there’s more markup than there is text.
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet.
an example | *an example* | **an example**
1. Bird
1. McHale
1. Parish
1. Bird
1. McHale
1. Parish
- Red
- Green
- Blue
- Red
- Green
- Blue
This is [an example](http://example.com/ "Title") inline link.
<http://example.com/>
# TDesign(腾讯设计体系)核心特性与技术架构
以下是关于 TDesign(腾讯设计体系)的核心特性与技术架构的表格化总结:
| 表头 | 表头 |
| ---- | ---- |
| 单元格 | 单元格 |
| 单元格 | 单元格 |
| 分类 | 核心内容 | 关键技术/特性 |
|------|----------|---------------|
| **设计理念** | • 设计价值观:用户为本、科技向善、突破创新... | • 设计原子单元 |
| **核心组件库** | • 基础组件:Button/Input/Table/Modal... | • 组件覆盖率 |
| **技术特性** | • 多框架支持:Vue/React/Angular... | • 按需加载 |
\`\`\`bash
$ npm i tdesign-vue-next
\`\`\`
---
\`\`\`javascript
import { createApp } from 'vue';
import App from './app.vue';
const app = createApp(App);
app.use(TDesignChat);
\`\`\`
`,
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
status: 'success',
},
{
role: 'user',
content: '帮我写一段 Vue3 组件代码。',
avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
userExtra: true,
inputFiles: [
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
size: 100,
type: 'image/png',
},
{
url: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'test.png',
size: 100,
type: 'image/png',
},
],
status: 'success',
},
{
role: 'assistant',
content: '好的,请问你需要什么类型的组件呢?',
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
status: 'success',
},
{
role: 'user',
content: '帮我写一段 Vue3 组件代码。',
avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
userExtra: false,
status: 'success',
},
{
role: 'assistant',
content: '好的,请问你需要什么类型的组件呢?',
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
status: 'success',
suggest: ['vue3', 'react', 'angular'],
},
]);
const handleQuote = (msg: IChatItem) => {
console.log(msg);
};
const handleAction = (action: IChatActions) => {
console.log(action);
};
// 处理发送消息
const handleSendMessage = (text: string) => {
if (!text.trim()) return;
// 添加用户消息
messages.value.push({ role: 'user', content: text, status: 'success' });
inputValue.value = ''; // 保证触发发送后立即清空双向绑定的输入框
// 模拟 AI 回复
setTimeout(() => {
messages.value.push({
role: 'assistant',
content: `收到你的消息:${text}。这是一个模拟的回复。`,
status: 'success',
});
}, 1000);
};
const handleRemoveFile = (file: IChatAttachment, index: number) => {
console.log(file, index);
inputFiles.value = inputFiles.value.filter((item, i) => i !== index);
};
onMounted(() => {
let i = 0;
const timer = setInterval(() => {
if (i < fullThinkingText.length) {
messages.value[0].thinking += fullThinkingText[i];
i++;
} else {
clearInterval(timer);
}
}, 30);
});
</script>
<style scoped lang="less">
@import '../src/style/variables.less';
.chat-demo-container {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: @chat-spacing-xl;
font-family: sans-serif;
}
.chat-box {
border: 1px solid @chat-border-color;
border-radius: @chat-border-radius-md;
display: flex;
flex-direction: column;
height: 600px;
overflow: hidden;
}
.chat-message-list {
flex: 1;
padding: @chat-spacing-xl;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: @chat-spacing-lg;
}
.chat-sender-wrapper {
padding: @chat-spacing-lg;
background-color: @chat-panel-bg-color;
border-top: 1px solid @chat-border-color;
}
.user-files-list {
display: flex;
gap: 10px;
}
.header-out {
display: flex;
gap: @chat-spacing-sm;
}
.skill-item {
padding: @chat-spacing-xs @chat-spacing-sm;
border-radius: @chat-border-radius-base;
background-color: @chat-bg-color;
color: @chat-text-color-1;
font-size: @chat-font-size-sm;
cursor: pointer;
&:hover {
background-color: @chat-hover-bg-color;
}
}
.information {
display: flex;
flex-direction: column;
gap: @chat-spacing-sm;
padding: @chat-spacing-md;
border-radius: @chat-border-radius-md;
background-color: @chat-bg-color;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
margin-bottom: @chat-spacing-md;
}
.card-item {
padding: @chat-spacing-md;
border-radius: @chat-border-radius-md;
background-color: @chat-bg-color;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
margin-bottom: @chat-spacing-md;
}
.suggest-list {
display: flex;
flex-direction: column;
gap: @chat-spacing-sm;
.suggest-item {
width: fit-content;
padding: @chat-spacing-xs @chat-spacing-sm;
border-radius: @chat-border-radius-base;
background-color: @chat-bg-color;
color: @chat-text-color-1;
font-size: @chat-font-size-sm;
cursor: pointer;
&:hover {
background-color: @chat-hover-bg-color;
}
}
}
.footer-prefix-item {
border-radius: @chat-border-radius-lg;
background-color: @chat-bg-color;
padding: @chat-spacing-xs @chat-spacing-md;
font-size: @chat-font-size-sm;
color: @chat-text-color-2;
cursor: pointer;
&:hover {
background-color: @chat-hover-bg-color;
}
}
</style>Aaas 大模型返回类型字典
| 事件名称 | 解释 | 图例 | 备注 |
| ------------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| answer | 大模型流式输出 | | 通用 |
| information | 标记信息,用作提示 | | 通用 |
| search_result | 搜索到的文件和片段 | | 文殊 |
| search_filter | 标记信息,用作展示过滤的文案信息 | | 文殊 |
| markdown | 输出markdown的消息 | | 通用 |
| ~~file_import~~ | ~~会话文件导入了文件,通知前端更新数据~~ | | ~~通用,已废弃~~ |
| thinking | 思考过程 | | 通用 |
| suggest | 推荐问题 | | 通用 |
| faqRet | 命中 faq | | 文殊 |
| docRet | 命中文档信息 | | 文殊 |
| k0_alarm_search_result | 命中告警查询 | | 哨兵 |
| ~~video_url_dict~~ | 视频高光信息 | | ~~指令成片器、精华切片机~~ |
| fileOutput | 溯源文档信息 用于定位引用的文档片段并高亮 | | 文殊 |
| process_input | 过程输出 | | 广西机房 demo |
| query | 工作流输出结果的操作按钮的文字 | | 哨兵 |
| agentPro | 流式大模型输出,不同于answer,这个 type 用作多个节点一起输出内容 | | 通用 |
| progressRate | 流式大模型输出,用作自定义渲染处理进度 | | 通用 |
