Files
ai-agent/frontend/src/tree.ts
Gabriel Vidal a85cd7682a feat(ai-agent): file-tree search bar
- Sidebar gains a search input that filters the file tree by path
  (case-insensitive) and auto-expands matching folders.
- FileTree rows are router links highlighting the active path; folders show
  aggregated token/cost. tree.ts adds filterFiles + nodeStats helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 12:58:15 +02:00

61 lines
1.7 KiB
TypeScript

import type { FileEntry, TreeNode } from "./types";
/** Build a nested folder/file tree from a flat list of files (by path). */
export function buildTree(files: FileEntry[]): TreeNode {
const root: TreeNode = { name: "", path: "", dir: true, children: [] };
for (const file of files) {
const parts = file.path.split("/");
let node = root;
let acc = "";
parts.forEach((part, i) => {
acc = acc ? `${acc}/${part}` : part;
const isLeaf = i === parts.length - 1;
let child = node.children.find((c) => c.name === part);
if (!child) {
child = {
name: part,
path: acc,
dir: !isLeaf,
file: isLeaf ? file : undefined,
children: [],
};
node.children.push(child);
}
node = child;
});
}
sortNode(root);
return root;
}
function sortNode(node: TreeNode) {
node.children.sort((a, b) => {
if (a.dir !== b.dir) return a.dir ? -1 : 1; // folders first
return a.name.localeCompare(b.name);
});
node.children.forEach(sortNode);
}
/** Case-insensitive substring filter on the full path. */
export function filterFiles(files: FileEntry[], query: string): FileEntry[] {
const q = query.trim().toLowerCase();
if (!q) return files;
return files.filter((f) => f.path.toLowerCase().includes(q));
}
/** Sum the token count + dollar cost over the files contained under a node. */
export function nodeStats(node: TreeNode): { tokens: number; cost: number } {
if (node.file) {
return { tokens: node.file.tokens, cost: node.file.cost };
}
return node.children.reduce(
(acc, c) => {
const t = nodeStats(c);
return { tokens: acc.tokens + t.tokens, cost: acc.cost + t.cost };
},
{ tokens: 0, cost: 0 },
);
}