Imageopl / script.js
doeqoth's picture
ปรับให้ใช้งานได้จริงยืนยันตัวตนด้วยการใส่ API หรือถ้าเป็นตัวผมเองไม่ต้องยืนยันตัวตนปรับใช้กับฐานข้อมูลจริงเลยนะโดยให้มีการยืนยันตัวตนการเข้าถึงข้อมูลในครั้งแรกก็ได้แล้วก็สามารถแก้ไขข้อมูลในตารางได้ด้วยหัวข้อต่างๆต้องทำให้มันสามารถเปลี่ยนได้
5ea2408 verified
// Real Hugging Face API integration
class AssetManager {
constructor() {
this.assets = [];
this.currentId = 0;
this.token = null;
this.spaceName = null;
this.username = null;
}
async authenticate(token) {
this.token = token;
try {
const response = await fetch('https://huggingface.co/api/whoami-v2', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const userData = await response.json();
this.username = userData.name;
return true;
} catch (error) {
console.error('Authentication failed:', error);
return false;
}
}
async loadAssets(spaceName) {
this.spaceName = spaceName;
try {
const response = await fetch(`https://huggingface.co/api/spaces/${this.username}/${spaceName}/files`, {
headers: {
'Authorization': `Bearer ${this.token}`
}
});
const files = await response.json();
this.assets = files.map(file => ({
id: ++this.currentId,
name: file.path.split('/').pop(),
type: this.getFileType(file.path),
url: `https://huggingface.co/spaces/${this.username}/${spaceName}/resolve/main/${file.path}`,
preview: this.getPreviewUrl(file.path),
path: file.path,
lastModified: file.lastModified
}));
return this.assets;
} catch (error) {
console.error('Failed to load assets:', error);
return [];
}
}
getFileType(filename) {
const extension = filename.split('.').pop().toLowerCase();
const types = {
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'png': 'image/png',
'gif': 'image/gif',
'pdf': 'application/pdf',
'txt': 'text/plain',
'csv': 'text/csv',
'json': 'application/json'
};
return types[extension] || 'application/octet-stream';
}
getPreviewUrl(filename) {
const extension = filename.split('.').pop().toLowerCase();
if (['jpg', 'jpeg', 'png', 'gif'].includes(extension)) {
return `https://huggingface.co/spaces/${this.username}/${this.spaceName}/preview/${filename}`;
}
return `http://static.photos/office/320x240/${Math.floor(Math.random() * 100)}`;
}
async addAsset(file) {
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch(`https://huggingface.co/api/spaces/${this.username}/${this.spaceName}/upload`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`
},
body: formData
});
if (response.ok) {
const newAsset = {
id: ++this.currentId,
name: file.name,
type: file.type || this.getFileType(file.name),
url: `https://huggingface.co/spaces/${this.username}/${this.spaceName}/resolve/main/${file.name}`,
preview: this.getPreviewUrl(file.name),
path: file.name,
lastModified: new Date().toISOString()
};
this.assets.push(newAsset);
return newAsset;
}
return null;
} catch (error) {
console.error('Failed to upload file:', error);
return null;
}
}
async updateAsset(id, updates) {
const asset = this.assets.find(a => a.id === id);
if (!asset) return null;
// For Hugging Face, updating means deleting and re-uploading
if (updates.file) {
await this.deleteAsset(id);
return await this.addAsset(updates.file);
} else {
// For metadata updates
const index = this.assets.findIndex(a => a.id === id);
this.assets[index] = { ...asset, ...updates };
return this.assets[index];
}
}
async deleteAsset(id) {
const asset = this.assets.find(a => a.id === id);
if (!asset) return false;
try {
const response = await fetch(`https://huggingface.co/api/spaces/${this.username}/${this.spaceName}/delete/${asset.path}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${this.token}`
}
});
if (response.ok) {
this.assets = this.assets.filter(a => a.id !== id);
return true;
}
return false;
} catch (error) {
console.error('Failed to delete file:', error);
return false;
}
}
getAssets() {
return [...this.assets];
}
exportAsJSON() {
return JSON.stringify(this.assets, null, 2);
}
}
// Main application with authentication
class HuggingSpaceApp {
constructor() {
this.assetManager = new AssetManager();
this.initElements();
this.initEventListeners();
this.checkAuth();
}
initElements() {
this.elements = {
assetsTable: document.getElementById('assetsTable'),
uploadBtn: document.getElementById('uploadBtn'),
uploadModal: document.getElementById('uploadModal'),
closeModal: document.getElementById('closeModal'),
fileInput: document.getElementById('fileInput'),
confirmUpload: document.getElementById('confirmUpload'),
exportBtn: document.getElementById('exportBtn'),
jsonData: document.getElementById('jsonData'),
authModal: document.getElementById('authModal'),
tokenInput: document.getElementById('tokenInput'),
spaceInput: document.getElementById('spaceInput'),
authSubmit: document.getElementById('authSubmit'),
authError: document.getElementById('authError'),
userInfo: document.getElementById('userInfo')
};
}
initEventListeners() {
this.elements.uploadBtn.addEventListener('click', () => this.toggleModal(true));
this.elements.closeModal.addEventListener('click', () => this.toggleModal(false));
this.elements.confirmUpload.addEventListener('click', () => this.handleFileUpload());
this.elements.exportBtn.addEventListener('click', () => this.exportData());
this.elements.authSubmit.addEventListener('click', () => this.handleAuth());
}
async checkAuth() {
const token = localStorage.getItem('hfToken');
const space = localStorage.getItem('hfSpace');
if (token && space) {
const authenticated = await this.assetManager.authenticate(token);
if (authenticated) {
await this.assetManager.loadAssets(space);
this.render();
this.elements.authModal.classList.add('hidden');
this.updateUserInfo();
return;
}
}
this.elements.authModal.classList.remove('hidden');
}
async handleAuth() {
const token = this.elements.tokenInput.value.trim();
const space = this.elements.spaceInput.value.trim();
if (!token || !space) {
this.elements.authError.textContent = 'Please enter both token and space name';
return;
}
const authenticated = await this.assetManager.authenticate(token);
if (!authenticated) {
this.elements.authError.textContent = 'Invalid token. Please check and try again.';
return;
}
try {
await this.assetManager.loadAssets(space);
localStorage.setItem('hfToken', token);
localStorage.setItem('hfSpace', space);
this.elements.authModal.classList.add('hidden');
this.render();
this.updateUserInfo();
} catch (error) {
this.elements.authError.textContent = 'Failed to load space. Please check space name and try again.';
}
}
updateUserInfo() {
if (this.assetManager.username && this.assetManager.spaceName) {
this.elements.userInfo.innerHTML = `
<div class="flex items-center gap-2">
<span class="font-medium">${this.assetManager.username}</span>
<span class="text-gray-500">/</span>
<span class="font-medium">${this.assetManager.spaceName}</span>
</div>
`;
}
}
async render() {
await this.renderAssetsTable();
this.updateJsonPreview();
}
async renderAssetsTable() {
const { assetsTable } = this.elements;
assetsTable.innerHTML = '';
const assets = this.assetManager.getAssets();
if (assets.length === 0) {
assetsTable.innerHTML = `
<tr>
<td colspan="5" class="px-6 py-4 text-center text-gray-500">
No assets found. Upload files to get started.
</td>
</tr>
`;
return;
}
assets.forEach(asset => {
const row = document.createElement('tr');
row.className = 'hover:bg-gray-50';
row.innerHTML = this.createAssetRowHTML(asset);
assetsTable.appendChild(row);
this.addRowEventListeners(row, asset.id);
});
feather.replace();
}
createAssetRowHTML(asset) {
return `
<td class="px-6 py-4 whitespace-nowrap">
<img src="${asset.preview}" alt="${asset.name}" class="file-preview">
</td>
<td class="px-6 py-4 whitespace-nowrap editable-cell" data-id="${asset.id}" data-field="name">${asset.name}</td>
<td class="px-6 py-4 whitespace-nowrap editable-cell" data-id="${asset.id}" data-field="type">${asset.type}</td>
<td class="px-6 py-4 whitespace-nowrap url-cell editable-cell" data-id="${asset.id}" data-field="url">${asset.url}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium table-actions">
<button class="text-primary-500 hover:text-primary-600" data-action="copy" data-url="${asset.url}">
<i data-feather="copy"></i>
</button>
<button class="text-red-500 hover:text-red-600" data-action="delete" data-id="${asset.id}">
<i data-feather="trash-2"></i>
</button>
</td>
`;
}
addRowEventListeners(row, assetId) {
// Editable cells
row.querySelectorAll('.editable-cell').forEach(cell => {
cell.addEventListener('dblclick', () => this.makeCellEditable(cell));
});
// Action buttons
row.querySelector('[data-action="copy"]')?.addEventListener('click', (e) => this.copyUrl(e));
row.querySelector('[data-action="delete"]')?.addEventListener('click', (e) => this.deleteAsset(e));
}
makeCellEditable(cell) {
const originalValue = cell.textContent;
const id = parseInt(cell.dataset.id);
const field = cell.dataset.field;
cell.innerHTML = `<input type="text" value="${originalValue}" class="w-full p-1 border border-gray-300 rounded">`;
const input = cell.querySelector('input');
input.focus();
const handleBlur = () => {
const newValue = input.value;
cell.textContent = newValue;
this.assetManager.updateAsset(id, { [field]: newValue });
this.updateJsonPreview();
// Re-add event listeners
cell.addEventListener('dblclick', () => this.makeCellEditable(cell));
};
input.addEventListener('blur', handleBlur);
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleBlur();
}
});
}
copyUrl(e) {
const url = e.target.closest('button').dataset.url;
navigator.clipboard.writeText(url).then(() => {
const originalHTML = e.target.closest('button').innerHTML;
e.target.closest('button').innerHTML = '<i data-feather="check"></i>';
feather.replace();
setTimeout(() => {
e.target.closest('button').innerHTML = originalHTML;
feather.replace();
}, 2000);
});
}
deleteAsset(e) {
const id = parseInt(e.target.closest('button').dataset.id);
if (confirm('Are you sure you want to delete this asset?')) {
this.assetManager.deleteAsset(id);
this.render();
}
}
toggleModal(show) {
this.elements.uploadModal.classList.toggle('hidden', !show);
if (!show) {
this.elements.fileInput.value = '';
}
}
async handleFileUpload() {
const files = this.elements.fileInput.files;
if (files.length === 0) {
alert('Please select files to upload');
return;
}
try {
for (const file of files) {
await this.assetManager.addAsset(file);
}
await this.render();
this.toggleModal(false);
} catch (error) {
alert('Failed to upload files. Please try again.');
console.error(error);
}
}
exportData() {
const dataStr = this.assetManager.exportAsJSON();
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', 'hugging-space-assets.json');
linkElement.click();
}
updateJsonPreview() {
this.elements.jsonData.value = this.assetManager.exportAsJSON();
}
}
// Initialize the application when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new HuggingSpaceApp();
});