Spaces:
Running
Running
import json | |
import requests | |
from flask import Flask, render_template_string, request, jsonify, redirect, url_for | |
import secrets | |
app = Flask(__name__) | |
app.secret_key = secrets.token_hex(16) | |
# 簡易的なOAuth状態管理 | |
oauth_state = {} | |
def index(): | |
if request.method == "POST": | |
# OAuthコールバック処理 | |
if "code" in request.args and "state" in request.args: | |
state = request.args["state"] | |
if state not in oauth_state: | |
return "無効な状態トークン", 400 | |
code = request.args["code"] | |
# 実際にはここでHugging FaceのOAuthトークンエンドポイントにリクエストを送信 | |
# デモ用に簡略化 | |
token = f"oauth-token-{code}" # 実際には取得したトークンを使用 | |
return render_index(token=token) | |
# スペース作成処理 | |
token = request.form.get("token") | |
if not token: | |
return "トークンが提供されていません", 400 | |
space_name = request.form["space_name"] | |
github_url = request.form["github_url"] | |
github_token = request.form.get("github_token") | |
private = request.form.get("private") == "on" | |
sdk = request.form["sdk"] | |
description = request.form["description"] | |
license_ = request.form["license"] | |
port = request.form["port"] | |
title = request.form["title"] | |
emoji = request.form["emoji"] | |
pinned = request.form.get("pinned") == "on" | |
if not github_url.startswith("https://"): | |
github_url = f"https://github.com/{github_url}" | |
headers = {"Authorization": f"Bearer {token}"} | |
user_info = requests.get("https://huggingface.co/api/whoami-v2", headers=headers).json() | |
username = user_info["name"] | |
api_url = f"https://huggingface.co/api/spaces/{username}/{space_name}" | |
payload = { | |
"sdk": sdk, | |
"private": private, | |
"title": title, | |
"emoji": emoji, | |
"pinned": pinned, | |
"license": license_, | |
"app_port": port, | |
"description": description, | |
"git": github_url | |
} | |
if github_token: | |
payload["secrets"] = {"GITHUB_TOKEN": github_token} | |
json_data = json.dumps(payload, ensure_ascii=False).encode("utf-8") | |
response = requests.put( | |
api_url, | |
headers={ | |
"Authorization": f"Bearer {token}", | |
"Content-Type": "application/json" | |
}, | |
data=json_data | |
) | |
try: | |
return jsonify(response.json()) | |
except requests.exceptions.JSONDecodeError: | |
return f"エラー内容(JSON形式でない可能性): {response.text}", response.status_code | |
return render_index() | |
def render_index(token=None): | |
return render_template_string(''' | |
<!DOCTYPE html> | |
<html lang="ja"> | |
<head> | |
<meta charset="UTF-8" /> | |
<title>HuggingFace Space 作成フォーム</title> | |
<style> | |
.tab { | |
overflow: hidden; | |
border: 1px solid #ccc; | |
background-color: #f1f1f1; | |
border-radius: 4px 4px 0 0; | |
} | |
.tab button { | |
background-color: inherit; | |
float: left; | |
border: none; | |
outline: none; | |
cursor: pointer; | |
padding: 10px 16px; | |
transition: 0.3s; | |
} | |
.tab button:hover { | |
background-color: #ddd; | |
} | |
.tab button.active { | |
background-color: #fff; | |
border-bottom: 2px solid #4CAF50; | |
} | |
.tabcontent { | |
display: none; | |
padding: 20px; | |
border: 1px solid #ccc; | |
border-top: none; | |
border-radius: 0 0 4px 4px; | |
} | |
.tabcontent.active { | |
display: block; | |
} | |
.form-group { | |
margin-bottom: 15px; | |
} | |
label { | |
display: block; | |
margin-bottom: 5px; | |
font-weight: bold; | |
} | |
input, select { | |
width: 100%; | |
padding: 8px; | |
box-sizing: border-box; | |
border: 1px solid #ddd; | |
border-radius: 4px; | |
} | |
button[type="submit"] { | |
background-color: #4CAF50; | |
color: white; | |
padding: 10px 15px; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
} | |
button[type="submit"]:hover { | |
background-color: #45a049; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Hugging Face Space 作成</h1> | |
<div class="tab"> | |
<button class="tablinks active" onclick="openTab(event, 'tokenTab')">トークン入力</button> | |
<button class="tablinks" onclick="openTab(event, 'oauthTab')">Hugging Faceでログイン</button> | |
</div> | |
<form method="POST"> | |
<div id="tokenTab" class="tabcontent active"> | |
<div class="form-group"> | |
<label for="token">Hugging Face 書き込みトークン:</label> | |
<input type="password" name="token" id="token" required> | |
</div> | |
</div> | |
<div id="oauthTab" class="tabcontent"> | |
<div class="form-group"> | |
<p>Hugging FaceのOAuthを使用してログインします。</p> | |
<button type="button" id="oauthLogin" onclick="startOAuth()">Hugging Faceでログイン</button> | |
</div> | |
</div> | |
<div class="form-group"> | |
<label for="space_name">スペース名:</label> | |
<input type="text" name="space_name" id="space_name" required> | |
</div> | |
<div class="form-group"> | |
<label for="github_url">GitHub リポジトリURL または ID:</label> | |
<input type="text" name="github_url" id="github_url" required> | |
</div> | |
<div class="form-group"> | |
<label for="github_token">GitHub トークン (必要な場合):</label> | |
<input type="password" name="github_token" id="github_token"> | |
</div> | |
<div class="form-group"> | |
<label> | |
<input type="checkbox" name="private" id="private"> | |
非公開にする | |
</label> | |
</div> | |
<div class="form-group"> | |
<label for="sdk">SDK:</label> | |
<select name="sdk" id="sdk"> | |
<option value="gradio">Gradio</option> | |
<option value="streamlit">Streamlit</option> | |
<option value="static">Static</option> | |
<option value="docker">Docker</option> | |
</select> | |
</div> | |
<div class="form-group"> | |
<label for="description">説明:</label> | |
<input type="text" name="description" id="description"> | |
</div> | |
<div class="form-group"> | |
<label for="license">ライセンス:</label> | |
<input type="text" name="license" id="license" placeholder="e.g. apache-2.0"> | |
</div> | |
<div class="form-group"> | |
<label for="port">アプリのポート:</label> | |
<input type="text" name="port" id="port" placeholder="例: 7860"> | |
</div> | |
<div class="form-group"> | |
<label for="title">スペースの名前 (表示用):</label> | |
<input type="text" name="title" id="title"> | |
</div> | |
<div class="form-group"> | |
<label for="emoji">絵文字 (表示アイコン):</label> | |
<input type="text" name="emoji" id="emoji" placeholder="例: 🚀"> | |
</div> | |
<div class="form-group"> | |
<label> | |
<input type="checkbox" name="pinned" id="pinned"> | |
固定 (ピン止め) | |
</label> | |
</div> | |
<button type="submit">スペースを作成</button> | |
</form> | |
<script> | |
function openTab(evt, tabName) { | |
const tabcontent = document.getElementsByClassName("tabcontent"); | |
for (let i = 0; i < tabcontent.length; i++) { | |
tabcontent[i].classList.remove("active"); | |
} | |
const tablinks = document.getElementsByClassName("tablinks"); | |
for (let i = 0; i < tablinks.length; i++) { | |
tablinks[i].classList.remove("active"); | |
} | |
document.getElementById(tabName).classList.add("active"); | |
evt.currentTarget.classList.add("active"); | |
} | |
function startOAuth() { | |
// 実際のアプリではHugging FaceのOAuthエンドポイントを使用 | |
// デモ用に簡略化 | |
const state = Math.random().toString(36).substring(2); | |
window.location.href = `https://huggingface.co/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(window.location.origin)}&scope=read-repos,write-repos&state=${state}`; | |
} | |
document.addEventListener("DOMContentLoaded", () => { | |
const tokenInput = document.getElementById("token"); | |
// URLからOAuthトークンを取得 (デモ用) | |
const urlParams = new URLSearchParams(window.location.search); | |
const oauthToken = urlParams.get('oauth_token'); | |
if (oauthToken) { | |
tokenInput.value = oauthToken; | |
} else { | |
// 保存されたトークンを自動で読み込み | |
tokenInput.value = localStorage.getItem("hf_token") || ""; | |
} | |
tokenInput.addEventListener("input", () => { | |
localStorage.setItem("hf_token", tokenInput.value); | |
}); | |
}); | |
</script> | |
</body> | |
</html> | |
''', token=token) | |
if __name__ == "__main__": | |
app.run(host="0.0.0.0", port=7860) |