Skip to content

Commit b4939b0

Browse files
committed
chore(frontend): update .dockerignore and .gitignore to correct lib directory entry; add new utility and file handling modules
1 parent eb7bb06 commit b4939b0

File tree

5 files changed

+362
-2
lines changed

5 files changed

+362
-2
lines changed

.dockerignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ dist/
6060
downloads/
6161
eggs/
6262
.eggs/
63-
lib/
63+
# lib/
6464
lib64/
6565
parts/
6666
sdist/

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ dist/
1111
downloads/
1212
eggs/
1313
.eggs/
14-
lib/
14+
# lib/
1515
lib64/
1616
parts/
1717
sdist/

frontend/lib/env.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { env } from 'next-runtime-env';
2+
3+
export const getEnv = (key: string, defaultValue?: string): string => {
4+
try {
5+
const value = env(key);
6+
return value || defaultValue || '';
7+
} catch (error) {
8+
console.error(`Error getting environment variable ${key}:`, error);
9+
return defaultValue || '';
10+
}
11+
};
12+
13+
export const getApiUrl = (): string => {
14+
return getEnv('NEXT_PUBLIC_API_URL', 'https://api-evoai.evoapicloud.com');
15+
};

frontend/lib/file-utils.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
┌──────────────────────────────────────────────────────────────────────────────┐
3+
│ @author: Davidson Gomes │
4+
│ @file: /lib/file-utils.ts │
5+
│ Developed by: Davidson Gomes │
6+
│ Creation date: August 24, 2025 │
7+
│ Contact: contato@evolution-api.com │
8+
├──────────────────────────────────────────────────────────────────────────────┤
9+
│ @copyright © Evolution API 2025. All rights reserved. │
10+
│ Licensed under the Apache License, Version 2.0 │
11+
│ │
12+
│ You may not use this file except in compliance with the License. │
13+
│ You may obtain a copy of the License at │
14+
│ │
15+
│ http://www.apache.org/licenses/LICENSE-2.0 │
16+
│ │
17+
│ Unless required by applicable law or agreed to in writing, software │
18+
│ distributed under the License is distributed on an "AS IS" BASIS, │
19+
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
20+
│ See the License for the specific language governing permissions and │
21+
│ limitations under the License. │
22+
├──────────────────────────────────────────────────────────────────────────────┤
23+
│ @important │
24+
│ For any future changes to the code in this file, it is recommended to │
25+
│ include, together with the modification, the information of the developer │
26+
│ who changed it and the date of modification. │
27+
└──────────────────────────────────────────────────────────────────────────────┘
28+
*/
29+
30+
export interface FileData {
31+
filename: string;
32+
content_type: string;
33+
data: string;
34+
size: number;
35+
preview_url?: string;
36+
}
37+
38+
export function fileToBase64(file: File): Promise<string> {
39+
return new Promise((resolve, reject) => {
40+
const reader = new FileReader();
41+
reader.readAsDataURL(file);
42+
reader.onload = () => {
43+
const base64 = reader.result as string;
44+
const base64Data = base64.split(',')[1];
45+
resolve(base64Data);
46+
};
47+
reader.onerror = error => reject(error);
48+
});
49+
}
50+
51+
export async function fileToFileData(file: File): Promise<FileData> {
52+
const base64Data = await fileToBase64(file);
53+
const previewUrl = URL.createObjectURL(file);
54+
55+
return {
56+
filename: file.name,
57+
content_type: file.type,
58+
data: base64Data,
59+
size: file.size,
60+
preview_url: previewUrl
61+
};
62+
}
63+
64+
export function formatFileSize(bytes: number): string {
65+
if (bytes === 0) {
66+
return '0 Bytes';
67+
}
68+
69+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
70+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
71+
72+
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i];
73+
}
74+
75+
export function isImageFile(mimeType: string): boolean {
76+
return mimeType.startsWith('image/');
77+
}
78+
79+
export function isPdfFile(mimeType: string): boolean {
80+
return mimeType === 'application/pdf';
81+
}
82+
83+
export function getFileIcon(mimeType: string): string {
84+
if (isImageFile(mimeType)) {
85+
return 'image';
86+
}
87+
if (isPdfFile(mimeType)) {
88+
return 'file-text';
89+
}
90+
if (mimeType.includes('word')) {
91+
return 'file-text';
92+
}
93+
if (mimeType.includes('excel') || mimeType.includes('spreadsheet')) {
94+
return 'file-spreadsheet';
95+
}
96+
if (mimeType.includes('presentation') || mimeType.includes('powerpoint')) {
97+
return 'file-presentation';
98+
}
99+
if (mimeType.includes('text/')) {
100+
return 'file-text';
101+
}
102+
if (mimeType.includes('audio/')) {
103+
return 'file-audio';
104+
}
105+
if (mimeType.includes('video/')) {
106+
return 'file-video';
107+
}
108+
109+
return 'file';
110+
}

