File size: 6,650 Bytes
40dafca
 
64a8c65
780f03f
 
 
 
 
 
 
 
40dafca
 
 
64a8c65
 
 
 
780f03f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64a8c65
780f03f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64a8c65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40dafca
64a8c65
 
 
 
 
40dafca
64a8c65
40dafca
64a8c65
 
 
40dafca
64a8c65
 
 
40dafca
780f03f
 
40dafca
64a8c65
 
 
 
40dafca
64a8c65
780f03f
 
 
40dafca
64a8c65
 
 
 
 
40dafca
 
64a8c65
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
#!/usr/bin/env python3
"""
HuggingFace Spaces main launcher for BytePlus Image Generation Studio

Alternative directory strategy for Spaces where the repository may not contain
the required folders (Generated/, static/, view_session/):

1) Create these folders under a persistent/writable base (prefer /data, fallback /tmp)
2) Symlink them into the working directory so the app can use relative paths

This works even when the repo itself doesn't track those directories.
"""

import os
import sys
from pathlib import Path
import importlib.util

def _is_writable(p: Path) -> bool:
    try:
        p.mkdir(parents=True, exist_ok=True)
        test_file = p / ".__writable_test__"
        test_file.write_text("ok")
        test_file.unlink(missing_ok=True)
        return True
    except Exception:
        return False

def _pick_persistent_base() -> Path:
    """Pick a persistent base directory on Spaces.
    - Prefer /data if available/writable (persisted across restarts)
    - Fallback to /tmp if /data is unavailable
    """
    candidates = [Path("/data/byteplus"), Path("/data"), Path("/tmp/byteplus")]
    for c in candidates:
        base = c if c.name == "byteplus" else c / "byteplus"
        if _is_writable(base):
            return base
    # Final fallback
    return Path("/tmp/byteplus")

def _ensure_dir(path: Path):
    path.mkdir(parents=True, exist_ok=True)
    try:
        path.chmod(0o755)
    except Exception:
        pass

def _safe_symlink(target: Path, link_path: Path):
    """Create/repair a symlink from link_path -> target.
    If link_path exists as a real dir, attempt to migrate contents, then replace with symlink.
    """
    try:
        if link_path.is_symlink() or link_path.exists():
            # If it's already the correct symlink, keep it
            try:
                if link_path.is_symlink() and link_path.resolve() == target.resolve():
                    return
            except Exception:
                pass

            if link_path.is_symlink():
                link_path.unlink()
            elif link_path.is_dir():
                # Migrate existing contents to target
                target.mkdir(parents=True, exist_ok=True)
                for item in link_path.iterdir():
                    dest = target / item.name
                    try:
                        if item.is_dir():
                            if dest.exists():
                                # Merge: move children
                                for sub in item.iterdir():
                                    sub.rename(dest / sub.name)
                                item.rmdir()
                            else:
                                item.rename(dest)
                        else:
                            if dest.exists():
                                dest.unlink()
                            item.rename(dest)
                    except Exception as e:
                        print(f"⚠️  Migration warning for {item}: {e}")
                # Now remove the original dir
                try:
                    link_path.rmdir()
                except Exception:
                    pass
            else:
                # Regular file: remove to replace with symlink
                link_path.unlink(missing_ok=True)

        # Ensure target exists before linking
        target.mkdir(parents=True, exist_ok=True)
        link_path.symlink_to(target, target_is_directory=True)
        print(f"πŸ”— Linked {link_path} -> {target}")
    except Exception as e:
        print(f"⚠️  Could not create symlink {link_path} -> {target}: {e}")
        # Fallback: ensure local directory exists
        try:
            _ensure_dir(link_path)
            print(f"πŸ“ Fallback: created local dir {link_path}")
        except Exception as ie:
            print(f"❌ Fallback failed for {link_path}: {ie}")

def ensure_persistent_dirs_and_symlinks():
    """Ensure required dirs exist in persistent storage and are symlinked into CWD."""
    print("πŸ—οΈ  Ensuring required directories via persistent storage and symlinks...")
    base = _pick_persistent_base()
    print(f"πŸ“¦ Using storage base: {base}")

    # Create persistent dirs
    gen_p = base / "Generated"
    static_p = base / "static"
    view_p = base / "view_session"

    for d in [gen_p, static_p, view_p, static_p / "css", static_p / "js", static_p / "images"]:
        _ensure_dir(d)

    # Symlink into working directory
    cwd = Path.cwd()
    _safe_symlink(gen_p, cwd / "Generated")
    _safe_symlink(static_p, cwd / "static")
    _safe_symlink(view_p, cwd / "view_session")

    print("🎯 Symlinked working directories are ready!")

def load_byteplus_app():
    """Load and return the BytePlus application."""
    try:
        # Load the BytePlus app module
        spec = importlib.util.spec_from_file_location("byteplus_app", "byteplus_app.py")
        byteplus_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(byteplus_module)
        
        # Try to find the Gradio app
        if hasattr(byteplus_module, 'demo'):
            return byteplus_module.demo
        elif hasattr(byteplus_module, 'app'):
            return byteplus_module.app
        elif hasattr(byteplus_module, 'interface'):
            return byteplus_module.interface
        else:
            # Look for any object with a launch method
            for attr_name in dir(byteplus_module):
                attr = getattr(byteplus_module, attr_name)
                if hasattr(attr, 'launch') and callable(attr.launch):
                    return attr
            
            raise ValueError("Could not find Gradio app in byteplus_app.py")
            
    except Exception as e:
        print(f"❌ Error loading BytePlus app: {e}")
        raise

def main():
    """Main function to setup and launch BytePlus."""
    print("πŸš€ Starting BytePlus Image Generation Studio for HuggingFace Spaces...")
    
    # Ensure persistent storage dirs exist and are symlinked into CWD
    ensure_persistent_dirs_and_symlinks()
    
    # Load and launch the BytePlus app
    try:
        print("πŸ“₯ Loading BytePlus Image Generation Studio...")
        demo = load_byteplus_app()
        
        print("πŸŽ‰ Launching BytePlus Image Generation Studio...")
        # Ensure proper binding for Spaces
        port = int(os.getenv("PORT", "7860"))
        demo.launch(server_name="0.0.0.0", server_port=port)
        
    except Exception as e:
        print(f"❌ Error launching BytePlus: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

if __name__ == "__main__":
    main()