Commit 7211c835 authored by 王舵's avatar 王舵

Merge branch 'main' into feature/take-road

parents c107c059 e87c7566
This diff is collapsed.
<template>
<TimelinePanel
:events="events"
:getEventTypeLabel="getEventTypeLabel"
:formatTime="formatTime"
:getExpandedState="getExpandedState"
:toggleExpand="toggleExpand"
:isToolEventType="isToolEventType"
:hasValidToolInput="hasValidToolInput"
:hasValidToolOutput="hasValidToolOutput"
:onClearTimeline="handleClearTimeline"
/>
</template>
<script setup lang="ts">
import { ref, onUnmounted, onMounted } from 'vue'
import TimelinePanel from './TimelinePanel.vue'
import { TimelineEventStateManager } from '../services/timelineEventStateManager'
import { CacheService } from '../services/CacheService'
import type { TimelineEvent } from '../types/timeline'
import { eventTypeLabels } from '../types/timeline'
import { isToolEventType, hasValidToolInput, hasValidToolOutput } from '../utils/timelineUtils'
import { UnifiedEventProcessor } from '../services/UnifiedEventProcessor'
// 状态管理器
const stateManager = new TimelineEventStateManager();
// 缓存服务
const cacheService = new CacheService();
// 统一事件处理器
const eventProcessor = new UnifiedEventProcessor();
// 事件数据
const events = ref<TimelineEvent[]>([]);
// 注册事件处理器
eventProcessor.registerHandler((event: TimelineEvent) => {
// 添加事件到列表
events.value.push(event);
console.log('[TimelineContainer] 成功添加事件:', event.type, event.title);
});
// 添加时间轴事件
const addEvent = (event: any) => {
// 使用统一事件处理器处理并分发事件
eventProcessor.processAndDispatch(event);
};
// 获取事件类型标签
const getEventTypeLabel = (type: string): string => {
return cacheService.getCachedEventTypeLabel(type, eventTypeLabels,
(labels, t) => labels[t] || t);
};
// 格式化时间
const formatTime = (timestamp: number): string => {
return cacheService.getCachedFormattedTime(timestamp, (t) => {
const date = new Date(t);
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
});
};
// 获取事件的展开状态
const getExpandedState = (index: number): boolean => {
return stateManager.getExpandedState(index);
};
// 切换事件详细信息的展开状态
const toggleExpand = (index: number) => {
stateManager.toggleExpand(index);
};
// 清除时间轴
const handleClearTimeline = () => {
events.value = [];
eventProcessor.clearProcessedEvents();
stateManager.clearAllStates();
cacheService.clearAllCaches();
};
// 显示性能统计信息
const showPerformanceStats = () => {
const stats = eventProcessor.getPerformanceStats();
console.log('[TimelineContainer] 性能统计:', stats);
alert(`总处理事件数: ${stats.totalProcessed}\n重用事件数: ${stats.totalReused}\n重用率: ${stats.reuseRate}%`);
};
// 暴露方法供父组件调用
defineExpose({
addEvent,
clearTimeline: handleClearTimeline,
showPerformanceStats
});
// 组件卸载时清理资源
onUnmounted(() => {
stateManager.clearAllStates();
cacheService.clearAllCaches();
});
// 组件挂载时启动定期性能监控
onMounted(() => {
// 每30秒输出一次性能统计
const perfInterval = setInterval(() => {
const stats = eventProcessor.getPerformanceStats();
console.log('[TimelineContainer] 定期性能统计:', stats);
}, 30000);
// 组件卸载时清除定时器
onUnmounted(() => {
clearInterval(perfInterval);
});
});
</script>
<style scoped>
.timeline-container-wrapper {
height: 100%;
}
</style>
\ No newline at end of file
This diff is collapsed.
// 事件去重服务
export class EventDeduplicationService {
// 用于跟踪已添加的事件的Set,防止重复
private eventHashSet: Set<string> = new Set();
/**
* 检查是否为重复事件
* @param event 当前事件
* @returns 是否为重复事件
*/
isDuplicateEvent(event: any): boolean {
// 对于某些关键事件类型,我们允许重复显示(如错误事件)
const criticalEventTypes = ['error', 'result'];
if (criticalEventTypes.includes(event.type)) {
return false;
}
// 生成事件的唯一标识符
const eventHash = this.generateEventHash(event);
if (this.eventHashSet.has(eventHash)) {
return true;
}
// 将事件哈希添加到Set中
this.eventHashSet.add(eventHash);
// 限制Set大小以避免内存泄漏
if (this.eventHashSet.size > 1000) {
// 删除最早的100个条目
const iterator = this.eventHashSet.values();
for (let i = 0; i < 100; i++) {
const value = iterator.next();
if (!value.done) {
this.eventHashSet.delete(value.value);
}
}
}
return false;
}
/**
* 生成事件的唯一标识符
* @param event 事件对象
* @returns 事件哈希值
*/
private generateEventHash(event: any): string {
// 确保有时间戳
const timestamp = event.timestamp || Date.now();
// 对于工具事件,使用类型+工具名+工具操作+时间戳作为标识
if (event.type && event.type.startsWith('tool_') && event.toolName && event.toolAction) {
return `${event.type}-${event.toolName}-${event.toolAction}-${timestamp}`;
}
// 对于嵌入事件,使用URL+时间戳作为标识
if (event.type === 'embed' && event.embedUrl) {
return `embed-${event.embedUrl}-${timestamp}`;
}
// 对于其他事件,使用类型+标题+时间戳作为标识
return `${event.type}-${event.title || ''}-${timestamp}`;
}
/**
* 清除事件哈希集合
*/
clearEventHashSet(): void {
this.eventHashSet.clear();
}
}
\ No newline at end of file
// 统一的事件处理服务
import type { TimelineEvent } from '../types/timeline';
export class EventProcessingService {
/**
* 标准化事件对象
* @param event 原始事件数据
* @returns 标准化的事件对象
*/
normalizeEvent(event: any): TimelineEvent {
// 确保时间戳存在
const timestamp = event.timestamp || Date.now();
// 根据事件类型创建相应类型的事件对象
switch (event.type) {
case 'thought':
return {
type: 'thought',
title: event.title || '思考事件',
content: event.content || '',
thinkingType: event.thinkingType,
metadata: event.metadata,
timestamp
};
case 'tool_call':
return {
type: 'tool_call',
title: event.title || '工具调用',
toolName: event.toolName || '',
toolAction: event.toolAction || '',
toolInput: event.toolInput,
toolStatus: event.toolStatus,
metadata: event.metadata,
timestamp
};
case 'tool_result':
return {
type: 'tool_result',
title: event.title || '工具结果',
toolName: event.toolName || '',
toolAction: event.toolAction || '',
toolOutput: event.toolOutput,
toolStatus: event.toolStatus,
executionTime: event.executionTime,
metadata: event.metadata,
timestamp
};
case 'tool_error':
return {
type: 'tool_error',
title: event.title || '工具错误',
toolName: event.toolName || '',
errorMessage: event.errorMessage || '',
errorCode: event.errorCode,
metadata: event.metadata,
timestamp
};
case 'embed':
return {
type: 'embed',
title: event.title || '嵌入内容',
embedUrl: event.embedUrl || '',
embedType: event.embedType,
embedTitle: event.embedTitle,
embedHtmlContent: event.embedHtmlContent,
metadata: event.metadata,
timestamp
};
default:
return {
type: event.type || 'thought',
title: event.title || '未命名事件',
metadata: event.metadata,
timestamp
};
}
}
/**
* 处理事件类型转换
* @param event 事件对象
* @returns 处理后的事件对象
*/
processEventType(event: any): any {
// 处理thinking类型的事件,如果是final_answer则转换为result类型
const processedEvent = { ...event };
if (processedEvent.type === 'thought' && processedEvent.title === '最终答案') {
processedEvent.type = 'result';
}
return processedEvent;
}
}
\ No newline at end of file
// 对象池服务
export class ObjectPoolService<T> {
private pool: T[] = [];
private factory: () => T;
private resetter?: (obj: T) => void;
private maxSize: number;
constructor(factory: () => T, resetter?: (obj: T) => void, maxSize: number = 100) {
this.factory = factory;
this.resetter = resetter;
this.maxSize = maxSize;
}
/**
* 从对象池获取对象
* @returns 对象实例
*/
acquire(): T {
if (this.pool.length > 0) {
return this.pool.pop()!;
}
return this.factory();
}
/**
* 将对象归还到对象池
* @param obj 对象实例
*/
release(obj: T): void {
if (this.resetter) {
this.resetter(obj);
}
if (this.pool.length < this.maxSize) {
this.pool.push(obj);
}
}
/**
* 清空对象池
*/
clear(): void {
this.pool = [];
}
/**
* 获取对象池当前大小
* @returns 对象池大小
*/
size(): number {
return this.pool.length;
}
}
\ No newline at end of file
// 优化的事件处理服务
import { ObjectPoolService } from './ObjectPoolService';
import type { TimelineEvent } from '../types/timeline';
export class OptimizedEventProcessingService {
private eventObjectPool: ObjectPoolService<Record<string, any>>;
private MAX_POOL_SIZE: number = 100;
private totalEventsProcessed: number = 0;
private totalEventsReused: number = 0;
constructor() {
// 创建对象池用于事件对象
this.eventObjectPool = new ObjectPoolService<Record<string, any>>(
() => ({}), // 工厂函数创建空对象
(obj) => { // 重置函数清空对象属性
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
delete obj[key];
}
}
},
this.MAX_POOL_SIZE
);
}
/**
* 标准化事件对象(使用对象池优化)
* @param event 原始事件数据
* @returns 标准化的事件对象
*/
normalizeEvent(event: any): TimelineEvent {
// 从对象池获取对象
const normalizedEvent = this.eventObjectPool.acquire();
try {
// 确保时间戳存在
const timestamp = event.timestamp || Date.now();
// 根据事件类型创建相应类型的事件对象
switch (event.type) {
case 'thought':
Object.assign(normalizedEvent, {
type: 'thought',
title: event.title || '思考事件',
content: event.content || '',
thinkingType: event.thinkingType,
metadata: event.metadata,
timestamp
});
break;
case 'tool_call':
Object.assign(normalizedEvent, {
type: 'tool_call',
title: event.title || '工具调用',
toolName: event.toolName || '',
toolAction: event.toolAction || '',
toolInput: event.toolInput,
toolStatus: event.toolStatus,
metadata: event.metadata,
timestamp
});
break;
case 'tool_result':
Object.assign(normalizedEvent, {
type: 'tool_result',
title: event.title || '工具结果',
toolName: event.toolName || '',
toolAction: event.toolAction || '',
toolOutput: event.toolOutput,
toolStatus: event.toolStatus,
executionTime: event.executionTime,
metadata: event.metadata,
timestamp
});
break;
case 'tool_error':
Object.assign(normalizedEvent, {
type: 'tool_error',
title: event.title || '工具错误',
toolName: event.toolName || '',
errorMessage: event.errorMessage || '',
errorCode: event.errorCode,
metadata: event.metadata,
timestamp
});
break;
case 'embed':
Object.assign(normalizedEvent, {
type: 'embed',
title: event.title || '嵌入内容',
embedUrl: event.embedUrl || '',
embedType: event.embedType,
embedTitle: event.embedTitle,
embedHtmlContent: event.embedHtmlContent,
metadata: event.metadata,
timestamp
});
break;
default:
Object.assign(normalizedEvent, {
type: event.type || 'thought',
title: event.title || '未命名事件',
metadata: event.metadata,
timestamp
});
break;
}
this.totalEventsProcessed++;
return normalizedEvent as TimelineEvent;
} catch (error) {
// 如果出现错误,将对象归还到池中
this.eventObjectPool.release(normalizedEvent);
throw error;
}
}
/**
* 处理完事件后,将对象归还到池中
* @param obj 事件对象
*/
releaseEventObject(obj: Record<string, any>): void {
if (obj && typeof obj === 'object') {
// 清空对象属性
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
delete obj[key];
}
}
// 如果对象池未满,将对象放回池中
if (this.eventObjectPool.size() < this.MAX_POOL_SIZE) {
this.eventObjectPool.release(obj);
this.totalEventsReused++;
}
}
}
/**
* 获取性能统计信息
* @returns 性能统计信息
*/
getPerformanceStats(): { totalProcessed: number; totalReused: number; reuseRate: number } {
const reuseRate = this.totalEventsProcessed > 0
? (this.totalEventsReused / this.totalEventsProcessed) * 100
: 0;
return {
totalProcessed: this.totalEventsProcessed,
totalReused: this.totalEventsReused,
reuseRate: parseFloat(reuseRate.toFixed(2))
};
}
/**
* 清空统计信息
*/
clearStats(): void {
this.totalEventsProcessed = 0;
this.totalEventsReused = 0;
}
}
\ No newline at end of file
# 前端服务层优化说明
## 概述
本文档介绍了前端服务层的一系列优化措施,旨在提高代码质量、可维护性和性能。
## 优化内容
### 1. 统一类型定义
- **文件**: `types/timeline.ts`
- **功能**: 统一定义了所有时间轴相关的事件类型和标签映射
- **优势**: 避免类型重复定义,提高类型安全性
### 2. 工具函数库
- **文件**: `utils/timelineUtils.ts`
- **功能**: 包含时间轴相关的工具函数,如事件类型判断、输入输出验证等
- **优势**: 集中管理工具函数,避免重复实现
### 3. 类型守卫函数
- **文件**: `utils/typeGuards.ts`
- **功能**: 提供类型守卫函数,用于精确的类型检查
- **优势**: 提高类型安全性,减少运行时错误
### 4. 事件去重服务
- **文件**: `services/EventDeduplicationService.ts`
- **功能**: 统一处理事件去重逻辑
- **优势**: 避免重复事件处理,节省资源
### 5. 对象池服务
- **文件**: `services/ObjectPoolService.ts`
- **功能**: 提供通用的对象池实现
- **优势**: 减少对象创建和垃圾回收压力,提高性能
### 6. 优化的事件处理服务
- **文件**: `services/OptimizedEventProcessingService.ts`
- **功能**: 使用对象池优化事件对象的创建和管理
- **优势**: 提高性能,减少内存分配
### 7. 统一事件处理器
- **文件**: `services/UnifiedEventProcessor.ts`
- **功能**: 整合所有事件处理逻辑
- **优势**: 统一事件处理流程,便于维护
## 使用方法
### 1. 类型和工具函数使用
```typescript
import type { TimelineEvent } from '../types/timeline';
import { isToolEventType, hasValidToolInput } from '../utils/timelineUtils';
import { isThoughtEvent } from '../utils/typeGuards';
```
### 2. 事件处理服务使用
```typescript
import { UnifiedEventProcessor } from '../services/UnifiedEventProcessor';
const eventProcessor = new UnifiedEventProcessor();
eventProcessor.registerHandler((event: TimelineEvent) => {
// 处理事件
});
```
### 3. 性能监控
在TimelineContainer组件中提供了性能监控功能:
```typescript
// 显示性能统计信息
showPerformanceStats();
// 定期输出性能统计(每30秒)
// 在控制台查看: [TimelineContainer] 定期性能统计
```
## 性能优化效果
通过对象池和事件去重等优化措施,预期可以获得以下性能提升:
1. 减少对象创建次数,降低垃圾回收压力
2. 避免重复事件处理,节省CPU资源
3. 提高事件处理速度,改善用户体验
## 维护建议
1. 定期查看性能监控数据,评估优化效果
2. 新增事件类型时,需在`types/timeline.ts`中定义相应类型
3. 新增工具函数时,应添加到`utils/timelineUtils.ts`
4. 保持服务层的单一职责原则,避免功能交叉
\ No newline at end of file
// 统一事件处理器
import type { TimelineEvent } from '../types/timeline'
import { EventProcessingService } from './EventProcessingService'
import { EventDeduplicationService } from './EventDeduplicationService'
import { OptimizedEventProcessingService } from './OptimizedEventProcessingService'
export class UnifiedEventProcessor {
private eventProcessingService: EventProcessingService
private eventDeduplicationService: EventDeduplicationService
private optimizedEventProcessingService: OptimizedEventProcessingService
private eventHandlers: Array<(event: TimelineEvent) => void> = []
private processedEvents: TimelineEvent[] = []
constructor() {
this.eventProcessingService = new EventProcessingService()
this.eventDeduplicationService = new EventDeduplicationService()
this.optimizedEventProcessingService = new OptimizedEventProcessingService()
}
/**
* 处理接收到的原始事件数据
* @param rawData 原始事件数据
* @returns 处理后的标准化事件对象
*/
processRawEvent(rawData: any): TimelineEvent | null {
try {
// 验证数据
if (!rawData || typeof rawData !== 'object') {
console.warn('[UnifiedEventProcessor] 无效的事件数据:', rawData)
return null
}
// 检查是否为重复事件
if (this.eventDeduplicationService.isDuplicateEvent(rawData)) {
console.log('[UnifiedEventProcessor] 跳过重复事件:', rawData.type, rawData.title)
return null
}
// 处理事件类型转换
const processedEvent = this.eventProcessingService.processEventType(rawData)
// 标准化事件对象(使用优化的服务)
const normalizedEvent = this.optimizedEventProcessingService.normalizeEvent(processedEvent)
// 添加到已处理事件列表
this.processedEvents.push(normalizedEvent)
// 限制已处理事件列表大小以避免内存泄漏
if (this.processedEvents.length > 1000) {
this.processedEvents.shift()
}
return normalizedEvent
} catch (error) {
console.error('[UnifiedEventProcessor] 处理事件数据时发生错误:', error, '原始数据:', rawData)
return null
}
}
/**
* 注册事件处理器
* @param handler 事件处理器函数
*/
registerHandler(handler: (event: TimelineEvent) => void): void {
this.eventHandlers.push(handler)
}
/**
* 分发事件给所有注册的处理器
* @param event 事件对象
*/
dispatchEvent(event: TimelineEvent): void {
this.eventHandlers.forEach(handler => {
try {
handler(event)
} catch (error) {
console.error('[UnifiedEventProcessor] 事件处理器执行错误:', error)
}
})
}
/**
* 处理并分发事件
* @param rawData 原始事件数据
*/
processAndDispatch(rawData: any): void {
const event = this.processRawEvent(rawData)
if (event) {
this.dispatchEvent(event)
}
}
/**
* 清除已处理事件列表
*/
clearProcessedEvents(): void {
this.processedEvents = []
this.eventDeduplicationService.clearEventHashSet()
}
/**
* 获取性能统计信息
* @returns 性能统计信息
*/
getPerformanceStats(): { totalProcessed: number; totalReused: number; reuseRate: number } {
return this.optimizedEventProcessingService.getPerformanceStats();
}
}
\ No newline at end of file
// 统一的时间轴事件类型定义
export interface BaseTimelineEvent {
type: string;
title: string;
timestamp: number;
metadata?: Record<string, any>;
}
export interface ThoughtEvent extends BaseTimelineEvent {
content: string;
thinkingType?: string;
}
export interface ToolCallEvent extends BaseTimelineEvent {
toolName: string;
toolAction?: string;
toolInput?: any;
toolStatus: string;
}
export interface ToolResultEvent extends BaseTimelineEvent {
toolName: string;
toolAction?: string;
toolOutput?: any;
toolStatus: string;
executionTime?: number;
}
export interface ToolErrorEvent extends BaseTimelineEvent {
toolName: string;
errorMessage: string;
errorCode?: string;
}
export interface EmbedEvent extends BaseTimelineEvent {
embedUrl: string;
embedType?: string;
embedTitle: string;
embedHtmlContent?: string;
}
export type TimelineEvent =
| ThoughtEvent
| ToolCallEvent
| ToolResultEvent
| ToolErrorEvent
| EmbedEvent
| BaseTimelineEvent;
// 事件类型标签映射
export const eventTypeLabels: Record<string, string> = {
thought: '🧠 思考',
tool_call: '🔧 工具调用',
tool_result: '✅ 工具结果',
tool_error: '❌ 工具错误',
embed: '🌐 网页预览',
log: '📝 日志',
result: '🎯 最终答案',
observation: '🔍 观察'
};
\ No newline at end of file
// 时间轴工具函数库
/**
* 检查是否为工具事件类型
* @param type 事件类型
* @returns 是否为工具事件类型
*/
export function isToolEventType(type: string): boolean {
return ['tool_call', 'tool_result', 'tool_error'].includes(type);
}
/**
* 检查工具输入是否有效
* @param event 事件对象
* @returns 工具输入是否有效
*/
export function hasValidToolInput(event: any): boolean {
return event.type === 'tool_call' && event.toolInput !== null && event.toolInput !== undefined;
}
/**
* 检查工具输出是否有效
* @param event 事件对象
* @returns 工具输出是否有效
*/
export function hasValidToolOutput(event: any): boolean {
return event.type === 'tool_result' && event.toolOutput !== null && event.toolOutput !== undefined;
}
/**
* 截断标题
* @param title 标题
* @param maxLength 最大长度
* @returns 截断后的标题
*/
export function truncateTitle(title: string, maxLength: number = 30): string {
if (!title) return '';
return title.length > maxLength ? title.substring(0, maxLength) + '...' : title;
}
\ No newline at end of file
// 类型守卫函数
import type {
TimelineEvent,
ThoughtEvent,
ToolCallEvent,
ToolResultEvent,
ToolErrorEvent,
EmbedEvent
} from '../types/timeline';
/**
* 检查是否为思考事件
* @param event 事件对象
* @returns 是否为思考事件
*/
export function isThoughtEvent(event: TimelineEvent): event is ThoughtEvent {
return event.type === 'thought';
}
/**
* 检查是否为工具调用事件
* @param event 事件对象
* @returns 是否为工具调用事件
*/
export function isToolCallEvent(event: TimelineEvent): event is ToolCallEvent {
return event.type === 'tool_call';
}
/**
* 检查是否为工具结果事件
* @param event 事件对象
* @returns 是否为工具结果事件
*/
export function isToolResultEvent(event: TimelineEvent): event is ToolResultEvent {
return event.type === 'tool_result';
}
/**
* 检查是否为工具错误事件
* @param event 事件对象
* @returns 是否为工具错误事件
*/
export function isToolErrorEvent(event: TimelineEvent): event is ToolErrorEvent {
return event.type === 'tool_error';
}
/**
* 检查是否为嵌入事件
* @param event 事件对象
* @returns 是否为嵌入事件
*/
export function isEmbedEvent(event: TimelineEvent): event is EmbedEvent {
return event.type === 'embed';
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment