File size: 7,060 Bytes
fa36814
59aa1d8
 
236548e
a0c22e2
 
f724384
a0c22e2
 
 
 
 
59aa1d8
236548e
59aa1d8
f724384
 
236548e
59aa1d8
236548e
 
 
 
 
 
59aa1d8
1598b2c
236548e
59aa1d8
 
236548e
59aa1d8
f724384
 
 
 
 
59aa1d8
 
 
 
236548e
59aa1d8
 
 
236548e
 
 
 
59aa1d8
 
 
f724384
 
 
 
 
236548e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f724384
236548e
59aa1d8
 
 
 
236548e
 
 
 
 
 
 
 
59aa1d8
236548e
 
 
 
 
 
 
 
 
 
 
 
 
59aa1d8
 
 
 
fa36814
a0c22e2
fa36814
 
 
 
a0c22e2
 
 
fa36814
f724384
fa36814
 
 
a0c22e2
fa36814
59aa1d8
 
 
 
 
 
fa36814
59aa1d8
 
 
 
 
 
 
 
 
 
236548e
59aa1d8
 
 
236548e
59aa1d8
 
 
 
 
fa36814
59aa1d8
 
 
fa36814
 
 
 
c7362e1
fa36814
 
a0c22e2
 
16e6e9c
 
 
c7362e1
16e6e9c
 
 
 
c7362e1
 
 
f724384
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c7362e1
 
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import gradio as gr
import shutil
from pathlib import Path
import os
from huggingface_hub import list_models

# Optional: Spaces API might not exist
try:
    from huggingface_hub import list_spaces
except ImportError:
    list_spaces = None

# ===============================
# Configuration
# ===============================
# Password is stored securely as a Hugging Face secret, not in code
UPLOAD_PASSWORD = os.environ.get("UPLOAD_PASSWORD")  # must be set in Space secrets

WORKFLOW_DIR = Path("/data/workflows")
WORKFLOW_IMG_DIR = Path("/data/workflows_images")
LORA_DIR = Path("/data/loras")
LORA_IMG_DIR = Path("/data/loras_images")

for d in [WORKFLOW_DIR, WORKFLOW_IMG_DIR, LORA_DIR, LORA_IMG_DIR]:
    d.mkdir(parents=True, exist_ok=True)

ALLOWED_FILE_EXTS = {".json", ".yaml", ".yml", ".png", ".safetensors", ".ckpt"}
ALLOWED_IMG_EXTS = {".png", ".jpg", ".jpeg", ".webp"}

# ===============================
# Upload handlers
# ===============================
def upload_workflow(file, image=None, password=""):
    if UPLOAD_PASSWORD is None:
        return "❌ Upload password not set in Space secrets."
    if password != UPLOAD_PASSWORD:
        return "❌ Unauthorized"
    if file is None:
        return "No file uploaded."
    src = Path(file.name)
    ext = src.suffix.lower()
    if ext not in ALLOWED_FILE_EXTS:
        return f"❌ Unsupported file type: `{ext}`"
    dest = WORKFLOW_DIR / src.name
    shutil.copy(src, dest)
    if image:
        img_ext = Path(image.name).suffix.lower()
        if img_ext in ALLOWED_IMG_EXTS:
            shutil.copy(image.name, WORKFLOW_IMG_DIR / (src.stem + img_ext))
    return f"βœ… Saved workflow: `{dest.name}`"


def upload_lora(file, image=None, password=""):
    if UPLOAD_PASSWORD is None:
        return "❌ Upload password not set in Space secrets."
    if password != UPLOAD_PASSWORD:
        return "❌ Unauthorized"
    if file is None:
        return "No file uploaded."
    src = Path(file.name)
    ext = src.suffix.lower()
    if ext not in ALLOWED_FILE_EXTS:
        return f"❌ Unsupported file type: `{ext}`"
    dest = LORA_DIR / src.name
    shutil.copy(src, dest)
    if image:
        img_ext = Path(image.name).suffix.lower()
        if img_ext in ALLOWED_IMG_EXTS:
            shutil.copy(image.name, LORA_IMG_DIR / (src.stem + img_ext))
    return f"βœ… Saved LoRA: `{dest.name}`"

