CodeReview/frontend/src/pages/AgentAudit/utils.ts

205 lines
5.1 KiB
TypeScript
Raw Normal View History

/**
* Agent Audit Utilities
* Helper functions for the Agent Audit page
*/
import type { AgentTreeNode, LogItem } from "./types";
/**
* Build tree structure from flat node list
*/
export function buildAgentTree(flatNodes: AgentTreeNode[]): AgentTreeNode[] {
if (!flatNodes || flatNodes.length === 0) return [];
// Create node map
const nodeMap = new Map<string, AgentTreeNode>();
flatNodes.forEach(node => {
nodeMap.set(node.agent_id, { ...node, children: [] });
});
// Build tree structure
const rootNodes: AgentTreeNode[] = [];
flatNodes.forEach(node => {
const currentNode = nodeMap.get(node.agent_id)!;
if (node.parent_agent_id && nodeMap.has(node.parent_agent_id)) {
const parentNode = nodeMap.get(node.parent_agent_id)!;
parentNode.children.push(currentNode);
} else {
rootNodes.push(currentNode);
}
});
return rootNodes;
}
/**
* Find agent by ID in tree
*/
export function findAgentInTree(nodes: AgentTreeNode[], id: string): AgentTreeNode | null {
for (const node of nodes) {
if (node.agent_id === id) return node;
const found = findAgentInTree(node.children, id);
if (found) return found;
}
return null;
}
/**
* Find agent name by ID in tree
*/
export function findAgentName(nodes: AgentTreeNode[], id: string): string | null {
const agent = findAgentInTree(nodes, id);
return agent?.agent_name || null;
}
/**
* Generate unique log ID
*/
let logIdCounter = 0;
export function generateLogId(): string {
return `log-${++logIdCounter}`;
}
/**
* Reset log ID counter (for testing)
*/
export function resetLogIdCounter(): void {
logIdCounter = 0;
}
/**
* Get current time string for logs
*/
export function getTimeString(): string {
return new Date().toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
/**
* Create a log item
*/
export function createLogItem(item: Omit<LogItem, 'id' | 'time'>): LogItem {
return {
...item,
id: generateLogId(),
time: getTimeString(),
};
}
/**
* Clean thinking content (extract only the Thought part, remove Action/Action Input)
*/
export function cleanThinkingContent(content: string): string {
if (!content) return "";
let cleaned = content;
// 1. 尝试提取 Thought: 后面的内容
const thoughtMatch = cleaned.match(/Thought:\s*([\s\S]*?)(?=\n\s*Action\s*:|$)/i);
if (thoughtMatch && thoughtMatch[1]) {
cleaned = thoughtMatch[1].trim();
} else {
// 2. 如果没有 Thought: 前缀,尝试移除 Action 部分
// 匹配 Action: 及其后面的所有内容(包括开头的 Action
cleaned = cleaned.replace(/^Action\s*:[\s\S]*$/i, "");
cleaned = cleaned.replace(/\n\s*Action\s*:[\s\S]*$/i, "");
}
// 3. 移除可能残留的 Action Input 部分
cleaned = cleaned.replace(/Action\s*Input\s*:[\s\S]*$/i, "");
// 4. 清理空白和特殊字符
cleaned = cleaned.trim();
// 5. 如果清理后只剩下 "Action" 或类似的碎片,返回空
if (/^Action\s*$/i.test(cleaned) || cleaned.length < 5) {
return "";
}
return cleaned;
}
/**
* Truncate output string
*/
export function truncateOutput(output: string, maxLength: number = 1000): string {
if (output.length <= maxLength) return output;
return output.slice(0, maxLength) + '\n... (truncated)';
}
/**
* Calculate severity counts from findings
*/
export function calculateSeverityCounts(findings: { severity: string }[]): Record<string, number> {
return {
critical: findings.filter(f => f.severity === 'critical').length,
high: findings.filter(f => f.severity === 'high').length,
medium: findings.filter(f => f.severity === 'medium').length,
low: findings.filter(f => f.severity === 'low').length,
};
}
/**
* Check if task is in running state
*/
export function isTaskRunning(status: string | undefined): boolean {
return status === 'running' || status === 'pending';
}
/**
* Check if task is complete
*/
export function isTaskComplete(status: string | undefined): boolean {
return status === 'completed' || status === 'failed' || status === 'cancelled';
}
/**
* Format token count
*/
export function formatTokens(tokens: number): string {
return (tokens / 1000).toFixed(1) + 'k';
}
/**
* Filter logs by agent
*/
export function filterLogsByAgent(
logs: LogItem[],
selectedAgentId: string | null,
treeNodes: AgentTreeNode[],
showAllLogs: boolean
): LogItem[] {
if (showAllLogs || !selectedAgentId) {
return logs;
}
const selectedAgentName = findAgentName(treeNodes, selectedAgentId);
if (!selectedAgentName) return logs;
return logs.filter(log =>
log.agentName?.toLowerCase() === selectedAgentName.toLowerCase() ||
log.agentName?.toLowerCase().includes(selectedAgentName.toLowerCase().split('_')[0])
);
}
/**
* Debounce function
*/
export function debounce<T extends (...args: unknown[]) => unknown>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout> | null = null;
return (...args: Parameters<T>) => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}