frontend/lib/utils.ts

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
┌──────────────────────────────────────────────────────────────────────────────┐
3+
│ @author: Davidson Gomes │
4+
│ @file: /lib/utils.ts │
5+
│ Developed by: Davidson Gomes │
6+
│ Creation date: May 13, 2025 │
7+
│ Contact: contato@evolution-api.com │
8+
├──────────────────────────────────────────────────────────────────────────────┤
9+
│ @copyright © Evolution API 2025. All rights reserved. │
10+
│ Licensed under the Apache License, Version 2.0 │
11+
│ │
12+
│ You may not use this file except in compliance with the License. │
13+
│ You may obtain a copy of the License at │
14+
│ │
15+
│ http://www.apache.org/licenses/LICENSE-2.0 │
16+
│ │
17+
│ Unless required by applicable law or agreed to in writing, software │
18+
│ distributed under the License is distributed on an "AS IS" BASIS, │
19+
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
20+
│ See the License for the specific language governing permissions and │
21+
│ limitations under the License. │
22+
├──────────────────────────────────────────────────────────────────────────────┤
23+
│ @important │
24+
│ For any future changes to the code in this file, it is recommended to │
25+
│ include, together with the modification, the information of the developer │
26+
│ who changed it and the date of modification. │
27+
└──────────────────────────────────────────────────────────────────────────────┘
28+
*/
29+
import { clsx, type ClassValue } from "clsx";
30+
import { twMerge } from "tailwind-merge";
31+
32+
export function cn(...inputs: ClassValue[]) {
33+
return twMerge(clsx(inputs));
34+
}
35+
36+
export function getAccessTokenFromCookie() {
37+
if (typeof document === "undefined") return "";
38+
const match = document.cookie.match(/(?:^|; )access_token=([^;]*)/);
39+
return match ? decodeURIComponent(match[1]) : "";
40+
}
41+
42+
/**
43+
* Obtém um parâmetro específico da URL atual
44+
*/
45+
export function getQueryParam(param: string): string | null {
46+
if (typeof window === "undefined") return null;
47+
const urlParams = new URLSearchParams(window.location.search);
48+
return urlParams.get(param);
49+
}
50+
51+
/**
52+
* Sanitizes the agent name by removing accents,
53+
* replacing spaces with underscores and removing special characters
54+
*/
55+
export function sanitizeAgentName(name: string): string {
56+
// Remove accents (normalize to basic form without combined characters)
57+
const withoutAccents = name.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
58+
59+
// Replace spaces with underscores and remove special characters
60+
return withoutAccents
61+
.replace(/\s+/g, "_") // Spaces to underscores
62+
.replace(/[^a-zA-Z0-9_]/g, ""); // Remove everything that is not alphanumeric or underscore
63+
}
64+
65+
/**
66+
* Escapes braces in instruction prompts to avoid interpretation errors
67+
* as context variables in Python. Uses a more robust approach to ensure
68+
* Python doesn't interpret any brace patterns as variables.
69+
*/
70+
export function escapePromptBraces(text: string): string {
71+
if (!text) return text;
72+
73+
// replace { per [ and } per ]
74+
return text.replace(/\{/g, "[").replace(/\}/g, "]");
75+
}
76+
77+
/**
78+
* Exports any data as a JSON file for download
79+
* @param data The data to export
80+
* @param filename The name of the file to download (without .json extension)
81+
* @param pretty Whether to pretty-print the JSON (default: true)
82+
* @param allAgents Optional array of all agents to resolve references
83+
* @returns boolean indicating success
84+
*/
85+
export function exportAsJson(
86+
data: any,
87+
filename: string,
88+
pretty: boolean = true,
89+
allAgents?: any[]
90+
): boolean {
91+
try {
92+
// Create a copy of the data
93+
let exportData = JSON.parse(JSON.stringify(data));
94+
95+
// If we have all agents available, use them to resolve references
96+
const agentsMap = new Map();
97+
if (allAgents && Array.isArray(allAgents)) {
98+
allAgents.forEach(agent => {
99+
if (agent && agent.id) {
100+
agentsMap.set(agent.id, { ...agent });
101+
}
102+
});
103+
} else if (exportData.agents && Array.isArray(exportData.agents)) {
104+
// If we're exporting a collection, build a map from that
105+
exportData.agents.forEach((agent: any) => {
106+
if (agent && agent.id) {
107+
agentsMap.set(agent.id, { ...agent });
108+
}
109+
});
110+
}
111+
112+
// Process each agent to replace IDs with full objects and remove sensitive fields
113+
const processAgent = (agent: any, depth = 0) => {
114+
if (!agent || depth > 2) return agent; // Limit recursion depth to avoid circular references
115+
116+
// Process agent_tools - replace IDs with full agent objects
117+
if (agent.config && agent.config.agent_tools && Array.isArray(agent.config.agent_tools)) {
118+
agent.config.agent_tools = agent.config.agent_tools.map((toolId: string) => {
119+
if (typeof toolId === 'string' && agentsMap.has(toolId)) {
120+
// Create a simplified version of the agent
121+
const toolAgent = { ...agentsMap.get(toolId) };
122+
return processAgent(toolAgent, depth + 1);
123+
}
124+
return toolId;
125+
});
126+
}
127+
128+
// Process sub_agents - replace IDs with full agent objects
129+
if (agent.config && agent.config.sub_agents && Array.isArray(agent.config.sub_agents)) {
130+
agent.config.sub_agents = agent.config.sub_agents.map((agentId: string) => {
131+
if (typeof agentId === 'string' && agentsMap.has(agentId)) {
132+
// Create a simplified version of the agent
133+
const subAgent = { ...agentsMap.get(agentId) };
134+
return processAgent(subAgent, depth + 1);
135+
}
136+
return agentId;
137+
});
138+
}
139+
140+
// Process task agents - extract tasks and create full agent objects
141+
if (agent.type === 'task' && agent.config?.tasks && Array.isArray(agent.config.tasks)) {
142+
agent.config.tasks = agent.config.tasks.map((task: any) => {
143+
if (task.agent_id && agentsMap.has(task.agent_id)) {
144+
// Add the corresponding agent to the task
145+
task.agent = processAgent({...agentsMap.get(task.agent_id)}, depth + 1);
146+
}
147+
return task;
148+
});
149+
}
150+
151+
// Process workflow nodes - recursively process any agent objects in agent-nodes
152+
if (agent.type === 'workflow' && agent.config?.workflow?.nodes && Array.isArray(agent.config.workflow.nodes)) {
153+
agent.config.workflow.nodes = agent.config.workflow.nodes.map((node: any) => {
154+
if (node.type === 'agent-node' && node.data?.agent) {
155+
// Process the embedded agent object
156+
node.data.agent = processAgent(node.data.agent, depth + 1);
157+
158+
// If this is a task agent, also process its tasks
159+
if (node.data.agent.type === 'task' && node.data.agent.config?.tasks && Array.isArray(node.data.agent.config.tasks)) {
160+
node.data.agent.config.tasks = node.data.agent.config.tasks.map((task: any) => {
161+
if (task.agent_id && agentsMap.has(task.agent_id)) {
162+
task.agent = processAgent({...agentsMap.get(task.agent_id)}, depth + 1);
163+
}
164+
return task;
165+
});
166+
}
167+
}
168+
return node;
169+
});
170+
}
171+
172+
// Remove sensitive fields
173+
const fieldsToRemove = [
174+
'api_key_id',
175+
'agent_card_url',
176+
'folder_id',
177+
'client_id',
178+
'created_at',
179+
'updated_at'
180+
];
181+
182+
// Remove top-level fields
183+
fieldsToRemove.forEach(field => {
184+
if (agent[field] !== undefined) {
185+
delete agent[field];
186+
}
187+
});
188+
189+
// Remove nested fields
190+
if (agent.config && agent.config.api_key !== undefined) {
191+
delete agent.config.api_key;
192+
}
193+
194+
return agent;
195+
};
196+
197+
// Apply processing for single agent or array of agents
198+
if (exportData.agents && Array.isArray(exportData.agents)) {
199+
// For collections of agents
200+
exportData.agents = exportData.agents.map((agent: any) => processAgent(agent));
201+
} else if (exportData.id && (exportData.type || exportData.name)) {
202+
// For a single agent
203+
exportData = processAgent(exportData);
204+
}
205+
206+
// Create JSON string
207+
const jsonString = pretty ? JSON.stringify(exportData, null, 2) : JSON.stringify(exportData);
208+
209+
// Create a Blob with the content
210+
const blob = new Blob([jsonString], { type: "application/json" });
211+
212+
// Create URL for the blob
213+
const url = URL.createObjectURL(blob);
214+
215+
// Create a temporary <a> element for download
216+
const link = document.createElement("a");
217+
link.href = url;
218+
link.download = filename.endsWith(".json") ? filename : `${filename}.json`;
219+
220+
// Append to document, click, and remove
221+
document.body.appendChild(link);
222+
link.click();
223+
224+
// Clean up
225+
setTimeout(() => {
226+
document.body.removeChild(link);
227+
URL.revokeObjectURL(url);
228+
}, 100);
229+
230+
return true;
231+
} catch (error) {
232+
console.error("Error exporting JSON:", error);
233+
return false;
234+
}
235+
}

0 commit comments

Comments
 (0)