Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .typos.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[files]
# Exclude README files from typos checking (they contain multiple languages)
extend-exclude = ["README*.md"]
# Exclude bundled-modules directory (contains third-party bundled code)
extend-exclude = ["README*.md", "**/bundled-modules/**"]
12 changes: 12 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
"indentStyle": "space",
"indentWidth": 2
}
},
{
"includes": ["**/*.d.ts"],
"linter": {
"rules": {
"complexity": {
"noBannedTypes": "off"
}
}
}
}
],
"files": {
Expand All @@ -46,6 +56,8 @@
"!**/coverage",
"!**/build",
"!**/assets",
"!**/bundled-modules",
"!**/.vscode",
"!.history"
]
}
Expand Down
65 changes: 65 additions & 0 deletions packages/aipex-react/src/components/file-manager/FileExplorer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* FileExplorer Component
*
* A simplified file explorer for browsing the virtual file system.
* For a full implementation, see browser-runtime/src/skill/components/file-manager/
*/

import { FolderOpen, HardDrive } from "lucide-react";
import type React from "react";
import { useState } from "react";

import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "../ui/card";
import type { FileSystemClient } from "./types";

interface FileExplorerProps {
fileSystemClient: FileSystemClient;
basePath?: string;
}

export const FileExplorer: React.FC<FileExplorerProps> = ({
fileSystemClient,
basePath = "/skills",
}) => {
const [loading] = useState(false);
void fileSystemClient;

return (
<Card className="w-full">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<HardDrive className="h-5 w-5" />
File Manager
</CardTitle>
<CardDescription>
Browse and manage files in the virtual file system
</CardDescription>
</CardHeader>

<CardContent>
{loading ? (
<div className="flex items-center justify-center py-8">
<span className="text-muted-foreground">Loading...</span>
</div>
) : (
<div className="flex flex-col items-center justify-center py-8 text-center">
<FolderOpen className="h-12 w-12 text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold mb-2">File Browser</h3>
<p className="text-muted-foreground">
File browser UI will be available in a future update
</p>
<p className="text-xs text-muted-foreground mt-2">
Base path: <span className="font-mono">{basePath}</span>
</p>
</div>
)}
</CardContent>
</Card>
);
};
14 changes: 14 additions & 0 deletions packages/aipex-react/src/components/file-manager/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* File Manager UI Components
*/

export { FileExplorer } from "./FileExplorer";
export type {
DiskUsage,
FileInfo,
FileStats,
FileSystemClient,
FileTreeNode,
SkillUsage,
} from "./types";
export * from "./utils";
106 changes: 106 additions & 0 deletions packages/aipex-react/src/components/file-manager/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* File Manager Adapter Types
*
* These interfaces define the contract between UI components and file system implementations.
*/

/**
* File tree node structure
*/
export interface FileTreeNode {
name: string;
path: string;
type: "file" | "directory";
size?: number;
modifiedAt?: number;
children?: FileTreeNode[];
}

/**
* File information
*/
export interface FileInfo {
path: string;
name: string;
type: "file" | "directory";
size: number;
modifiedAt: number;
content?: string;
}

/**
* Disk usage statistics
*/
export interface DiskUsage {
totalSize: number;
fileCount: number;
directoryCount: number;
bySkill: Record<string, SkillUsage>;
}

/**
* Per-skill disk usage
*/
export interface SkillUsage {
size: number;
fileCount: number;
directoryCount: number;
}

/**
* File stats (similar to Node.js fs.Stats)
*/
export interface FileStats {
isDirectory: boolean;
isFile: boolean;
size: number;
mtime: Date | number;
}

