File size: 8,447 Bytes
1e5eb40
1579e31
 
9cf5174
1579e31
 
5afbff9
0f09667
5afbff9
 
 
 
18d0b22
 
 
1579e31
 
 
9cf5174
30672bd
 
 
1579e31
9cf5174
30672bd
 
 
 
 
1579e31
9cf5174
67e6ea6
1579e31
 
 
116e0aa
 
5afbff9
 
 
 
 
 
 
 
 
 
 
b257fdf
1579e31
 
 
 
 
9cf5174
18d0b22
90bdafc
 
 
 
18d0b22
90bdafc
9cf5174
18d0b22
90bdafc
 
 
9cf5174
18d0b22
5afbff9
f554d83
90bdafc
5afbff9
 
 
18a9d30
 
7bc7863
46001b0
5afbff9
18a9d30
 
46001b0
 
 
9cf5174
46001b0
5afbff9
9cf5174
fd6b0f0
93abc19
9cf5174
 
 
30672bd
9cf5174
 
5afbff9
46001b0
9cf5174
18d0b22
90bdafc
f50d820
90bdafc
0f09667
 
 
 
90bdafc
b9b6447
90bdafc
cd2ceea
90bdafc
 
71a1f86
cd2ceea
 
 
 
b9b6447
dedeb27
b9b6447
c244294
b9b6447
 
 
 
454ba01
cd2ceea
8aaffcc
 
90bdafc
46001b0
18a9d30
46001b0
 
 
 
 
5afbff9
46001b0
 
 
 
90bdafc
18d0b22
90bdafc
 
 
 
 
 
 
 
 
 
46001b0
18a9d30
46001b0
 
 
 
9cf5174
46001b0
5afbff9
46001b0
 
 
90bdafc
18d0b22
90bdafc
 
 
18d0b22
 
 
 
 
 
 
e9dd265
 
46001b0
18a9d30
46001b0
 
 
 
9cf5174
46001b0
5afbff9
46001b0
 
 
8cc42bf
18d0b22
5afbff9
90bdafc
 
 
 
 
1579e31
1890cbb
9cf5174
1890cbb
 
18d0b22
b257fdf
1e5eb40
 
 
18d0b22
 
c08b935
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
210
211
212
213
214
215
from flask import Flask, request, jsonify, Response
import os
import json
from huggingface_hub import HfApi, create_repo, upload_file, login
import random
import string
import logging
import black

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Import documentation from assembler_docs.py
from assembler_docs import DOCUMENTATION

app = Flask(__name__)

# Hugging Face API token (set in Space settings)
HF_TOKEN = os.getenv("HF_TOKEN")
if not HF_TOKEN:
    raise ValueError("HF_TOKEN not set. Add it in Space settings.")

# Log in to Hugging Face Hub with the token
try:
    login(token=HF_TOKEN)
    logger.info("Successfully logged in to Hugging Face Hub")
except Exception as e:
    raise ValueError(f"Failed to log in to Hugging Face Hub: {str(e)}")

hf_api = HfApi()

def generate_space_name():
    """Generate a unique Space name."""
    random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
    #return f"GeneratedSpace-{random_suffix}"
    return "auto_space_1"
def verify_organization_access(organization: str, token: str) -> bool:
    """Verify if the token has write access to the organization."""
    try:
        # Try to list repositories or check organization membership
        repos = hf_api.list_repos(author=organization, token=token)
        logger.info(f"Verified access to organization {organization}")
        return True
    except Exception as e:
        logger.error(f"No write access to organization {organization}: {str(e)}")
        return False

