|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const Utils = {
|
|
|
|
|
|
toast: {
|
|
|
show(message, type = "info", duration = 3000) {
|
|
|
const container = document.getElementById("toast-container");
|
|
|
const toast = document.createElement("div");
|
|
|
toast.className = `toast ${type}`;
|
|
|
|
|
|
const icons = {
|
|
|
success: "β
",
|
|
|
error: "β",
|
|
|
warning: "β οΈ",
|
|
|
info: "βΉοΈ"
|
|
|
};
|
|
|
|
|
|
toast.innerHTML = `
|
|
|
<span class="toast-icon">${icons[type]}</span>
|
|
|
<span class="toast-message">${message}</span>
|
|
|
<button class="toast-close">Γ</button>
|
|
|
`;
|
|
|
|
|
|
container.appendChild(toast);
|
|
|
|
|
|
const close = () => {
|
|
|
toast.style.animation = "slideOut 0.3s ease";
|
|
|
setTimeout(() => toast.remove(), 300);
|
|
|
};
|
|
|
|
|
|
toast.querySelector(".toast-close").onclick = close;
|
|
|
if (duration > 0) setTimeout(close, duration);
|
|
|
|
|
|
return toast;
|
|
|
},
|
|
|
|
|
|
success: (msg, duration) => Utils.toast.show(msg, "success", duration),
|
|
|
error: (msg, duration) => Utils.toast.show(msg, "error", duration),
|
|
|
warning: (msg, duration) => Utils.toast.show(msg, "warning", duration),
|
|
|
info: (msg, duration) => Utils.toast.show(msg, "info", duration)
|
|
|
},
|
|
|
|
|
|
|
|
|
modal: {
|
|
|
_initialized: false,
|
|
|
|
|
|
open(modalId) {
|
|
|
const modal = document.getElementById(modalId);
|
|
|
if (modal) {
|
|
|
modal.classList.add("active");
|
|
|
document.body.style.overflow = "hidden";
|
|
|
}
|
|
|
},
|
|
|
|
|
|
close(modalId) {
|
|
|
const modal = document.getElementById(modalId);
|
|
|
if (modal) {
|
|
|
modal.classList.remove("active");
|
|
|
document.body.style.overflow = "";
|
|
|
}
|
|
|
},
|
|
|
|
|
|
init() {
|
|
|
|
|
|
if (this._initialized) return;
|
|
|
this._initialized = true;
|
|
|
|
|
|
|
|
|
document.querySelectorAll(".modal").forEach(modal => {
|
|
|
modal.addEventListener("click", e => {
|
|
|
if (e.target === modal) {
|
|
|
Utils.modal.close(modal.id);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
document.querySelectorAll(".modal-close, [data-modal]").forEach(btn => {
|
|
|
btn.addEventListener("click", () => {
|
|
|
const modalId = btn.getAttribute("data-modal");
|
|
|
if (modalId) Utils.modal.close(modalId);
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
|
|
|
|
|
|
storage: {
|
|
|
get(key, defaultValue = null) {
|
|
|
try {
|
|
|
const value = localStorage.getItem(key);
|
|
|
return value ? JSON.parse(value) : defaultValue;
|
|
|
} catch {
|
|
|
return defaultValue;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
set(key, value) {
|
|
|
try {
|
|
|
localStorage.setItem(key, JSON.stringify(value));
|
|
|
return true;
|
|
|
} catch {
|
|
|
return false;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
remove(key) {
|
|
|
localStorage.removeItem(key);
|
|
|
},
|
|
|
|
|
|
clear() {
|
|
|
localStorage.clear();
|
|
|
}
|
|
|
},
|
|
|
|
|
|
|
|
|
validateApiKey(key) {
|
|
|
if (!key) {
|
|
|
return { valid: false, error: "API key is required" };
|
|
|
}
|
|
|
|
|
|
if (!key.startsWith("sk-or-")) {
|
|
|
return { valid: false, error: "Invalid OpenRouter API key format (must start with 'sk-or-')" };
|
|
|
}
|
|
|
|
|
|
if (key.length < 20) {
|
|
|
return { valid: false, error: "API key too short" };
|
|
|
}
|
|
|
|
|
|
return { valid: true };
|
|
|
},
|
|
|
|
|
|
|
|
|
formatFileSize(bytes) {
|
|
|
if (bytes === 0) return "0 B";
|
|
|
const k = 1024;
|
|
|
const sizes = ["B", "KB", "MB", "GB"];
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
|
},
|
|
|
|
|
|
|
|
|
getFileExtension(filename) {
|
|
|
const parts = filename.split(".");
|
|
|
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : "";
|
|
|
},
|
|
|
|
|
|
|
|
|
getLanguageFromExtension(ext) {
|
|
|
const map = {
|
|
|
js: "javascript",
|
|
|
jsx: "javascript",
|
|
|
ts: "typescript",
|
|
|
tsx: "typescript",
|
|
|
py: "python",
|
|
|
rb: "ruby",
|
|
|
java: "java",
|
|
|
cpp: "cpp",
|
|
|
c: "c",
|
|
|
h: "c",
|
|
|
hpp: "cpp",
|
|
|
cs: "csharp",
|
|
|
go: "go",
|
|
|
rs: "rust",
|
|
|
php: "php",
|
|
|
html: "html",
|
|
|
htm: "html",
|
|
|
css: "css",
|
|
|
scss: "scss",
|
|
|
less: "less",
|
|
|
sass: "sass",
|
|
|
json: "json",
|
|
|
xml: "xml",
|
|
|
md: "markdown",
|
|
|
sql: "sql",
|
|
|
sh: "bash",
|
|
|
bash: "bash",
|
|
|
zsh: "bash",
|
|
|
yml: "yaml",
|
|
|
yaml: "yaml",
|
|
|
toml: "toml",
|
|
|
vue: "markup",
|
|
|
svelte: "markup",
|
|
|
astro: "markup",
|
|
|
swift: "swift",
|
|
|
kt: "kotlin",
|
|
|
kts: "kotlin",
|
|
|
dart: "dart",
|
|
|
lua: "lua",
|
|
|
r: "r",
|
|
|
scala: "scala",
|
|
|
groovy: "groovy",
|
|
|
txt: "none",
|
|
|
log: "none",
|
|
|
env: "bash",
|
|
|
dockerfile: "docker",
|
|
|
makefile: "makefile"
|
|
|
};
|
|
|
return map[ext] || "none";
|
|
|
},
|
|
|
|
|
|
|
|
|
debounce(func, wait) {
|
|
|
let timeout;
|
|
|
const debounced = function executedFunction(...args) {
|
|
|
const later = () => {
|
|
|
timeout = null;
|
|
|
func(...args);
|
|
|
};
|
|
|
if (timeout) clearTimeout(timeout);
|
|
|
timeout = setTimeout(later, wait);
|
|
|
};
|
|
|
|
|
|
debounced.cancel = () => {
|
|
|
if (timeout) clearTimeout(timeout);
|
|
|
};
|
|
|
return debounced;
|
|
|
},
|
|
|
|
|
|
|
|
|
async copyToClipboard(text) {
|
|
|
try {
|
|
|
await navigator.clipboard.writeText(text);
|
|
|
Utils.toast.success("Copied to clipboard!");
|
|
|
return true;
|
|
|
} catch (err) {
|
|
|
Utils.toast.error("Failed to copy to clipboard");
|
|
|
return false;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
|
|
|
formatDateTime(date) {
|
|
|
const d = new Date(date);
|
|
|
const pad = n => n.toString().padStart(2, "0");
|
|
|
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
|
},
|
|
|
|
|
|
|
|
|
escapeHtml(text) {
|
|
|
const div = document.createElement("div");
|
|
|
div.textContent = text;
|
|
|
return div.innerHTML;
|
|
|
},
|
|
|
|
|
|
|
|
|
truncate(text, maxLength) {
|
|
|
return text.length > maxLength ? text.substring(0, maxLength) + "..." : text;
|
|
|
},
|
|
|
|
|
|
|
|
|
isTextFile(filename) {
|
|
|
const textExtensions = [
|
|
|
"txt",
|
|
|
"md",
|
|
|
"js",
|
|
|
"jsx",
|
|
|
"ts",
|
|
|
"tsx",
|
|
|
"json",
|
|
|
"html",
|
|
|
"css",
|
|
|
"scss",
|
|
|
"less",
|
|
|
"sass",
|
|
|
"py",
|
|
|
"rb",
|
|
|
"java",
|
|
|
"cpp",
|
|
|
"c",
|
|
|
"h",
|
|
|
"hpp",
|
|
|
"cs",
|
|
|
"go",
|
|
|
"rs",
|
|
|
"php",
|
|
|
"xml",
|
|
|
"yml",
|
|
|
"yaml",
|
|
|
"toml",
|
|
|
"sql",
|
|
|
"sh",
|
|
|
"bash",
|
|
|
"zsh",
|
|
|
"bat",
|
|
|
"ps1",
|
|
|
"vue",
|
|
|
"svelte",
|
|
|
"astro",
|
|
|
"swift",
|
|
|
"kt",
|
|
|
"kts",
|
|
|
"dart",
|
|
|
"lua",
|
|
|
"r",
|
|
|
"scala",
|
|
|
"groovy",
|
|
|
"dockerfile",
|
|
|
"makefile",
|
|
|
"cmake",
|
|
|
"env",
|
|
|
"gitignore",
|
|
|
"editorconfig"
|
|
|
];
|
|
|
const ext = Utils.getFileExtension(filename);
|
|
|
|
|
|
const baseName = filename.toLowerCase();
|
|
|
return (
|
|
|
textExtensions.includes(ext) ||
|
|
|
["dockerfile", "makefile", "gemfile", "rakefile", "procfile"].includes(baseName)
|
|
|
);
|
|
|
},
|
|
|
|
|
|
|
|
|
isCodeFile(filename) {
|
|
|
const codeExtensions = [
|
|
|
"js",
|
|
|
"jsx",
|
|
|
"ts",
|
|
|
"tsx",
|
|
|
"py",
|
|
|
"rb",
|
|
|
"java",
|
|
|
"cpp",
|
|
|
"c",
|
|
|
"cs",
|
|
|
"go",
|
|
|
"rs",
|
|
|
"php",
|
|
|
"vue",
|
|
|
"svelte"
|
|
|
];
|
|
|
const ext = Utils.getFileExtension(filename);
|
|
|
return codeExtensions.includes(ext);
|
|
|
},
|
|
|
|
|
|
|
|
|
loading: {
|
|
|
show(text = "Processing...") {
|
|
|
const overlay = document.getElementById("loading-overlay");
|
|
|
const loadingText = document.getElementById("loading-text");
|
|
|
if (overlay && loadingText) {
|
|
|
loadingText.textContent = text;
|
|
|
overlay.style.display = "flex";
|
|
|
}
|
|
|
},
|
|
|
|
|
|
hide() {
|
|
|
const overlay = document.getElementById("loading-overlay");
|
|
|
if (overlay) {
|
|
|
overlay.style.display = "none";
|
|
|
}
|
|
|
},
|
|
|
|
|
|
update(text) {
|
|
|
const loadingText = document.getElementById("loading-text");
|
|
|
if (loadingText) {
|
|
|
loadingText.textContent = text;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
|
Utils.modal.init();
|
|
|
});
|
|
|
|
|
|
export default Utils;
|
|
|
|