Spaces:
Runtime error
Runtime error
Jose Benitez
commited on
Commit
·
bbc89f6
0
Parent(s):
wip
Browse files- .env.example +8 -0
- .gitignore +2 -0
- Dockerfile +9 -0
- README.md +3 -0
- assets/logo.jpg +0 -0
- auth.py +12 -0
- config.py +18 -0
- database.py +45 -0
- gradio_app.py +266 -0
- main.py +31 -0
- models.py +11 -0
- routes.py +154 -0
- services/get_stripe.py +2 -0
- services/image_generation.py +21 -0
- services/train_lora.py +46 -0
- static/css/login.css +76 -0
- static/css/main.css +60 -0
- static/html/landing.html +189 -0
- static/html/main_header.html +82 -0
- utils/image_utils.py +23 -0
- utils/stripe_utils.py +44 -0
.env.example
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
REPLICATE_API_TOKEN=
|
2 |
+
|
3 |
+
SUPABASE_KEY=
|
4 |
+
SUPABASE_URL=
|
5 |
+
|
6 |
+
GOOGLE_CLIENT_ID=
|
7 |
+
GOOGLE_CLIENT_SECRET=
|
8 |
+
SECRET_KEY=
|
.gitignore
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
__pycache__
|
2 |
+
.env
|
Dockerfile
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.8-slim
|
2 |
+
|
3 |
+
WORKDIR /usr/src/app
|
4 |
+
COPY . .
|
5 |
+
RUN pip install --no-cache-dir gradio
|
6 |
+
EXPOSE 7860
|
7 |
+
ENV GRADIO_SERVER_NAME="0.0.0.0"
|
8 |
+
|
9 |
+
CMD ["python", "app.py"]
|
README.md
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
test card
|
2 |
+
4242 4242 4242 4242
|
3 |
+
wip
|
assets/logo.jpg
ADDED
![]() |
auth.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from starlette.config import Config
|
2 |
+
from authlib.integrations.starlette_client import OAuth
|
3 |
+
from config import GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
|
4 |
+
|
5 |
+
config_data = {'GOOGLE_CLIENT_ID': GOOGLE_CLIENT_ID, 'GOOGLE_CLIENT_SECRET': GOOGLE_CLIENT_SECRET}
|
6 |
+
starlette_config = Config(environ=config_data)
|
7 |
+
oauth = OAuth(starlette_config)
|
8 |
+
oauth.register(
|
9 |
+
name='google',
|
10 |
+
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
|
11 |
+
client_kwargs={'scope': 'openid email profile'},
|
12 |
+
)
|
config.py
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from dotenv import load_dotenv
|
3 |
+
import logging
|
4 |
+
|
5 |
+
load_dotenv()
|
6 |
+
|
7 |
+
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
8 |
+
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
9 |
+
SECRET_KEY = os.getenv("SECRET_KEY")
|
10 |
+
|
11 |
+
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
|
12 |
+
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
|
13 |
+
|
14 |
+
STRIPE_API_KEY = os.getenv("STRIPE_API_KEY")
|
15 |
+
STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET")
|
16 |
+
|
17 |
+
logging.basicConfig(level=logging.INFO)
|
18 |
+
logger = logging.getLogger(__name__)
|
database.py
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
from supabase import create_client, Client
|
3 |
+
from config import SUPABASE_URL, SUPABASE_KEY
|
4 |
+
|
5 |
+
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
6 |
+
|
7 |
+
def get_user_credits(user_id):
|
8 |
+
user = supabase.table("users").select("generation_credits, train_credits").eq("id", user_id).execute()
|
9 |
+
if user.data:
|
10 |
+
return user.data[0]["generation_credits"], user.data[0]["train_credits"]
|
11 |
+
return 0, 0
|
12 |
+
|
13 |
+
def update_user_credits(user_id, generation_credits, train_credits):
|
14 |
+
supabase.table("users").update({
|
15 |
+
"generation_credits": generation_credits,
|
16 |
+
"train_credits": train_credits
|
17 |
+
}).eq("id", user_id).execute()
|
18 |
+
|
19 |
+
def get_or_create_user(google_id, email, name, given_name, profile_picture):
|
20 |
+
user = supabase.table("users").select("*").eq("google_id", google_id).execute()
|
21 |
+
|
22 |
+
if not user.data:
|
23 |
+
new_user = {
|
24 |
+
"google_id": google_id,
|
25 |
+
"email": email,
|
26 |
+
"name": name,
|
27 |
+
"profile_picture": profile_picture,
|
28 |
+
"generation_credits": 2,
|
29 |
+
"train_credits": 1,
|
30 |
+
"given_name": given_name
|
31 |
+
}
|
32 |
+
result = supabase.table("users").insert(new_user).execute()
|
33 |
+
return result.data[0]
|
34 |
+
else:
|
35 |
+
return user.data[0]
|
36 |
+
|
37 |
+
def get_lora_models_info():
|
38 |
+
lora_models = supabase.table("lora_models").select("*").execute()
|
39 |
+
return lora_models.data
|
40 |
+
|
41 |
+
def get_user_by_id(user_id):
|
42 |
+
user = supabase.table("users").select("*").eq("id", user_id).execute()
|
43 |
+
if user.data:
|
44 |
+
return user.data[0]
|
45 |
+
return None
|
gradio_app.py
ADDED
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
|
3 |
+
import os
|
4 |
+
import json
|
5 |
+
import zipfile
|
6 |
+
from pathlib import Path
|
7 |
+
|
8 |
+
from database import get_user_credits, update_user_credits, get_lora_models_info
|
9 |
+
from services.image_generation import generate_image
|
10 |
+
from services.train_lora import lora_pipeline
|
11 |
+
from utils.image_utils import url_to_pil_image
|
12 |
+
|
13 |
+
lora_models = get_lora_models_info()
|
14 |
+
|
15 |
+
|
16 |
+
if not isinstance(lora_models, list):
|
17 |
+
raise ValueError("Expected loras_models to be a list of dictionaries.")
|
18 |
+
|
19 |
+
login_css_path = Path(__file__).parent / 'static/css/login.css'
|
20 |
+
main_css_path = Path(__file__).parent / 'static/css/main.css'
|
21 |
+
landing_html_path = Path(__file__).parent / 'static/html/landing.html'
|
22 |
+
main_header_path = Path(__file__).parent / 'static/html/main_header.html'
|
23 |
+
|
24 |
+
if login_css_path.is_file(): # Check if the file exists
|
25 |
+
with login_css_path.open() as file:
|
26 |
+
login_css = file.read()
|
27 |
+
|
28 |
+
if main_css_path.is_file(): # Check if the file exists
|
29 |
+
with main_css_path.open() as file:
|
30 |
+
main_css = file.read()
|
31 |
+
|
32 |
+
if landing_html_path.is_file():
|
33 |
+
with landing_html_path.open() as file:
|
34 |
+
landin_page = file.read()
|
35 |
+
|
36 |
+
if main_header_path.is_file():
|
37 |
+
with main_header_path.open() as file:
|
38 |
+
main_header = file.read()
|
39 |
+
|
40 |
+
def update_selection(evt: gr.SelectData, width, height):
|
41 |
+
selected_lora = lora_models[evt.index]
|
42 |
+
new_placeholder = f"Ingresa un prompt para tu modelo {selected_lora['lora_name']}"
|
43 |
+
trigger_word = selected_lora["trigger_word"]
|
44 |
+
updated_text = f"#### Palabra clave: {trigger_word} ✨"
|
45 |
+
|
46 |
+
if "aspect" in selected_lora:
|
47 |
+
if selected_lora["aspect"] == "portrait":
|
48 |
+
width, height = 768, 1024
|
49 |
+
elif selected_lora["aspect"] == "landscape":
|
50 |
+
width, height = 1024, 768
|
51 |
+
|
52 |
+
return gr.update(placeholder=new_placeholder), updated_text, evt.index, width, height
|
53 |
+
|
54 |
+
def compress_and_train(files, model_name, trigger_word, train_steps, lora_rank, batch_size, learning_rate):
|
55 |
+
if not files:
|
56 |
+
return "No images uploaded. Please upload images before training."
|
57 |
+
|
58 |
+
# Create a directory in the user's home folder
|
59 |
+
output_dir = os.path.expanduser("~/gradio_training_data")
|
60 |
+
os.makedirs(output_dir, exist_ok=True)
|
61 |
+
|
62 |
+
# Create a zip file in the output directory
|
63 |
+
zip_path = os.path.join(output_dir, "training_data.zip")
|
64 |
+
|
65 |
+
with zipfile.ZipFile(zip_path, 'w') as zipf:
|
66 |
+
for file_info in files:
|
67 |
+
file_path = file_info[0] # The first element of the tuple is the file path
|
68 |
+
file_name = os.path.basename(file_path)
|
69 |
+
zipf.write(file_path, file_name)
|
70 |
+
|
71 |
+
print(f"Zip file created at: {zip_path}")
|
72 |
+
|
73 |
+
print(f'[INFO] Procesando {trigger_word}')
|
74 |
+
# Now call the train_lora function with the zip file path
|
75 |
+
result = lora_pipeline(zip_path,
|
76 |
+
model_name,
|
77 |
+
trigger_word=trigger_word,
|
78 |
+
steps=train_steps,
|
79 |
+
lora_rank=lora_rank,
|
80 |
+
batch_size=batch_size,
|
81 |
+
autocaption=True,
|
82 |
+
learning_rate=learning_rate)
|
83 |
+
|
84 |
+
return f"{result}\n\nZip file saved at: {zip_path}"
|
85 |
+
|
86 |
+
def run_lora(request: gr.Request, prompt, cfg_scale, steps, selected_index, randomize_seed, width, height, lora_scale, progress=gr.Progress(track_tqdm=True)):
|
87 |
+
user = request.session.get('user')
|
88 |
+
if not user:
|
89 |
+
raise gr.Error("User not authenticated. Please log in.")
|
90 |
+
|
91 |
+
generation_credits, _ = get_user_credits(user['id'])
|
92 |
+
|
93 |
+
if generation_credits <= 0:
|
94 |
+
raise gr.Error("Ya no tienes creditos disponibles. Compra para continuar.")
|
95 |
+
|
96 |
+
image_url = generate_image(prompt, steps, cfg_scale, width, height, lora_scale, progress)
|
97 |
+
image = url_to_pil_image(image_url)
|
98 |
+
|
99 |
+
# Update user's credits
|
100 |
+
new_generation_credits = generation_credits - 1
|
101 |
+
update_user_credits(user['id'], new_generation_credits, user['train_credits'])
|
102 |
+
|
103 |
+
# Update session data
|
104 |
+
user['generation_credits'] = new_generation_credits
|
105 |
+
request.session['user'] = user
|
106 |
+
|
107 |
+
print(f"Generation credits remaining: {new_generation_credits}")
|
108 |
+
|
109 |
+
return image, new_generation_credits
|
110 |
+
|
111 |
+
def display_credits(request: gr.Request):
|
112 |
+
user = request.session.get('user')
|
113 |
+
if user:
|
114 |
+
generation_credits, train_credits = get_user_credits(user['id'])
|
115 |
+
return generation_credits, train_credits
|
116 |
+
return 0, 0
|
117 |
+
|
118 |
+
def load_greet_and_credits(request: gr.Request):
|
119 |
+
greeting = greet(request)
|
120 |
+
generation_credits, train_credits = display_credits(request)
|
121 |
+
return greeting, generation_credits, train_credits
|
122 |
+
|
123 |
+
def greet(request: gr.Request):
|
124 |
+
user = request.session.get('user')
|
125 |
+
if user:
|
126 |
+
with gr.Column():
|
127 |
+
with gr.Row():
|
128 |
+
greeting = f"Hola 👋 {user['given_name']}!"
|
129 |
+
return f"{greeting}\n"
|
130 |
+
return "OBTU AI. Please log in."
|
131 |
+
|
132 |
+
with gr.Blocks(theme=gr.themes.Soft(), css=login_css) as login_demo:
|
133 |
+
with gr.Column(elem_id="google-btn-container", elem_classes="google-btn-container svelte-vt1mxs gap"):
|
134 |
+
btn = gr.Button("Iniciar Sesion con Google", elem_classes="login-with-google-btn")
|
135 |
+
_js_redirect = """
|
136 |
+
() => {
|
137 |
+
url = '/login' + window.location.search;
|
138 |
+
window.open(url, '_blank');
|
139 |
+
}
|
140 |
+
"""
|
141 |
+
btn.click(None, js=_js_redirect)
|
142 |
+
gr.HTML(landin_page)
|
143 |
+
|
144 |
+
|
145 |
+
header = '<script src="https://cdn.lordicon.com/lordicon.js"></script>'
|
146 |
+
|
147 |
+
with gr.Blocks(theme=gr.themes.Soft(), head=header, css=main_css) as main_demo:
|
148 |
+
title = gr.HTML(main_header)
|
149 |
+
|
150 |
+
with gr.Column(elem_id="logout-btn-container"):
|
151 |
+
gr.Button("Salir", link="/logout", elem_id="logout_btn")
|
152 |
+
|
153 |
+
|
154 |
+
greetings = gr.Markdown("Loading user information...")
|
155 |
+
gr.Button("Comprar Creditos", link="/buy_credits", elem_id="buy_credits_btn")
|
156 |
+
|
157 |
+
selected_index = gr.State(None)
|
158 |
+
|
159 |
+
with gr.Row():
|
160 |
+
with gr.Column():
|
161 |
+
generation_credits_display = gr.Number(label="Generation Credits", precision=0, interactive=False)
|
162 |
+
with gr.Column():
|
163 |
+
train_credits_display = gr.Number(label="Training Credits", precision=0, interactive=False)
|
164 |
+
|
165 |
+
|
166 |
+
with gr.Tabs():
|
167 |
+
with gr.TabItem('Generacion'):
|
168 |
+
with gr.Row():
|
169 |
+
with gr.Column(scale=3):
|
170 |
+
prompt = gr.Textbox(label="Prompt", lines=1, placeholder="Ingresa un prompt para empezar a crear")
|
171 |
+
with gr.Column(scale=1, elem_id="gen_column"):
|
172 |
+
generate_button = gr.Button("Generate", variant="primary", elem_id="gen_btn")
|
173 |
+
|
174 |
+
with gr.Row():
|
175 |
+
with gr.Column(scale=4):
|
176 |
+
result = gr.Image(label="Imagen Generada")
|
177 |
+
|
178 |
+
with gr.Column(scale=3):
|
179 |
+
with gr.Accordion("Tus Modelos"):
|
180 |
+
user_model_gallery = gr.Gallery(
|
181 |
+
label="Galeria de Modelos",
|
182 |
+
allow_preview=False,
|
183 |
+
columns=3,
|
184 |
+
elem_id="galley"
|
185 |
+
)
|
186 |
+
|
187 |
+
with gr.Accordion("Modelos Publicos", open=False):
|
188 |
+
selected_info = gr.Markdown("")
|
189 |
+
gallery = gr.Gallery(
|
190 |
+
[(item["image_url"], item["lora_name"]) for item in lora_models],
|
191 |
+
label="Galeria de Modelos Publicos",
|
192 |
+
allow_preview=False,
|
193 |
+
columns=3,
|
194 |
+
elem_id="gallery"
|
195 |
+
)
|
196 |
+
|
197 |
+
|
198 |
+
with gr.Accordion("Configuracion Avanzada", open=False):
|
199 |
+
with gr.Row():
|
200 |
+
cfg_scale = gr.Slider(label="CFG Scale", minimum=1, maximum=20, step=0.5, value=3.5)
|
201 |
+
steps = gr.Slider(label="Steps", minimum=1, maximum=50, step=1, value=28)
|
202 |
+
with gr.Row():
|
203 |
+
width = gr.Slider(label="Width", minimum=256, maximum=1536, step=64, value=1024)
|
204 |
+
height = gr.Slider(label="Height", minimum=256, maximum=1536, step=64, value=1024)
|
205 |
+
with gr.Row():
|
206 |
+
randomize_seed = gr.Checkbox(True, label="Randomize seed")
|
207 |
+
lora_scale = gr.Slider(label="LoRA Scale", minimum=0, maximum=1, step=0.01, value=0.95)
|
208 |
+
|
209 |
+
gallery.select(
|
210 |
+
update_selection,
|
211 |
+
inputs=[width, height],
|
212 |
+
outputs=[prompt, selected_info, selected_index, width, height]
|
213 |
+
)
|
214 |
+
|
215 |
+
gr.on(
|
216 |
+
triggers=[generate_button.click, prompt.submit],
|
217 |
+
fn=run_lora,
|
218 |
+
inputs=[prompt, cfg_scale, steps, selected_index, randomize_seed, width, height, lora_scale],
|
219 |
+
outputs=[result, generation_credits_display]
|
220 |
+
)
|
221 |
+
|
222 |
+
with gr.TabItem("Training"):
|
223 |
+
gr.Markdown("# Entrena tu propio modelo 🧠")
|
224 |
+
gr.Markdown("En esta seccion podes entrenar tu propio modelo a partir de tus imagenes.")
|
225 |
+
with gr.Row():
|
226 |
+
with gr.Column():
|
227 |
+
train_dataset = gr.Gallery(columns=4, interactive=True, label="Tus Imagenes")
|
228 |
+
model_name = gr.Textbox(label="Nombre del Modelo",)
|
229 |
+
trigger_word = gr.Textbox(label="Palabra clave",
|
230 |
+
info="Esta seria una palabra clave para luego indicar al modelo cuando debe usar estas nuevas capacidad es que le vamos a ensenar",
|
231 |
+
)
|
232 |
+
train_button = gr.Button("Comenzar Training")
|
233 |
+
with gr.Accordion("Configuracion Avanzada", open=False):
|
234 |
+
train_steps = gr.Slider(label="Training Steps", minimum=100, maximum=10000, step=100, value=1000)
|
235 |
+
lora_rank = gr.Number(label='lora_rank', value=16)
|
236 |
+
batch_size = gr.Number(label='batch_size', value=1)
|
237 |
+
learning_rate = gr.Number(label='learning_rate', value=0.0004)
|
238 |
+
training_status = gr.Textbox(label="Training Status")
|
239 |
+
|
240 |
+
|
241 |
+
|
242 |
+
train_button.click(
|
243 |
+
compress_and_train,
|
244 |
+
inputs=[train_dataset, model_name, trigger_word, train_steps, lora_rank, batch_size, learning_rate],
|
245 |
+
outputs=training_status
|
246 |
+
)
|
247 |
+
|
248 |
+
|
249 |
+
#main_demo.load(greet, None, title)
|
250 |
+
#main_demo.load(greet, None, greetings)
|
251 |
+
#main_demo.load((greet, display_credits), None, [greetings, generation_credits_display, train_credits_display])
|
252 |
+
main_demo.load(load_greet_and_credits, None, [greetings, generation_credits_display, train_credits_display])
|
253 |
+
|
254 |
+
|
255 |
+
|
256 |
+
# TODO:
|
257 |
+
'''
|
258 |
+
- Galeria Modelos Propios (si existe alguno del user, si no, mostrar un mensaje para entrenar)
|
259 |
+
- Galeria Modelos Open Source (accordion)
|
260 |
+
- Training con creditos.
|
261 |
+
- Stripe(?)
|
262 |
+
- Mejorar boton de login/logout
|
263 |
+
- Retoque landing page
|
264 |
+
'''
|
265 |
+
|
266 |
+
|
main.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import uvicorn
|
2 |
+
from fastapi import FastAPI
|
3 |
+
from fastapi.staticfiles import StaticFiles
|
4 |
+
from starlette.middleware.sessions import SessionMiddleware
|
5 |
+
from config import SECRET_KEY
|
6 |
+
from routes import router, get_user
|
7 |
+
from gradio_app import login_demo, main_demo
|
8 |
+
import gradio as gr
|
9 |
+
from pathlib import Path
|
10 |
+
from fastapi.middleware.cors import CORSMiddleware
|
11 |
+
|
12 |
+
app = FastAPI()
|
13 |
+
|
14 |
+
login_demo.queue()
|
15 |
+
main_demo.queue()
|
16 |
+
|
17 |
+
static_dir = Path("./static")
|
18 |
+
app.mount("/static", StaticFiles(directory=static_dir, html=True), name="static")
|
19 |
+
#app.mount("/assets", StaticFiles(directory="assets", html=True), name="assets")
|
20 |
+
|
21 |
+
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
|
22 |
+
|
23 |
+
app.include_router(router)
|
24 |
+
|
25 |
+
app = gr.mount_gradio_app(app, login_demo, path="/main")
|
26 |
+
app = gr.mount_gradio_app(app, main_demo, path="/gradio", auth_dependency=get_user, show_error=True)
|
27 |
+
|
28 |
+
if __name__ == "__main__":
|
29 |
+
uvicorn.run(app)
|
30 |
+
|
31 |
+
|
models.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
|
3 |
+
class User(BaseModel):
|
4 |
+
id: str
|
5 |
+
google_id: str
|
6 |
+
email: str
|
7 |
+
name: str
|
8 |
+
given_name: str
|
9 |
+
profile_picture: str
|
10 |
+
generation_credits: int
|
11 |
+
train_credits: int
|
routes.py
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# routes.py
|
2 |
+
from fastapi import APIRouter, Depends, Request
|
3 |
+
from starlette.responses import RedirectResponse
|
4 |
+
from auth import oauth
|
5 |
+
from database import get_or_create_user, update_user_credits, get_user_by_id
|
6 |
+
from authlib.integrations.starlette_client import OAuthError
|
7 |
+
import gradio as gr
|
8 |
+
from utils.stripe_utils import create_checkout_session, verify_webhook, retrieve_stripe_session
|
9 |
+
|
10 |
+
router = APIRouter()
|
11 |
+
|
12 |
+
def get_user(request: Request):
|
13 |
+
user = request.session.get('user')
|
14 |
+
return user['name'] if user else None
|
15 |
+
|
16 |
+
@router.get('/')
|
17 |
+
def public(request: Request, user = Depends(get_user)):
|
18 |
+
root_url = gr.route_utils.get_root_url(request, "/", None)
|
19 |
+
print(f'Root URL: {root_url}')
|
20 |
+
if user:
|
21 |
+
return RedirectResponse(url=f'{root_url}/gradio/')
|
22 |
+
else:
|
23 |
+
return RedirectResponse(url=f'{root_url}/main/')
|
24 |
+
|
25 |
+
@router.route('/logout')
|
26 |
+
async def logout(request: Request):
|
27 |
+
request.session.pop('user', None)
|
28 |
+
return RedirectResponse(url='/')
|
29 |
+
|
30 |
+
@router.route('/login')
|
31 |
+
async def login(request: Request):
|
32 |
+
root_url = gr.route_utils.get_root_url(request, "/login", None)
|
33 |
+
redirect_uri = f"{root_url}/auth"
|
34 |
+
return await oauth.google.authorize_redirect(request, redirect_uri)
|
35 |
+
|
36 |
+
@router.route('/auth')
|
37 |
+
async def auth(request: Request):
|
38 |
+
try:
|
39 |
+
token = await oauth.google.authorize_access_token(request)
|
40 |
+
user_info = token.get('userinfo')
|
41 |
+
if user_info:
|
42 |
+
google_id = user_info['sub']
|
43 |
+
email = user_info['email']
|
44 |
+
name = user_info['name']
|
45 |
+
given_name = user_info['given_name']
|
46 |
+
profile_picture = user_info.get('picture', '')
|
47 |
+
|
48 |
+
user = get_or_create_user(google_id, email, name, given_name, profile_picture)
|
49 |
+
request.session['user'] = user
|
50 |
+
|
51 |
+
return RedirectResponse(url='/gradio')
|
52 |
+
else:
|
53 |
+
return RedirectResponse(url='/main')
|
54 |
+
except OAuthError as e:
|
55 |
+
print(f"OAuth Error: {str(e)}")
|
56 |
+
return RedirectResponse(url='/main')
|
57 |
+
|
58 |
+
# Handle Stripe payments
|
59 |
+
@router.get("/buy_credits")
|
60 |
+
async def buy_credits(request: Request):
|
61 |
+
user = request.session.get('user')
|
62 |
+
if not user:
|
63 |
+
return {"error": "User not authenticated"}
|
64 |
+
session = create_checkout_session(100, 50, user['id']) # $1 for 50 credits
|
65 |
+
|
66 |
+
# Store the session ID and user ID in the session
|
67 |
+
request.session['stripe_session_id'] = session['id']
|
68 |
+
request.session['user_id'] = user['id']
|
69 |
+
print(f"Stripe session created: {session['id']} for user {user['id']}")
|
70 |
+
|
71 |
+
return RedirectResponse(session['url'])
|
72 |
+
|
73 |
+
@router.post("/webhook")
|
74 |
+
async def stripe_webhook(request: Request):
|
75 |
+
payload = await request.body()
|
76 |
+
sig_header = request.headers.get("Stripe-Signature")
|
77 |
+
|
78 |
+
event = verify_webhook(payload, sig_header)
|
79 |
+
|
80 |
+
if event is None:
|
81 |
+
return {"error": "Invalid payload or signature"}
|
82 |
+
|
83 |
+
if event['type'] == 'checkout.session.completed':
|
84 |
+
session = event['data']['object']
|
85 |
+
user_id = session.get('client_reference_id')
|
86 |
+
|
87 |
+
if user_id:
|
88 |
+
# Fetch the user from the database
|
89 |
+
user = get_user_by_id(user_id) # You'll need to implement this function
|
90 |
+
if user:
|
91 |
+
# Update user's credits
|
92 |
+
new_credits = user['generation_credits'] + 50 # Assuming 50 credits were purchased
|
93 |
+
update_user_credits(user['id'], new_credits, user['train_credits'])
|
94 |
+
print(f"Credits updated for user {user['id']}")
|
95 |
+
else:
|
96 |
+
print(f"User not found for ID: {user_id}")
|
97 |
+
else:
|
98 |
+
print("No client_reference_id found in the session")
|
99 |
+
|
100 |
+
return {"status": "success"}
|
101 |
+
|
102 |
+
# @router.get("/success")
|
103 |
+
# async def payment_success(request: Request):
|
104 |
+
# print("Payment successful")
|
105 |
+
# user = request.session.get('user')
|
106 |
+
# print(user)
|
107 |
+
# if user:
|
108 |
+
# updated_user = get_user_by_id(user['id'])
|
109 |
+
# if updated_user:
|
110 |
+
# request.session['user'] = updated_user
|
111 |
+
# return RedirectResponse(url='/gradio', status_code=303)
|
112 |
+
# return RedirectResponse(url='/login', status_code=303)
|
113 |
+
|
114 |
+
@router.get("/cancel")
|
115 |
+
async def payment_cancel(request: Request):
|
116 |
+
print("Payment cancelled")
|
117 |
+
user = request.session.get('user')
|
118 |
+
print(user)
|
119 |
+
if user:
|
120 |
+
return RedirectResponse(url='/gradio', status_code=303)
|
121 |
+
return RedirectResponse(url='/login', status_code=303)
|
122 |
+
|
123 |
+
@router.get("/success")
|
124 |
+
async def payment_success(request: Request):
|
125 |
+
print("Payment successful")
|
126 |
+
stripe_session_id = request.session.get('stripe_session_id')
|
127 |
+
user_id = request.session.get('user_id')
|
128 |
+
|
129 |
+
print(f"Session data: stripe_session_id={stripe_session_id}, user_id={user_id}")
|
130 |
+
|
131 |
+
if stripe_session_id and user_id:
|
132 |
+
# Retrieve the Stripe session
|
133 |
+
stripe_session = retrieve_stripe_session(stripe_session_id)
|
134 |
+
|
135 |
+
if stripe_session.get('payment_status') == 'paid':
|
136 |
+
user = get_user_by_id(user_id)
|
137 |
+
if user:
|
138 |
+
# Update the session with the latest user data
|
139 |
+
request.session['user'] = user
|
140 |
+
print(f"User session updated: {user}")
|
141 |
+
|
142 |
+
# Clear the stripe_session_id and user_id from the session
|
143 |
+
request.session.pop('stripe_session_id', None)
|
144 |
+
request.session.pop('user_id', None)
|
145 |
+
|
146 |
+
return RedirectResponse(url='/gradio', status_code=303)
|
147 |
+
else:
|
148 |
+
print(f"User not found for ID: {user_id}")
|
149 |
+
else:
|
150 |
+
print(f"Payment not completed for session: {stripe_session_id}")
|
151 |
+
else:
|
152 |
+
print("No Stripe session ID or user ID found in the session")
|
153 |
+
|
154 |
+
return RedirectResponse(url='/login', status_code=303)
|
services/get_stripe.py
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
import stripe
|
2 |
+
|
services/image_generation.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import replicate
|
2 |
+
from PIL import Image
|
3 |
+
import requests
|
4 |
+
from io import BytesIO
|
5 |
+
|
6 |
+
#model_custom_test = "josebenitezg/flux-dev-ruth-estilo-1:c7ff81b58007c7cee3f69416e1e999192dafd8d1b1f269ea6cae137f04b34172"
|
7 |
+
flux_pro = "black-forest-labs/flux-pro"
|
8 |
+
def generate_image(prompt, steps, cfg_scale, width, height, lora_scale, progress, trigger_word='hi'):
|
9 |
+
print(f"Generating image for prompt: {prompt}")
|
10 |
+
img_url = replicate.run(
|
11 |
+
flux_pro,
|
12 |
+
input={
|
13 |
+
"steps": steps,
|
14 |
+
"prompt": prompt,
|
15 |
+
"guidance": cfg_scale,
|
16 |
+
"interval": 2,
|
17 |
+
"aspect_ratio": "1:1",
|
18 |
+
"safety_tolerance": 2
|
19 |
+
}
|
20 |
+
)
|
21 |
+
return img_url
|
services/train_lora.py
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import replicate
|
2 |
+
import os
|
3 |
+
from huggingface_hub import create_repo
|
4 |
+
|
5 |
+
REPLICATE_OWNER = "josebenitezg"
|
6 |
+
|
7 |
+
def lora_pipeline(zip_path, model_name, trigger_word="TOK", steps=1000, lora_rank=16, batch_size=1, autocaption=True, learning_rate=0.0004):
|
8 |
+
print(f'Creating dataset for {model_name}')
|
9 |
+
repo_name = f"joselobenitezg/flux-dev-{model_name}"
|
10 |
+
create_repo(repo_name, repo_type='model')
|
11 |
+
|
12 |
+
lora_name = f"flux-dev-{model_name}"
|
13 |
+
|
14 |
+
model = replicate.models.create(
|
15 |
+
owner=REPLICATE_OWNER,
|
16 |
+
name=lora_name,
|
17 |
+
visibility="public", # or "private" if you prefer
|
18 |
+
hardware="gpu-t4", # Replicate will override this for fine-tuned models
|
19 |
+
description="A fine-tuned FLUX.1 model"
|
20 |
+
)
|
21 |
+
|
22 |
+
print(f"Model created: {model.name}")
|
23 |
+
print(f"Model URL: https://replicate.com/{model.owner}/{model.name}")
|
24 |
+
|
25 |
+
# Now use this model as the destination for your training
|
26 |
+
print(f"[INFO] Starting training")
|
27 |
+
|
28 |
+
print(f'\n[INFO] Parametros a entrenar: \n Trigger word: {trigger_word}\n steps: {steps} \n lora_rank: {lora_rank}\n autocaption: {autocaption}\n learning_rate: {learning_rate}\n')
|
29 |
+
training = replicate.trainings.create(
|
30 |
+
version="ostris/flux-dev-lora-trainer:1296f0ab2d695af5a1b5eeee6e8ec043145bef33f1675ce1a2cdb0f81ec43f02",
|
31 |
+
input={
|
32 |
+
"input_images": open(zip_path, "rb"),
|
33 |
+
"steps": steps,
|
34 |
+
"lora_rank": lora_rank,
|
35 |
+
"batch_size": batch_size,
|
36 |
+
"autocaption": autocaption,
|
37 |
+
"trigger_word": trigger_word,
|
38 |
+
"learning_rate": learning_rate,
|
39 |
+
"hf_token": os.getenv('HF_TOKEN'), # optional
|
40 |
+
"hf_repo_id": repo_name, # optional
|
41 |
+
},
|
42 |
+
destination=f"{model.owner}/{model.name}"
|
43 |
+
)
|
44 |
+
|
45 |
+
print(f"Training started: {training.status}")
|
46 |
+
print(f"Training URL: https://replicate.com/p/{training.id}")
|
static/css/login.css
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
.login-with-google-btn {
|
3 |
+
display: inline-block;
|
4 |
+
width: 220px; /* Ancho fijo */
|
5 |
+
max-width: 100%; /* Para asegurar responsividad */
|
6 |
+
transition: background-color .3s, box-shadow .3s;
|
7 |
+
padding: 8px 12px 8px 35px;
|
8 |
+
border: none;
|
9 |
+
border-radius: 3px;
|
10 |
+
box-shadow: 0 -1px 0 rgba(0, 0, 0, .04), 0 1px 1px rgba(0, 0, 0, .25);
|
11 |
+
color: #757575;
|
12 |
+
font-size: 12px;
|
13 |
+
font-weight: 500;
|
14 |
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
15 |
+
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMTcuNiA5LjJsLS4xLTEuOEg5djMuNGg0LjhDMTMuNiAxMiAxMyAxMyAxMiAxMy42djIuMmgzYTguOCA4LjggMCAwIDAgMi42LTYuNnoiIGZpbGw9IiM0Mjg1RjQiIGZpbGwtcnVsZT0ibm9uemVybyIvPjxwYXRoIGQ9Ik05IDE4YzIuNCAwIDQuNS0uOCA2LTIuMmwtMy0yLjJhNS40IDUuNCAwIDAgMS04LTIuOUgxVjEzYTkgOSAwIDAgMCA4IDV6IiBmaWxsPSIjMzRBODUzIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48cGF0aCBkPSJNNCAxMC43YTUuNCA1LjQgMCAwIDEgMC0zLjRWNUgxYTkgOSAwIDAgMCAwIDhsMy0yLjN6IiBmaWxsPSIjRkJCQzA1IiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48cGF0aCBkPSJNOSAzLjZjMS4zIDAgMi41LjQgMy40IDEuM0wxNSAyLjNBOSA5IDAgMCAwIDEgNWwzIDIuNGE1LjQgNS40IDAgMCAxIDUtMy43eiIgZmlsbD0iI0VBNDMzNSIgZmlsbC1ydWxlPSJub256ZXJvIi8+PHBhdGggZD0iTTAgMGgxOHYxOEgweiIvPjwvZz48L3N2Zz4=);
|
16 |
+
background-color: white;
|
17 |
+
background-repeat: no-repeat;
|
18 |
+
background-position: 10px 50%;
|
19 |
+
background-size: 15px 15px;
|
20 |
+
text-align: center;
|
21 |
+
}
|
22 |
+
|
23 |
+
/* Contenedor para centrar el botón */
|
24 |
+
/* Estilos adicionales para el botón de Google y su contenedor */
|
25 |
+
.google-btn-container {
|
26 |
+
display: flex;
|
27 |
+
justify-content: flex-end;
|
28 |
+
width: 100%;
|
29 |
+
padding-right: 20px;
|
30 |
+
box-sizing: border-box;
|
31 |
+
position: absolute;
|
32 |
+
top: 20px;
|
33 |
+
right: 0;
|
34 |
+
}
|
35 |
+
.svelte-vt1mxs.gap {
|
36 |
+
position: static !important;
|
37 |
+
margin-top: 0 !important;
|
38 |
+
}
|
39 |
+
|
40 |
+
.login-with-google-btn:active {
|
41 |
+
background-color: #eeeeee;
|
42 |
+
}
|
43 |
+
|
44 |
+
.login-with-google-btn:focus {
|
45 |
+
outline: none;
|
46 |
+
box-shadow:
|
47 |
+
0 -1px 0 rgba(0, 0, 0, .04),
|
48 |
+
0 2px 4px rgba(0, 0, 0, .25),
|
49 |
+
0 0 0 3px #c8dafc;
|
50 |
+
}
|
51 |
+
|
52 |
+
.login-with-google-btn:disabled {
|
53 |
+
filter: grayscale(100%);
|
54 |
+
background-color: #ebebeb;
|
55 |
+
box-shadow: 0 -1px 0 rgba(0, 0, 0, .04), 0 1px 1px rgba(0, 0, 0, .25);
|
56 |
+
cursor: not-allowed;
|
57 |
+
}
|
58 |
+
|
59 |
+
/* Estilos específicos para trabajar con las clases de Gradio */
|
60 |
+
.svelte-vt1mxs.gap {
|
61 |
+
position: absolute;
|
62 |
+
top: 20px;
|
63 |
+
right: 20px;
|
64 |
+
z-index: 1000;
|
65 |
+
}
|
66 |
+
@media(max-width: 768px) {
|
67 |
+
.feature-grid {
|
68 |
+
grid-template-columns: 1fr;
|
69 |
+
}
|
70 |
+
.google-btn-container {
|
71 |
+
position: static;
|
72 |
+
justify-content: center;
|
73 |
+
padding-right: 0;
|
74 |
+
margin-top: 20px;
|
75 |
+
}
|
76 |
+
}
|
static/css/main.css
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#gen_btn {
|
2 |
+
height: 100%
|
3 |
+
}
|
4 |
+
|
5 |
+
#title {
|
6 |
+
text-align: center
|
7 |
+
}
|
8 |
+
|
9 |
+
#title h1 {
|
10 |
+
font-size: 3em;
|
11 |
+
display: inline-flex;
|
12 |
+
align-items: center
|
13 |
+
}
|
14 |
+
|
15 |
+
#title img {
|
16 |
+
width: 100px;
|
17 |
+
margin-right: 0.5em
|
18 |
+
}
|
19 |
+
|
20 |
+
#gallery .grid-wrap {
|
21 |
+
height: 10vh
|
22 |
+
}
|
23 |
+
|
24 |
+
/* Estilo para el contenedor del botón */
|
25 |
+
#logout-btn-container.svelte-vt1mxs.gap {
|
26 |
+
position: absolute;
|
27 |
+
top: 10px;
|
28 |
+
right: 10px;
|
29 |
+
z-index: 1000;
|
30 |
+
display: flex;
|
31 |
+
justify-content: flex-end;
|
32 |
+
width: auto;
|
33 |
+
}
|
34 |
+
|
35 |
+
/* Estilo para el botón de logout */
|
36 |
+
#logout_btn.lg.secondary.svelte-cmf5ev {
|
37 |
+
width: auto;
|
38 |
+
min-width: 80px;
|
39 |
+
background-color: #f44336;
|
40 |
+
color: white;
|
41 |
+
border: none;
|
42 |
+
padding: 5px 10px;
|
43 |
+
border-radius: 5px;
|
44 |
+
cursor: pointer;
|
45 |
+
font-size: 0.9em;
|
46 |
+
transition: background-color 0.3s;
|
47 |
+
text-align: center;
|
48 |
+
text-decoration: none;
|
49 |
+
display: inline-block;
|
50 |
+
margin-left: auto; /* Empuja el botón hacia la derecha */
|
51 |
+
}
|
52 |
+
|
53 |
+
#logout_btn.lg.secondary.svelte-cmf5ev:hover {
|
54 |
+
background-color: #d32f2f;
|
55 |
+
}
|
56 |
+
|
57 |
+
/* Ajuste del layout principal si es necesario */
|
58 |
+
.gradio-container {
|
59 |
+
position: relative;
|
60 |
+
}
|
static/html/landing.html
ADDED
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="es">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>ObtuAI - Creación Visual con IA</title>
|
7 |
+
<style>
|
8 |
+
:root {
|
9 |
+
--background-color: #0f0f0f;
|
10 |
+
--text-color: #ffffff;
|
11 |
+
--accent-color: #bb86fc;
|
12 |
+
--surface-color: #1e1e1e;
|
13 |
+
--border-radius: 20px;
|
14 |
+
}
|
15 |
+
body {
|
16 |
+
font-family: 'Arial', sans-serif;
|
17 |
+
line-height: 1.6;
|
18 |
+
color: var(--text-color);
|
19 |
+
background-color: var(--background-color);
|
20 |
+
margin: 0;
|
21 |
+
padding: 0;
|
22 |
+
}
|
23 |
+
.container {
|
24 |
+
width: 90%;
|
25 |
+
max-width: 1200px;
|
26 |
+
margin: auto;
|
27 |
+
overflow: hidden;
|
28 |
+
padding: 0 20px;
|
29 |
+
}
|
30 |
+
header {
|
31 |
+
background: var(--surface-color);
|
32 |
+
padding: 20px 0;
|
33 |
+
position: relative;
|
34 |
+
border-bottom-left-radius: var(--border-radius);
|
35 |
+
border-bottom-right-radius: var(--border-radius);
|
36 |
+
}
|
37 |
+
.header-content {
|
38 |
+
display: flex;
|
39 |
+
justify-content: space-between;
|
40 |
+
align-items: center;
|
41 |
+
}
|
42 |
+
.logo {
|
43 |
+
font-size: 2em;
|
44 |
+
font-weight: bold;
|
45 |
+
color: var(--accent-color);
|
46 |
+
}
|
47 |
+
#google-btn-container {
|
48 |
+
position: absolute;
|
49 |
+
right: 20px;
|
50 |
+
top: 20px;
|
51 |
+
}
|
52 |
+
.hero {
|
53 |
+
background: url('https://news.ubc.ca/wp-content/uploads/2023/08/AdobeStock_559145847.jpeg') no-repeat center center/cover;
|
54 |
+
height: 60vh;
|
55 |
+
position: relative;
|
56 |
+
display: flex;
|
57 |
+
align-items: center;
|
58 |
+
justify-content: center;
|
59 |
+
text-align: center;
|
60 |
+
margin-top: 20px;
|
61 |
+
border-radius: var(--border-radius);
|
62 |
+
overflow: hidden;
|
63 |
+
}
|
64 |
+
.hero-content {
|
65 |
+
background: rgba(0,0,0,0.7);
|
66 |
+
padding: 30px;
|
67 |
+
border-radius: var(--border-radius);
|
68 |
+
max-width: 600px;
|
69 |
+
}
|
70 |
+
.hero-content h1 {
|
71 |
+
font-size: 2.5em;
|
72 |
+
margin-bottom: 0.5em;
|
73 |
+
}
|
74 |
+
.features {
|
75 |
+
padding: 40px 0;
|
76 |
+
}
|
77 |
+
.feature-grid {
|
78 |
+
display: grid;
|
79 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
80 |
+
gap: 30px;
|
81 |
+
}
|
82 |
+
.feature {
|
83 |
+
background: var(--surface-color);
|
84 |
+
padding: 30px;
|
85 |
+
border-radius: var(--border-radius);
|
86 |
+
box-shadow: 0 8px 20px rgba(0,0,0,0.3);
|
87 |
+
transition: transform 0.3s ease;
|
88 |
+
}
|
89 |
+
.feature:hover {
|
90 |
+
transform: translateY(-5px);
|
91 |
+
}
|
92 |
+
.testimonials {
|
93 |
+
background: var(--surface-color);
|
94 |
+
padding: 40px 0;
|
95 |
+
border-radius: var(--border-radius);
|
96 |
+
margin-top: 40px;
|
97 |
+
}
|
98 |
+
.testimonial {
|
99 |
+
background: rgba(255,255,255,0.1);
|
100 |
+
padding: 25px;
|
101 |
+
margin-top: 30px;
|
102 |
+
border-radius: var(--border-radius);
|
103 |
+
transition: transform 0.3s ease;
|
104 |
+
}
|
105 |
+
.testimonial:hover {
|
106 |
+
transform: scale(1.03);
|
107 |
+
}
|
108 |
+
footer {
|
109 |
+
background: var(--surface-color);
|
110 |
+
text-align: center;
|
111 |
+
padding: 20px 0;
|
112 |
+
margin-top: 40px;
|
113 |
+
border-top-left-radius: var(--border-radius);
|
114 |
+
border-top-right-radius: var(--border-radius);
|
115 |
+
}
|
116 |
+
@media(max-width: 768px) {
|
117 |
+
.header-content {
|
118 |
+
flex-direction: column;
|
119 |
+
text-align: center;
|
120 |
+
}
|
121 |
+
#google-btn-container {
|
122 |
+
position: static;
|
123 |
+
transform: none;
|
124 |
+
margin-top: 20px;
|
125 |
+
}
|
126 |
+
.hero-content {
|
127 |
+
padding: 20px;
|
128 |
+
}
|
129 |
+
}
|
130 |
+
</style>
|
131 |
+
</head>
|
132 |
+
<body>
|
133 |
+
<header>
|
134 |
+
<div class="container">
|
135 |
+
<div class="header-content">
|
136 |
+
<div class="logo">🎨 ObtuAI</div>
|
137 |
+
<div id="google-btn-container">
|
138 |
+
<!-- El botón será insertado aquí por Gradio -->
|
139 |
+
</div>
|
140 |
+
</div>
|
141 |
+
</div>
|
142 |
+
</header>
|
143 |
+
|
144 |
+
<div class="container">
|
145 |
+
<section class="hero">
|
146 |
+
<div class="hero-content">
|
147 |
+
<h1>🚀 Bienvenido al Futuro de la Creación Visual</h1>
|
148 |
+
<p>Crea imágenes con IA en segundos. ¡Escribe tu idea y mira cómo se convierte en arte!</p>
|
149 |
+
</div>
|
150 |
+
</section>
|
151 |
+
|
152 |
+
<section class="features">
|
153 |
+
<h2>🌟 Descubre el Poder de la Generación de Imágenes por IA</h2>
|
154 |
+
<div class="feature-grid">
|
155 |
+
<div class="feature">
|
156 |
+
<h3>Personaliza</h3>
|
157 |
+
<p>Alimenta tu modelo con tus propias imágenes y estilos.</p>
|
158 |
+
</div>
|
159 |
+
<div class="feature">
|
160 |
+
<h3>Entrena</h3>
|
161 |
+
<p>Nuestra IA aprende de tus preferencias.</p>
|
162 |
+
</div>
|
163 |
+
<div class="feature">
|
164 |
+
<h3>Crea</h3>
|
165 |
+
<p>Genera imágenes que reflejen tu visión única.</p>
|
166 |
+
</div>
|
167 |
+
</div>
|
168 |
+
</section>
|
169 |
+
|
170 |
+
<section class="testimonials">
|
171 |
+
<div class="container">
|
172 |
+
<h2>💬 Lo Que Dicen Nuestros Usuarios</h2>
|
173 |
+
<div class="testimonial">
|
174 |
+
<p>"ObtuAI ha revolucionado mi proceso creativo. ¡Ahora puedo visualizar mis ideas más locas en minutos!"</p>
|
175 |
+
<p><strong>- Ana, Diseñadora Gráfica</strong></p>
|
176 |
+
</div>
|
177 |
+
<div class="testimonial">
|
178 |
+
<p>"Entrenar mi propio modelo fue sorprendentemente fácil. Ahora hago fotografías mías y de mis clientes en segundos."</p>
|
179 |
+
<p><strong>- Carlos, Fotógrafo Profesional</strong></p>
|
180 |
+
</div>
|
181 |
+
</div>
|
182 |
+
</section>
|
183 |
+
</div>
|
184 |
+
|
185 |
+
<footer>
|
186 |
+
<p>ObtuAI - Tus ideas locas en píxeles con AI.</p>
|
187 |
+
</footer>
|
188 |
+
</body>
|
189 |
+
</html>
|
static/html/main_header.html
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="es">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>ObtuAI Header</title>
|
7 |
+
<style>
|
8 |
+
:root {
|
9 |
+
--background-color: #4337C9;
|
10 |
+
--text-color: #ffffff;
|
11 |
+
--accent-color: #bb86fc;
|
12 |
+
--surface-color:#4337C9;
|
13 |
+
--border-radius: 20px;
|
14 |
+
}
|
15 |
+
body {
|
16 |
+
line-height: 1.6;
|
17 |
+
color: var(--text-color);
|
18 |
+
background-color: var(--background-color);
|
19 |
+
margin: 0;
|
20 |
+
padding: 0;
|
21 |
+
}
|
22 |
+
header {
|
23 |
+
background: var(--surface-color);
|
24 |
+
padding: 15px 20px;
|
25 |
+
border-bottom-left-radius: var(--border-radius);
|
26 |
+
border-bottom-right-radius: var(--border-radius);
|
27 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
28 |
+
}
|
29 |
+
.header-content {
|
30 |
+
display: flex;
|
31 |
+
justify-content: space-between;
|
32 |
+
align-items: center;
|
33 |
+
max-width: 1200px;
|
34 |
+
margin: 0 auto;
|
35 |
+
}
|
36 |
+
.logo {
|
37 |
+
background: rgba(187, 134, 252, 0.1);
|
38 |
+
padding: 10px 20px;
|
39 |
+
border-radius: var(--border-radius);
|
40 |
+
}
|
41 |
+
.logo h1 {
|
42 |
+
font-size: 2em;
|
43 |
+
color: var(--accent-color);
|
44 |
+
margin: 0;
|
45 |
+
}
|
46 |
+
.status {
|
47 |
+
display: flex;
|
48 |
+
align-items: center;
|
49 |
+
background: rgba(255, 255, 255, 0.1);
|
50 |
+
padding: 10px 20px;
|
51 |
+
border-radius: var(--border-radius);
|
52 |
+
}
|
53 |
+
.badge {
|
54 |
+
background-color: #4CAF50;
|
55 |
+
color: white;
|
56 |
+
padding: 5px 15px;
|
57 |
+
border-radius: 25px;
|
58 |
+
font-size: 0.8em;
|
59 |
+
margin-left: 15px;
|
60 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
61 |
+
}
|
62 |
+
|
63 |
+
</style>
|
64 |
+
</head>
|
65 |
+
<body>
|
66 |
+
<header>
|
67 |
+
<div class="header-content">
|
68 |
+
<h1>Obtu AI 📸</h1>
|
69 |
+
<div class="status">
|
70 |
+
<lord-icon
|
71 |
+
src="https://cdn.lordicon.com/jgjfuggm.json"
|
72 |
+
trigger="loop"
|
73 |
+
state="loop-cycle"
|
74 |
+
colors="primary:#4be1ec,secondary:#4030e8"
|
75 |
+
style="width:50px;height:50px">
|
76 |
+
</lord-icon>
|
77 |
+
<span class="badge">GPU🔥</span>
|
78 |
+
</div>
|
79 |
+
</div>
|
80 |
+
</header>
|
81 |
+
</body>
|
82 |
+
</html>
|
utils/image_utils.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
from PIL import Image
|
3 |
+
from io import BytesIO
|
4 |
+
|
5 |
+
def url_to_pil_image(url):
|
6 |
+
try:
|
7 |
+
# Ensure url is a string, not a list
|
8 |
+
if isinstance(url, list):
|
9 |
+
url = url[0] # Take the first URL if it's a list
|
10 |
+
|
11 |
+
response = requests.get(url)
|
12 |
+
response.raise_for_status()
|
13 |
+
image = Image.open(BytesIO(response.content))
|
14 |
+
|
15 |
+
# Convert to RGB if the image is in RGBA mode (for transparency)
|
16 |
+
if image.mode == 'RGBA':
|
17 |
+
image = image.convert('RGB')
|
18 |
+
|
19 |
+
return image
|
20 |
+
except Exception as e:
|
21 |
+
print(f"Error loading image from URL: {url}")
|
22 |
+
print(f"Error details: {str(e)}")
|
23 |
+
return None
|
utils/stripe_utils.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import stripe
|
2 |
+
from config import STRIPE_API_KEY, STRIPE_WEBHOOK_SECRET
|
3 |
+
|
4 |
+
stripe.api_key = STRIPE_API_KEY
|
5 |
+
|
6 |
+
|
7 |
+
def create_checkout_session(amount, quantity, user_id):
|
8 |
+
session = stripe.checkout.Session.create(
|
9 |
+
payment_method_types=['card'],
|
10 |
+
line_items=[{
|
11 |
+
'price_data': {
|
12 |
+
'currency': 'usd',
|
13 |
+
'unit_amount': amount,
|
14 |
+
'product_data': {
|
15 |
+
'name': f'Buy {quantity} credits',
|
16 |
+
},
|
17 |
+
},
|
18 |
+
'quantity': 1,
|
19 |
+
}],
|
20 |
+
mode='payment',
|
21 |
+
success_url='http://localhost:8000/success?session_id={CHECKOUT_SESSION_ID}&user_id=' + str(user_id),
|
22 |
+
cancel_url='http://localhost:8000/cancel?user_id=' + str(user_id),
|
23 |
+
|
24 |
+
client_reference_id=str(user_id), # Add this line
|
25 |
+
)
|
26 |
+
return session
|
27 |
+
|
28 |
+
def verify_webhook(payload, signature):
|
29 |
+
try:
|
30 |
+
event = stripe.Webhook.construct_event(
|
31 |
+
payload, signature, STRIPE_WEBHOOK_SECRET
|
32 |
+
)
|
33 |
+
return event
|
34 |
+
except ValueError as e:
|
35 |
+
return None
|
36 |
+
except stripe.error.SignatureVerificationError as e:
|
37 |
+
return None
|
38 |
+
|
39 |
+
def retrieve_stripe_session(session_id):
|
40 |
+
try:
|
41 |
+
return stripe.checkout.Session.retrieve(session_id)
|
42 |
+
except stripe.error.StripeError as e:
|
43 |
+
print(f"Error retrieving Stripe session: {str(e)}")
|
44 |
+
return None
|