@app.route('/create-space', methods=['POST'])
def create_hf_space():
    try:
        # Parse JSON input
        data = request.get_json()
        if not data:
            return jsonify({"error": "No JSON data provided"}), 400

        # Extract parameters
        space_type = data.get("space_type", "gradio")  # Default to gradio if not specified
        files = data.get("files", {})  # Dictionary of filename: content
        params = data.get("parameters", {})  # Optional parameters

        if not files:
            return jsonify({"error": "No files provided in JSON"}), 400

        # Validate space_type
        valid_space_types = ["gradio", "static", "docker", "streamlit"]
        if space_type not in valid_space_types:
            return jsonify({"error": f"Invalid space_type. Must be one of {valid_space_types}"}), 400

        # Organization namespace for new Spaces
        ORGANIZATION = "broadfield-dev"
        space_name = generate_space_name()
        full_repo_id = f"{ORGANIZATION}/{space_name}"

        # Verify token has access to Space-Share organization
        #if not verify_organization_access(ORGANIZATION, HF_TOKEN):
        #    return jsonify({"error": f"No write access to organization {ORGANIZATION}. Check your HF_TOKEN permissions."}), 403
        api=HfApi(endpoint="https://huggingface.co", token=HF_TOKEN)
        try:
            # Attempt to create the repository with explicit organization context
            repo_info = api.create_repo(
                repo_id=full_repo_id,
                repo_type="space",
                space_sdk=space_type,
                private=False,
                exist_ok=True  # Allow creation even if the repo might exist
            )
            logger.info(f"Repository created: {full_repo_id}")
        except Exception as e:
            #return jsonify({"error": f"Failed to create repository {full_repo_id}: {str(e)}"}), 500
            print(f"Failed to create repository {full_repo_id}: {str(e)}")

        # Verify repository existence before uploading
        try:
            repo_exists = hf_api.repo_exists(repo_id=full_repo_id, repo_type="space")
            if not repo_exists:
                return jsonify({"error": f"Repository {full_repo_id} does not exist after creation attempt."}), 500
            logger.info(f"Verified repository exists: {full_repo_id}")
        except Exception as e:
            return jsonify({"error": f"Failed to verify repository {full_repo_id}: {str(e)}"}), 500

        # Handle multi-file uploads
        print(str(files))
        for filename, content in files.items():
            #content_lines = content.split("\n")
            #content = ""
            #for line in content_lines:
            #    content += line + "\n"
            # Write content to a temporary file
            output=""
            with open(f"temp_{filename}", "w") as f:
                '''content=""
                if filename.endswith(".py"):
                    # Inject parameters into Python files if present
                    #content = f"PARAMS = {json.dumps(params)}\n\n{content}"
                    content_box = list(content.split("\n"))
                    for line in content_box:
                        f.write(line + "\n")
                else:'''
                print(content)
                content_box = list(content.replace("\\n", "\n").split("\n"))
                print(content_box)
                for line in content_box:
                    output+=line + "\n"
                    
                    
                f.write(output)
            f.close()
            
            #if filename.endswith(".py"):
            #    os.system(f"black temp_{filename}")
            # Upload to the new Space
            try:
                api.upload_file(
                    path_or_fileobj=f"temp_{filename}",
                    path_in_repo=filename,
                    repo_id=full_repo_id,
                    repo_type="space",
                )
                logger.info(f"Uploaded file: {filename}")
            except Exception as e:
                os.remove(f"temp_{filename}")
                return jsonify({"error": f"Failed to upload file {filename}: {str(e)}"}), 500

            os.remove(f"temp_{filename}")

        # Add requirements.txt if not provided (basic defaults)
        if "requirements.txt" not in files:
            default_requirements = {
                "gradio": "gradio",
                "static": "",
                "docker": "flask",  # Example; adjust based on needs
                "streamlit": "streamlit"
            }.get(space_type, "")
            with open("temp_requirements.txt", "w") as f:
                f.write(default_requirements)
            try:
                api.upload_file(
                    path_or_fileobj="temp_requirements.txt",
                    path_in_repo="requirements.txt",
                    repo_id=full_repo_id,
                    repo_type="space",
                    token=HF_TOKEN
                )
                logger.info("Uploaded requirements.txt")
            except Exception as e:
                os.remove("temp_requirements.txt")
                return jsonify({"error": f"Failed to upload requirements.txt: {str(e)}"}), 500
            os.remove("temp_requirements.txt")

        # Special handling for Docker Spaces
        if space_type == "docker" and "Dockerfile" not in files:
            default_dockerfile = """
FROM python:3.10-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 7860
CMD ["python", "app.py"]
"""
            with open("temp_Dockerfile", "w") as f:
                f.write(default_dockerfile)
            try:
                api.upload_file(
                    path_or_fileobj="temp_Dockerfile",
                    path_in_repo="Dockerfile",
                    repo_id=full_repo_id,
                    repo_type="space",
                    token=HF_TOKEN
                )
                logger.info("Uploaded Dockerfile")
            except Exception as e:
                os.remove("temp_Dockerfile")
                return jsonify({"error": f"Failed to upload Dockerfile: {str(e)}"}), 500
            os.remove("temp_Dockerfile")

        space_url = f"https://huggingface.co/spaces/{full_repo_id}"
        return jsonify({
            "message": "New Space created",
            "url": space_url,
            "note": "It may take a few minutes to build and deploy."
        }), 200

    except json.JSONDecodeError:
        return jsonify({"error": "Invalid JSON format"}), 400
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/docs', methods=['GET'])
def get_docs():
    """Return the API documentation as plain text."""
    return Response(DOCUMENTATION, mimetype='text/plain'), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=7860)