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

205 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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);
};
}