# ===============================
# Listing functions
# ===============================
def list_uploaded_workflows():
    files = sorted(WORKFLOW_DIR.glob("*"))
    if not files:
        return "No uploaded workflows yet."
    md = ""
    for f in files:
        img_path = WORKFLOW_IMG_DIR / f"{f.stem}.png"
        if img_path.exists():
            md += f"### {f.name}\n![ref image]({img_path})\n---\n"
        else:
            md += f"### {f.name}\n(no image)\n---\n"
    return md


def list_uploaded_loras():
    files = sorted(LORA_DIR.glob("*"))
    if not files:
        return "No uploaded LoRAs yet."
    md = ""
    for f in files:
        img_path = LORA_IMG_DIR / f"{f.stem}.png"
        if img_path.exists():
            md += f"### {f.name}\n![ref image]({img_path})\n---\n"
        else:
            md += f"### {f.name}\n(no image)\n---\n"
    return md

# ===============================
# Hub browsing functions
# ===============================
def browse_loras(limit=20):
    models = list(list_models(
        tags=["lora"],
        sort="downloads",
        direction=-1,
        limit=limit,
    ))
    if not models:
        return "No LoRAs found."
    return "\n---\n".join(
        f"### {m.modelId}\n⬇️ {m.downloads or 0} | ⭐ {m.likes or 0}\nhttps://huggingface.co/{m.modelId}"
        for m in models
    )


def browse_workflows(limit=20):
    uploaded_files = sorted(WORKFLOW_DIR.glob("*"))
    uploaded_md = ""
    if uploaded_files:
        uploaded_md = "## πŸ§‘β€πŸ’» Uploaded Workflows\n\n" + "\n---\n".join(
            f"### {f.name}\nLocal upload"
            for f in uploaded_files
        )
    if list_spaces is None:
        remote_md = "Spaces API unavailable."
    else:
        spaces = list(list_spaces(
            sort="likes",
            direction=-1,
            limit=limit,
        ))
        workflows = [
            s for s in spaces
            if any(tag in (s.tags or []) for tag in ["comfyui", "workflow", "pipeline", "diffusers"])
        ]
        if workflows:
            remote_md = "## 🌍 Community Workflows\n\n" + "\n---\n".join(
                f"### {s.id}\n⭐ {s.likes or 0}\nhttps://huggingface.co/spaces/{s.id}"
                for s in workflows[:limit]
            )
        else:
            remote_md = "No community workflows found."
    return uploaded_md + "\n\n" + remote_md

# ===============================
# Gradio UI
# ===============================
with gr.Blocks() as demo:
    gr.Markdown("# πŸ€— Workflow & LoRA Browser")

    with gr.Tabs():
        # Workflows Tab
        with gr.Tab("βš™οΈ Workflows"):
            wf_out = gr.Markdown()
            gr.Button("Browse Workflows").click(
                browse_workflows,
                outputs=wf_out,
            )

        # LoRAs Tab
        with gr.Tab("🧩 LoRAs"):
            lora_out = gr.Markdown()
            gr.Button("Browse LoRAs").click(
                browse_loras,
                outputs=lora_out,
            )

        # Upload Workflow Tab (always visible, password-protected)
        with gr.Tab("⬆️ Upload Workflow"):
            gr.Markdown("Upload workflow files with optional reference image (.json/.yaml/.png). Owner only (password required).")
            wf_file = gr.File(label="Workflow file")
            wf_image = gr.File(label="Reference image (optional)")
            wf_password = gr.Textbox(label="Password", type="password")
            wf_status = gr.Markdown()
            gr.Button("Upload").click(
                upload_workflow,
                inputs=[wf_file, wf_image, wf_password],
                outputs=wf_status,
            )
            wf_list = gr.Markdown()
            gr.Button("Refresh Uploaded Workflows").click(
                list_uploaded_workflows,
                outputs=wf_list,
            )

        # Upload LoRA Tab (always visible, password-protected)
        with gr.Tab("⬆️ Upload LoRA"):
            gr.Markdown("Upload LoRA files with optional reference image (.safetensors/.ckpt/.png). Owner only (password required).")
            lora_file = gr.File(label="LoRA file")
            lora_image = gr.File(label="Reference image (optional)")
            lora_password = gr.Textbox(label="Password", type="password")
            lora_status = gr.Markdown()
            gr.Button("Upload").click(
                upload_lora,
                inputs=[lora_file, lora_image, lora_password],
                outputs=lora_status,
            )
            lora_list = gr.Markdown()
            gr.Button("Refresh Uploaded LoRAs").click(
                list_uploaded_loras,
                outputs=lora_list,
            )

demo.launch()