File size: 2,648 Bytes
43024e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
 * Hugging Face XetHub Storage Helpers
 * 
 * These utilities upload files to your HF Space's persistent storage
 * (assets/ for GLB models, outputs/ for rendered videos).
 * 
 * Requires the HF_TOKEN environment variable to be set as a Space Secret.
 * 
 * Usage in your component:
 *   import { uploadAsset, uploadOutput } from '../utils/hfStorage';
 *   await uploadAsset(file, 'my-space/studio3d');
 */

const HF_API = 'https://huggingface.co/api';

/**
 * Upload a GLB model to the Space's assets/ folder via HF API
 */
export async function uploadAsset(
  file: File,
  repoId: string,
  hfToken: string
): Promise<string> {
  const filename = `assets/${Date.now()}_${file.name}`;
  return uploadToHF(file, repoId, filename, hfToken);
}

/**
 * Upload a rendered video/image to the Space's outputs/ folder
 */
export async function uploadOutput(
  blob: Blob,
  filename: string,
  repoId: string,
  hfToken: string
): Promise<string> {
  const file = new File([blob], filename, { type: blob.type });
  const path = `outputs/${Date.now()}_${filename}`;
  return uploadToHF(file, repoId, path, hfToken);
}

async function uploadToHF(
  file: File,
  repoId: string,
  path: string,
  hfToken: string
): Promise<string> {
  const arrayBuffer = await file.arrayBuffer();

  const response = await fetch(
    `${HF_API}/spaces/${repoId}/upload/${encodeURIComponent(path)}`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${hfToken}`,
        'Content-Type': file.type || 'application/octet-stream',
      },
      body: arrayBuffer,
    }
  );

  if (!response.ok) {
    throw new Error(`HF upload failed: ${response.statusText}`);
  }

  const data = await response.json();
  // Return the public URL to the uploaded file
  return `https://huggingface.co/spaces/${repoId}/resolve/main/${path}`;
}

/**
 * List files in assets/ folder of the Space
 */
export async function listAssets(repoId: string, hfToken: string): Promise<string[]> {
  const response = await fetch(
    `${HF_API}/spaces/${repoId}/tree/main/assets`,
    {
      headers: { Authorization: `Bearer ${hfToken}` },
    }
  );
  if (!response.ok) return [];
  const data = await response.json();
  return data.map((f: any) => f.path);
}

/**
 * List files in outputs/ folder of the Space
 */
export async function listOutputs(repoId: string, hfToken: string): Promise<string[]> {
  const response = await fetch(
    `${HF_API}/spaces/${repoId}/tree/main/outputs`,
    {
      headers: { Authorization: `Bearer ${hfToken}` },
    }
  );
  if (!response.ok) return [];
  const data = await response.json();
  return data.map((f: any) => f.path);
}