Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
441c1e4
agui-activity (#362)
carolin913 Feb 3, 2026
5ddd85d
feat(chatengine): add isvalidchunk api for filter validated sse chunk
carolin913 Feb 10, 2026
6fec53f
fix(chat-item): fix right align content (#364)
LzhengH Feb 10, 2026
ce01c9c
Feat/chat msg attachment 支持user附件消息传入参数或slot自定义 (#365)
LzhengH Feb 10, 2026
3ed640d
fix(chat-item): fix user all variant right align style (#367)
LzhengH Feb 10, 2026
d4a0b42
Feat/code block custom 改用cherryMD的代码块组件,支持更多代码块风格 (#366)
LzhengH Feb 12, 2026
8394afa
feat(chatmarkdown): fix cherry iconfont
carolin913 Feb 12, 2026
bbba687
fix(markdown): fix button styles (#370)
LzhengH Feb 12, 2026
80691b5
chore: fix compatibility (#372)
uyarn Mar 2, 2026
922fd19
chore: release 1.3.1-alpha.9
uyarn Mar 2, 2026
86717fb
fix: fix filecard style (#373)
LeonardoSya Mar 3, 2026
14fe3d4
fix(cherry): 修复cherry阻塞md元素事件挂载问题
LzhengH Mar 6, 2026
6311f6d
chore: release 1.3.1-alpha.10
uyarn Mar 6, 2026
bdbdf07
feat(chat-sender): 新增 `sendBtnDisabled` 支持禁止发送按钮 (#375)
RSS1102 Mar 17, 2026
77391da
reactor(chat-sender): 对于 chat-sender api 进行重构 (#376)
RSS1102 Mar 20, 2026
98b4d9d
chore: release 1.3.1-alpha.11
uyarn Mar 20, 2026
2268bac
feat(chatList): support data paging
LzhengH Mar 25, 2026
0e85550
chore(chatList): patch propTypes
LzhengH Mar 25, 2026
6758a42
chore(chatList): patch types
LzhengH Mar 25, 2026
3dc637b
chore(chat): append dataPaging Chat example
LzhengH Mar 26, 2026
6fc0271
feat(chatList): improve dataPaging example
LzhengH Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
347 changes: 62 additions & 285 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "tdesign-web-components",
"purename": "tdesign",
"version": "1.2.10",
"version": "1.3.1-alpha.11",
"type": "module",
"main": "lib/index.js",
"module": "lib/index.js",
Expand All @@ -13,7 +13,8 @@
"site": "cd site && vite build",
"site:preview": "cd site && vite build --mode preview && cd ../_site && cp index.html 404.html",
"prebuild": "rimraf es/* lib/* dist/* esm/* cjs/*",
"build": "cross-env NODE_ENV=production rollup -c script/rollup.config.js && npm run build:tsc",
"build": "cross-env NODE_ENV=production rollup -c script/rollup.config.js && npm run build:tsc && npm run copy:fonts",
"copy:fonts": "mkdir -p lib/assets && cp node_modules/cherry-markdown/dist/fonts/ch-icon.* lib/assets/",
"build:analyze": "cross-env NODE_ENV=production ANALYZE=true rollup -c script/rollup.config.js && npm run build:tsc",
"build:analyze:all": "cross-env NODE_ENV=production ANALYZE=true ANALYZE_MODE=all rollup -c script/rollup.config.js && npm run build:tsc",
"build:analyze:umd": "cross-env NODE_ENV=production ANALYZE=true ANALYZE_MODE=umd rollup -c script/rollup.config.js && npm run build:tsc",
Expand Down Expand Up @@ -73,7 +74,7 @@
"dependencies": {
"@babel/runtime": "^7.24.7",
"@popperjs/core": "^2.11.8",
"cherry-markdown": "0.11.0-alpha-2",
"cherry-markdown": "^0.11.0-alpha-5",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"copy-to-clipboard": "^3.3.3",
Expand Down
32 changes: 29 additions & 3 deletions script/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,42 @@ const getAnalyzePlugins = (buildType = 'umd') => {
};

const input = 'src/index-lib.ts';

// todo: 这里先按白名单:只打包 chat 相关组件
const inputList = [
'src/**/*.ts',
'src/**/*.jsx',
'src/**/*.tsx',
// chat 开头的组件
'src/chat-*/**/*.ts',
'src/chat-*/**/*.tsx',
// 额外需要的组件
'src/chatbot/**/*.ts',
'src/chatbot/**/*.tsx',
'src/attachments/**/*.ts',
'src/attachments/**/*.tsx',
'src/filecard/**/*.ts',
'src/filecard/**/*.tsx',
// 公共模块
'src/_util/**/*.ts',
'src/_util/**/*.tsx',
'src/*.ts',
// 排除项
'!src/**/_example',
'!src/**/*.d.ts',
'!src/**/__tests__',
'!src/**/_usage',
];

// 全部都打包
// const inputList = [
// 'src/**/*.ts',
// 'src/**/*.jsx',
// 'src/**/*.tsx',
// '!src/**/_example',
// '!src/**/*.d.ts',
// '!src/**/__tests__',
// '!src/**/_usage',
// ];


const getPlugins = ({ env, isProd = false, ignoreLess = false } = {}) => {
const plugins = [
nodeResolve(),
Expand Down
4 changes: 2 additions & 2 deletions src/_util/classname.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
// import { twMerge } from 'tailwind-merge';

export default function classname(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
return clsx(inputs);
}

export function getClassPrefix() {
Expand Down
18 changes: 18 additions & 0 deletions src/_util/copyToClipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function copy(text: string) {
let success: boolean;
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
success = true;
} catch (e) {
console.error('fallback 复制失败', e);
success = false;
}
document.body.removeChild(textarea);
return success;
}
29 changes: 24 additions & 5 deletions src/attachments/attachments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,44 @@ export default class Attachments extends Component<TdAttachmentsProps> {

containerRef = createRef<HTMLElement>();

/** ResizeObserver 实例 */
private resizeObserver: ResizeObserver | null = null;

/** 滚动事件处理器(保存引用以便清理) */
private handleScroll = () => {
this.updateButtonVisibility();
};

installed() {
// 添加延迟确保DOM完全渲染
requestAnimationFrame(() => {
requestAnimationFrame(() => {
this.updateButtonVisibility();
// 添加尺寸变化监听
const resizeObserver = new ResizeObserver(() => {
this.resizeObserver = new ResizeObserver(() => {
this.updateButtonVisibility();
});
if (this.containerRef.current) {
resizeObserver.observe(this.containerRef.current);
this.resizeObserver.observe(this.containerRef.current);
}
});
});

// 监听手动滚动事件
this.containerRef.current?.addEventListener('scroll', () => {
this.updateButtonVisibility();
});
this.containerRef.current?.addEventListener('scroll', this.handleScroll);
}

uninstalled() {
// 清理 ResizeObserver
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}

// 清理滚动事件监听器
if (this.containerRef.current) {
this.containerRef.current.removeEventListener('scroll', this.handleScroll);
}
}

ready(): void {
Expand Down
23 changes: 15 additions & 8 deletions src/chat-action/action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../../src/tooltip';
import { Component, signal, tag } from 'omi';

import { getClassPrefix } from '../_util/classname';
import { copy as fallbackCopy } from '../_util/copyToClipboard';
import { setExportparts } from '../_util/dom';
import { type ChatComment } from '../chat-engine';
import { MessagePlugin } from '../message';
Expand All @@ -30,14 +31,20 @@ export const renderActions = (
const clickCopyHandler = () => {
const text = copyText.toString();
if (!text) return;
navigator.clipboard
.writeText(copyText.toString())
.then(() => {
MessagePlugin.success('复制成功');
})
.catch(() => {
MessagePlugin.success('复制失败,请手动复制');
});
if (navigator.clipboard) {
navigator.clipboard
.writeText(copyText.toString())
.then(() => {
MessagePlugin.success('复制成功');
})
.catch(() => {
MessagePlugin.success('复制失败,请手动复制');
});
} else {
const success = fallbackCopy(text);
if (success) MessagePlugin.success('复制成功');
else MessagePlugin.error('复制失败,请手动复制');
}
};

const handleClickAction = (action: TdChatActionsName, data: any) => {
Expand Down
206 changes: 206 additions & 0 deletions src/chat-engine/adapters/agui/ActivityManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { ActivityData } from '../../type';
import { applyJsonPatch } from '../../utils';

/**
* Activity 状态管理器
*
* 职责:
* - 处理 AG-UI Activity 事件的 Snapshot/Delta 增量合并
* - 提供当前 Activity 数据的快捷访问(用于 event-mapper 生成消息内容)
*
* 注意:
* - Activity 本身会渲染在消息流中,不需要独立订阅机制
* - 如需监听 Activity 变化,应使用 eventBus 的 MESSAGE_UPDATE 事件
*/

export interface ActivityManager {
/**
* 获取当前活跃的 activityType
*/
getCurrentActivityType: () => string | null;
/**
* 获取当前 Activity 内容(基于当前活跃的 activityType)
*/
getCurrentActivity: () => ActivityData | null;
/**
* 获取指定 activityType 的 Activity 内容
*/
getActivity: (activityType: string) => ActivityData | null;
/**
* 获取所有 activityType
*/
getAllActivityTypes: () => string[];
/**
* 处理 AG-UI Activity 事件
*/
handleActivityEvent: (event: {
type: string;
activityType: string;
content?: any;
patch?: any[];
messageId?: string;
}) => ActivityData | null;
/**
* 清理活动数据
*/
clear: () => void;
}

export class ActivityManagerImpl implements ActivityManager {
private activities: Record<string, ActivityData> = {};

private currentActivityType: string | null = null;

/**
* 获取当前活跃的 activityType
*/
getCurrentActivityType(): string | null {
return this.currentActivityType;
}

/**
* 获取当前 Activity 内容
*/
getCurrentActivity(): ActivityData | null {
if (!this.currentActivityType) return null;
return this.activities[this.currentActivityType] || null;
}

/**
* 获取指定 activityType 的 Activity 内容
*/
getActivity(activityType: string): ActivityData | null {
return this.activities[activityType] || null;
}

/**
* 获取所有 activityType
*/
getAllActivityTypes(): string[] {
return Object.keys(this.activities);
}

/**
* 处理 AG-UI Activity 事件
* 返回更新后的 ActivityData,供 event-mapper 使用
*/
handleActivityEvent(event: {
type: string;
activityType: string;
content?: any;
patch?: any[];
messageId?: string;
}): ActivityData | null {
const { activityType, messageId } = event;

if (event.type === 'ACTIVITY_SNAPSHOT') {
// SNAPSHOT: 全量更新
const activityData: ActivityData = {
activityType,
content: event.content || {},
messageId,
};
this.setActivity(activityType, activityData);
return activityData;
}

if (event.type === 'ACTIVITY_DELTA') {
// DELTA: 增量更新
const isFirstDelta = !this.activities[activityType];
let currentActivity = this.activities[activityType];

// 如果没有 snapshot,自动初始化空的 activity(纯增量模式)
if (isFirstDelta) {
currentActivity = {
activityType,
content: this.inferInitialContent(event.patch),
messageId,
};
}

// 记录旧的 operations/messages 数量(用于增量标记)
const oldContent = currentActivity.content;
const oldOpsCount = this.getOperationsCount(oldContent);

let newContent = oldContent;

// 使用 patch 进行 JSON Patch 更新
if (event.patch && Array.isArray(event.patch)) {
newContent = applyJsonPatch(oldContent, event.patch);
}

// 计算新增的 operations 范围
const newOpsCount = this.getOperationsCount(newContent);
const deltaInfo = newOpsCount > oldOpsCount ? { fromIndex: oldOpsCount, toIndex: newOpsCount } : undefined;

const activityData: ActivityData = {
activityType,
content: newContent,
messageId,
deltaInfo,
};

this.setActivity(activityType, activityData);
return activityData;
}

return null;
}

/**
* 清理所有活动数据
* 用于新一轮对话开始时重置状态
*/
clear(): void {
this.activities = {};
this.currentActivityType = null;
}

/**
* 根据 JSON Patch 路径推断初始内容结构
*/
private inferInitialContent(patch: any[] | undefined): Record<string, any> {
if (!patch || !Array.isArray(patch) || patch.length === 0) {
return {};
}

const initialContent: Record<string, any> = {};

for (const op of patch) {
if (op.path) {
const pathParts = op.path.split('/').filter(Boolean);
if (pathParts.length > 0) {
const rootKey = pathParts[0];
// 如果路径包含 - 或数字索引,说明是数组
if (pathParts.length > 1 && (pathParts[1] === '-' || /^\d+$/.test(pathParts[1]))) {
if (!initialContent[rootKey]) {
initialContent[rootKey] = [];
}
}
}
}
}

return initialContent;
}

/**
* 获取 content 中的 operations/messages 数量
*/
private getOperationsCount(content: Record<string, any> | null | undefined): number {
if (!content) return 0;
const ops = content.operations || content.messages;
return Array.isArray(ops) ? ops.length : 0;
}

/**
* 设置 Activity
*/
private setActivity(activityType: string, activity: ActivityData): void {
this.activities[activityType] = activity;
this.currentActivityType = activityType;
}
}

// 全局单例
export const activityManager = new ActivityManagerImpl();
Loading