/**
* Client interface for file system operations
*
* UI components receive this interface and call its methods.
* Browser-ext provides the implementation using zenfs.
*/
export interface FileSystemClient {
/**
* Initialize the file system
*/
initialize(): Promise<void>;

/**
* Get file tree for a base path
*/
getFileTree(basePath: string): Promise<FileTreeNode[]>;

/**
* Get disk usage statistics
*/
getDiskUsage(basePath: string): Promise<DiskUsage>;

/**
* Read file content
*/
readFile(path: string, encoding?: "utf8"): Promise<string | ArrayBuffer>;

/**
* Remove file or directory
*/
rm(path: string, options?: { recursive?: boolean }): Promise<void>;

/**
* Check if file or directory exists
*/
exists(path: string): Promise<boolean>;

/**
* Get file stats
*/
stat(path: string): Promise<FileStats>;

/**
* Read directory contents
*/
readdir(path: string): Promise<string[]>;
}
124 changes: 124 additions & 0 deletions packages/aipex-react/src/components/file-manager/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Format bytes to human-readable string
*/
export function formatBytes(bytes: number): string {
if (bytes === 0) return "0 Bytes";

const k = 1024;
const sizes = ["Bytes", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));

return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;
}

/**
* Format date to human-readable string
*/
export function formatDate(date: Date): string {
const now = new Date();
const diff = now.getTime() - date.getTime();

// Less than 1 minute
if (diff < 60000) {
return "Just now";
}

// Less than 1 hour
if (diff < 3600000) {
const minutes = Math.floor(diff / 60000);
return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
}

// Less than 1 day
if (diff < 86400000) {
const hours = Math.floor(diff / 3600000);
return `${hours} hour${hours > 1 ? "s" : ""} ago`;
}

// Less than 7 days
if (diff < 604800000) {
const days = Math.floor(diff / 86400000);
return `${days} day${days > 1 ? "s" : ""} ago`;
}

// Otherwise, show the date
return date.toLocaleDateString();
}

/**
* Get file extension
*/
export function getFileExtension(filename: string): string {
const lastDot = filename.lastIndexOf(".");
if (lastDot === -1) return "";
return filename.substring(lastDot + 1).toLowerCase();
}

/**
* Get file icon based on extension
*/
export function getFileIcon(filename: string): string {
const ext = getFileExtension(filename);

const iconMap: Record<string, string> = {
// Documents
md: "📝",
txt: "📄",
pdf: "📕",
// Code
js: "📜",
ts: "📘",
tsx: "📘",
jsx: "📜",
json: "📋",
html: "🌐",
css: "🎨",
scss: "🎨",
// Images
png: "🖼️",
jpg: "🖼️",
jpeg: "🖼️",
gif: "🖼️",
svg: "🎨",
// Archives
zip: "📦",
tar: "📦",
gz: "📦",
// Other
xml: "📰",
yaml: "⚙️",
yml: "⚙️",
sh: "⚡",
py: "🐍",
go: "🐹",
rs: "🦀",
java: "☕",
c: "©️",
cpp: "©️",
h: "©️",
log: "📊",
};

return iconMap[ext] || "📄";
}

/**
* Check if a path is protected (should not be deleted)
*/
export function isProtectedPath(path: string): boolean {
const protectedPaths = ["/skills", "/skills/skill-creator"];

// Exact match
if (protectedPaths.includes(path)) {
return true;
}

// Check if path starts with protected path + /
for (const protectedPath of protectedPaths) {
if (path.startsWith(`${protectedPath}/`)) {
return true;
}
}

return false;
}
3 changes: 3 additions & 0 deletions packages/aipex-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ export * from "./adapters/chat-adapter.js";
export * from "./components/chatbot/index.js";
export * from "./components/content-script/index.js";
export * from "./components/fake-mouse/index.js";
export * from "./components/file-manager/index.js";
export * from "./components/intervention/index.js";
export * from "./components/omni/index.js";
export * from "./components/settings/index.js";
// Skill UI components moved to browser-ext - no longer exported from aipex-react
// export * from "./components/skill/index.js";
export * from "./hooks/index.js";
export * from "./lib/index.js";
export * from "./types/index.js";
Expand Down
3 changes: 3 additions & 0 deletions packages/browser-ext/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
"page": "src/pages/options/index.html",
"open_in_tab": true
},
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
},
"web_accessible_resources": [
{
"resources": ["assets/*"],
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-ext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"markdown-to-jsx": "^9.3.0",
"nanoid": "^5.1.6",
"react": "19.2.3",
"react-dom": "19.2.0",
"react-dom": "19.2.3",
"react-syntax-highlighter": "^16.1.0",
"remark-gfm": "^4.0.1",
"streamdown": "^1.6.10",
Expand Down
Loading