euIaxs22 commited on
Commit
1e1df5b
·
verified ·
1 Parent(s): 2a24304

Update app_vince.py

Browse files
Files changed (1) hide show
  1. app_vince.py +134 -92
app_vince.py CHANGED
@@ -1,35 +1,63 @@
1
  #!/usr/bin/env python3
2
  """
3
- VINCIE Service UI (Gradio) — in-process
4
-
5
- - Importa o servidor in-process e mantém uma instância única (GPU quente).
6
- - UI minimalista: galeria e vídeo por aba.
7
- - Opções avançadas: steps, cfg_scale, aspect_ratio, resolution.
 
 
 
8
  """
9
 
10
  import os
 
11
  from pathlib import Path
12
  from typing import List, Tuple, Optional
13
 
14
- import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- # Servidor persistente (pipeline aquecida)
17
- from services.vince_server import VinceServer
18
- server = VinceServer() # instancia única e persistente (evita erro de 'self' e aquece GPU)
19
 
20
- # -------- util --------
 
21
  def _list_media(out_dir: Path, max_images: int = 24) -> Tuple[List[str], Optional[str]]:
 
 
22
  img_globs = ("*.png", "*.jpg", "*.jpeg", "*.webp")
23
  images: List[Path] = []
24
  for pat in img_globs:
25
  images += list(out_dir.rglob(pat))
26
- images = sorted(images, key=lambda p: p.stat().st_mtime) if images else []
27
- image_paths = [str(p) for p in images[-max_images:]] if images else []
28
- videos = sorted(out_dir.rglob("*.mp4"), key=lambda p: p.stat().st_mtime)
29
- video_path = str(videos[-1]) if videos else None
 
 
 
 
 
30
  return image_paths, video_path
31
 
32
- # -------- UI handlers --------
 
 
33
  def ui_multi_turn(
34
  input_image: Optional[str],
35
  turns_text: Optional[str],
@@ -37,37 +65,46 @@ def ui_multi_turn(
37
  cfg_scale_input: float,
38
  aspect_ratio_input: str,
39
  resolution_input: int,
 
40
  ):
41
- if not input_image or not str(input_image).strip():
42
- print("[multi_turn] input_image ausente")
 
 
43
  return [], None
44
  if not turns_text or not turns_text.strip():
45
- print("[multi_turn] turns ausentes")
46
  return [], None
47
 
48
  turns = [ln.strip() for ln in turns_text.splitlines() if ln.strip()]
49
-
50
  try:
 
 
51
  out_dir = server.generate_multi_turn(
52
  input_image=input_image,
53
  turns=turns,
54
- cfg_scale=float(cfg_scale_input) if cfg_scale_input is not None else None,
55
- aspect_ratio=str(aspect_ratio_input) if aspect_ratio_input else None,
56
- resolution=int(resolution_input) if resolution_input is not None else None,
57
- steps=int(steps_input) if steps_input is not None else None,
58
  )
 
59
  except Exception as e:
60
- print(f"[multi_turn] erro: {e}")
 
 
61
  return [], None
62
 
63
  out_path = Path(out_dir)
64
  if not out_path.exists():
65
- print(f"[multi_turn] saída inexistente: {out_path}")
66
  return [], None
67
 
68
  imgs, vid = _list_media(out_path)
69
  if not imgs and not vid:
70
- print(f"[multi_turn] sem mídias em {out_path}")
 
71
  return imgs, vid
72
 
73
  def ui_multi_concept(
@@ -78,105 +115,110 @@ def ui_multi_concept(
78
  cfg_scale_input: float,
79
  aspect_ratio_input: str,
80
  resolution_input: int,
 
81
  ):
 
 
82
  if not files:
83
- print("[multi_concept] files ausentes")
84
  return [], None
85
  if not descs_text or not descs_text.strip():
86
- print("[multi_concept] descs ausentes")
87
  return [], None
88
  if not final_prompt or not final_prompt.strip():
89
- print("[multi_concept] final_prompt ausente")
90
  return [], None
91
 
92
  descs = [ln.strip() for ln in descs_text.splitlines() if ln.strip()]
93
  if len(descs) != len(files):
94
- print(f"[multi_concept] mismatch descs({len(descs)}) vs files({len(files)})")
95
  return [], None
96
 
97
  try:
 
98
  out_dir = server.generate_multi_concept(
99
  concept_images=files,
100
  concept_prompts=descs,
101
  final_prompt=final_prompt,
102
- cfg_scale=float(cfg_scale_input) if cfg_scale_input is not None else None,
103
- aspect_ratio=str(aspect_ratio_input) if aspect_ratio_input else None,
104
- resolution=int(resolution_input) if resolution_input is not None else None,
105
- steps=int(steps_input) if steps_input is not None else None,
106
- pad_placeholder=False,
107
  )
 
108
  except Exception as e:
109
- print(f"[multi_concept] erro: {e}")
 
110
  return [], None
111
 
112
  out_path = Path(out_dir)
113
- if not out_path.exists():
114
- print(f"[multi_concept] saída inexistente: {out_path}")
115
- return [], None
116
-
117
  imgs, vid = _list_media(out_path)
118
- if not imgs and not vid:
119
- print(f"[multi_concept] sem mídias em {out_path}")
120
  return imgs, vid
121
 
122
- # -------- UI --------
123
- with gr.Blocks(title="VINCIE 🎨") as demo:
124
- gr.Markdown(
125
- "\n".join(
126
- [
127
- "🎨 VINCIE — Edição Multi-Turn e Composição Multi-Concept",
128
- ]
129
- )
130
- )
131
 
132
- # Opções Avançadas
133
- with gr.Accordion("Opções Avançadas", open=False):
134
  steps_input = gr.Slider(label="Passos de Inferência", minimum=10, maximum=100, step=1, value=50)
135
  cfg_scale_input = gr.Slider(label="Escala de Orientação (CFG)", minimum=1.0, maximum=15.0, step=0.5, value=7.5)
136
  aspect_ratio_input = gr.Dropdown(
137
- label="Aspect Ratio",
138
  choices=["keep_ratio", "1:1", "16:9", "9:16", "4:3", "3:4"],
139
  value="keep_ratio",
140
  )
141
  resolution_input = gr.Slider(label="Resolução (lado menor)", minimum=256, maximum=1024, step=64, value=512)
142
-
143
-
144
-
145
-
146
- # Aba 1 — Multi-turn Editing
147
- with gr.Tab("Multi-turn Editing"):
148
- with gr.Row():
149
- img = gr.Image(type="filepath", label="Imagem de entrada")
150
- turns = gr.Textbox(lines=8, label="Instruções (uma por linha)")
151
- run1 = gr.Button("Gerar")
152
- out_gallery = gr.Gallery(label="Imagens", columns=4, height="auto")
153
- #out_video = gr.Video(label="Vídeo (se houver)")
154
- run1.click(
155
- ui_multi_turn,
156
- inputs=[img, turns, steps_input, cfg_scale_input, aspect_ratio_input, resolution_input],
157
- outputs=[out_gallery],
158
- )
159
-
160
- # Aba 2 — Multi-concept Composition
161
- with gr.Tab("Multi-concept Composition"):
162
- with gr.Row():
163
- files = gr.File(file_count="multiple", type="filepath", label="Imagens de conceito")
164
- descs = gr.Textbox(lines=8, label="Descrições (uma por linha, mesma ordem das imagens)")
165
- final_prompt = gr.Textbox(lines=2, label="Prompt final")
166
- run2 = gr.Button("Gerar")
167
- out_gallery2 = gr.Gallery(label="Imagens", columns=4, height="auto")
168
- #out_video2 = gr.Video(label="Vídeo (se houver)")
169
- run2.click(
170
- ui_multi_concept,
171
- inputs=[files, descs, final_prompt, steps_input, cfg_scale_input, aspect_ratio_input, resolution_input],
172
- outputs=[out_gallery2],
173
- )
174
-
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  if __name__ == "__main__":
 
 
 
 
 
177
  demo.launch(
178
- server_name=os.getenv("GRADIO_SERVER_NAME", "0.0.0.0"),
179
- server_port=int(os.getenv("GRADIO_SERVER_PORT", os.getenv("PORT", "7860"))),
180
- allowed_paths=["/app/outputs", "/app/ckpt"],
181
- show_error=True,
182
- )
 
 
1
  #!/usr/bin/env python3
2
  """
3
+ VINCIE Service UI (Gradio) — Multi-GPU Pool Manager
4
+
5
+ - Importa e utiliza o singleton 'vince_pool_manager_singleton'.
6
+ - A instância do manager é criada uma única vez no primeiro import, mantendo
7
+ os modelos "quentes" em todas as GPUs disponíveis.
8
+ - A UI do Gradio despacha as tarefas para o pool manager, que as distribui
9
+ automaticamente entre os workers da GPU.
10
+ - UI minimalista: galeria e vídeo por aba, com opções avançadas.
11
  """
12
 
13
  import os
14
+ import gradio as gr
15
  from pathlib import Path
16
  from typing import List, Tuple, Optional
17
 
18
+ # ==============================================================================
19
+ # <<< PONTO CENTRAL DA INTEGRAÇÃO >>>
20
+ # Importamos o singleton do nosso novo pool manager.
21
+ # A inicialização pesada (download, carregamento de modelos para 4 GPUs)
22
+ # acontece automaticamente dentro deste módulo na primeira vez que ele é importado.
23
+ # ==============================================================================
24
+ try:
25
+ from services.vince_pool_manager import vince_pool_manager_singleton as server
26
+ except Exception as e:
27
+ print(f"ERRO FATAL: Não foi possível importar o VincePoolManager. A aplicação não pode iniciar.")
28
+ print(f"Detalhe do erro: {e}")
29
+ # Se o import falhar, a aplicação não tem como funcionar.
30
+ # Lançamos um erro para que os logs mostrem claramente o problema.
31
+ raise RuntimeError("Falha na inicialização do VincePoolManager.") from e
32
+
33
+ # Verificação para garantir que o singleton foi criado com sucesso.
34
+ if server is None:
35
+ raise RuntimeError("O VincePoolManager não foi inicializado corretamente. Verifique os logs de erro.")
36
 
 
 
 
37
 
38
+ # --- Funções Utilitárias ---
39
+
40
  def _list_media(out_dir: Path, max_images: int = 24) -> Tuple[List[str], Optional[str]]:
41
+ """Busca os arquivos de imagem e vídeo mais recentes no diretório de saída."""
42
+ # Esta função não precisa de alterações.
43
  img_globs = ("*.png", "*.jpg", "*.jpeg", "*.webp")
44
  images: List[Path] = []
45
  for pat in img_globs:
46
  images += list(out_dir.rglob(pat))
47
+
48
+ # Ordena por data de modificação para pegar os mais recentes
49
+ images = sorted(images, key=lambda p: p.stat().st_mtime, reverse=True) if images else []
50
+
51
+ image_paths = [str(p) for p in images[:max_images]]
52
+
53
+ videos = sorted(out_dir.rglob("*.mp4"), key=lambda p: p.stat().st_mtime, reverse=True)
54
+ video_path = str(videos[0]) if videos else None
55
+
56
  return image_paths, video_path
57
 
58
+
59
+ # --- Funções de Callback da UI (Handlers) ---
60
+
61
  def ui_multi_turn(
62
  input_image: Optional[str],
63
  turns_text: Optional[str],
 
65
  cfg_scale_input: float,
66
  aspect_ratio_input: str,
67
  resolution_input: int,
68
+ progress=gr.Progress(track_tqdm=True)
69
  ):
70
+ """Callback para a aba 'Multi-turn Editing'."""
71
+ progress(0.1, desc="Validando entradas...")
72
+ if not input_image or not Path(input_image).exists():
73
+ gr.Warning("Arquivo de imagem de entrada ausente ou inválido.")
74
  return [], None
75
  if not turns_text or not turns_text.strip():
76
+ gr.Warning("As instruções (turns) estão vazias.")
77
  return [], None
78
 
79
  turns = [ln.strip() for ln in turns_text.splitlines() if ln.strip()]
80
+
81
  try:
82
+ progress(0.5, desc="Enviando tarefa para o pool de GPUs. Aguardando a inferência...")
83
+ # A chamada para o servidor agora é limpa e delega todo o trabalho pesado.
84
  out_dir = server.generate_multi_turn(
85
  input_image=input_image,
86
  turns=turns,
87
+ cfg_scale=float(cfg_scale_input),
88
+ aspect_ratio=str(aspect_ratio_input),
89
+ resolution=int(resolution_input),
90
+ steps=int(steps_input),
91
  )
92
+ progress(0.9, desc="Inferência concluída. Buscando resultados...")
93
  except Exception as e:
94
+ print(f"[UI][multi_turn] Erro durante a inferência: {e}")
95
+ # gr.Error exibe uma notificação de erro clara para o usuário.
96
+ gr.Error(f"Erro na Geração: {e}")
97
  return [], None
98
 
99
  out_path = Path(out_dir)
100
  if not out_path.exists():
101
+ gr.Warning(f"O diretório de saída '{out_path}' não foi encontrado.")
102
  return [], None
103
 
104
  imgs, vid = _list_media(out_path)
105
  if not imgs and not vid:
106
+ gr.Warning(f"Nenhum arquivo de mídia encontrado no diretório de saída.")
107
+
108
  return imgs, vid
109
 
110
  def ui_multi_concept(
 
115
  cfg_scale_input: float,
116
  aspect_ratio_input: str,
117
  resolution_input: int,
118
+ progress=gr.Progress(track_tqdm=True)
119
  ):
120
+ """Callback para a aba 'Multi-concept Composition'."""
121
+ progress(0.1, desc="Validando entradas...")
122
  if not files:
123
+ gr.Warning("Nenhum arquivo de imagem de conceito fornecido.")
124
  return [], None
125
  if not descs_text or not descs_text.strip():
126
+ gr.Warning("As descrições dos conceitos estão vazias.")
127
  return [], None
128
  if not final_prompt or not final_prompt.strip():
129
+ gr.Warning("O prompt final está vazio.")
130
  return [], None
131
 
132
  descs = [ln.strip() for ln in descs_text.splitlines() if ln.strip()]
133
  if len(descs) != len(files):
134
+ gr.Warning(f"O número de descrições ({len(descs)}) não corresponde ao número de imagens ({len(files)}).")
135
  return [], None
136
 
137
  try:
138
+ progress(0.5, desc="Enviando tarefa para o pool de GPUs. Aguardando a inferência...")
139
  out_dir = server.generate_multi_concept(
140
  concept_images=files,
141
  concept_prompts=descs,
142
  final_prompt=final_prompt,
143
+ cfg_scale=float(cfg_scale_input),
144
+ aspect_ratio=str(aspect_ratio_input),
145
+ resolution=int(resolution_input),
146
+ steps=int(steps_input),
147
+ pad_placeholder=False, # Este parâmetro pode ser exposto na UI se necessário
148
  )
149
+ progress(0.9, desc="Inferência concluída. Buscando resultados...")
150
  except Exception as e:
151
+ print(f"[UI][multi_concept] Erro durante a inferência: {e}")
152
+ gr.Error(f"Erro na Geração: {e}")
153
  return [], None
154
 
155
  out_path = Path(out_dir)
 
 
 
 
156
  imgs, vid = _list_media(out_path)
 
 
157
  return imgs, vid
158
 
159
+ # --- Definição da Interface Gráfica com Gradio ---
160
+ with gr.Blocks(title="VINCIE (Multi-GPU)") as demo:
161
+ gr.Markdown("# 🎨 VINCIE — Edição e Composição (Multi-GPU)")
162
+ gr.Markdown("Esta interface utiliza um pool de GPUs para processar as solicitações de forma rápida e paralela.")
 
 
 
 
 
163
 
164
+ # Opções Avançadas são definidas uma vez e reutilizadas nas abas
165
+ with gr.Accordion("Opções Avançadas (Comum a todas as abas)", open=False):
166
  steps_input = gr.Slider(label="Passos de Inferência", minimum=10, maximum=100, step=1, value=50)
167
  cfg_scale_input = gr.Slider(label="Escala de Orientação (CFG)", minimum=1.0, maximum=15.0, step=0.5, value=7.5)
168
  aspect_ratio_input = gr.Dropdown(
169
+ label="Proporção da Imagem (Aspect Ratio)",
170
  choices=["keep_ratio", "1:1", "16:9", "9:16", "4:3", "3:4"],
171
  value="keep_ratio",
172
  )
173
  resolution_input = gr.Slider(label="Resolução (lado menor)", minimum=256, maximum=1024, step=64, value=512)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
+ with gr.Tabs():
176
+ # Aba 1 — Multi-turn Editing
177
+ with gr.TabItem("Edição Sequencial (Multi-turn)"):
178
+ with gr.Row():
179
+ with gr.Column(scale=1):
180
+ img_in_1 = gr.Image(type="filepath", label="Imagem de Entrada")
181
+ turns_in_1 = gr.Textbox(lines=8, label="Instruções de Edição (uma por linha)")
182
+ run_btn_1 = gr.Button("Gerar Edição", variant="primary")
183
+ with gr.Column(scale=2):
184
+ gallery_out_1 = gr.Gallery(label="Imagens Geradas", columns=4, height="auto")
185
+ video_out_1 = gr.Video(label="Vídeo Gerado (se aplicável)")
186
+
187
+ run_btn_1.click(
188
+ fn=ui_multi_turn,
189
+ inputs=[img_in_1, turns_in_1, steps_input, cfg_scale_input, aspect_ratio_input, resolution_input],
190
+ outputs=[gallery_out_1, video_out_1],
191
+ )
192
+
193
+ # Aba 2 — Multi-concept Composition
194
+ with gr.TabItem("Composição de Conceitos (Multi-concept)"):
195
+ with gr.Row():
196
+ with gr.Column(scale=1):
197
+ files_in_2 = gr.File(file_count="multiple", type="filepath", label="Imagens de Conceito")
198
+ descs_in_2 = gr.Textbox(lines=8, label="Descrições (uma por linha, na mesma ordem das imagens)")
199
+ final_prompt_in_2 = gr.Textbox(lines=2, label="Prompt Final da Composição")
200
+ run_btn_2 = gr.Button("Gerar Composição", variant="primary")
201
+ with gr.Column(scale=2):
202
+ gallery_out_2 = gr.Gallery(label="Imagens Geradas", columns=4, height="auto")
203
+ video_out_2 = gr.Video(label="Vídeo Gerado (se aplicável)")
204
+
205
+ run_btn_2.click(
206
+ fn=ui_multi_concept,
207
+ inputs=[files_in_2, descs_in_2, final_prompt_in_2, steps_input, cfg_scale_input, aspect_ratio_input, resolution_input],
208
+ outputs=[gallery_out_2, video_out_2],
209
+ )
210
+
211
+ # --- Ponto de Entrada da Aplicação ---
212
  if __name__ == "__main__":
213
+ # Busca as configurações do servidor a partir de variáveis de ambiente, com valores padrão.
214
+ server_name = os.getenv("GRADIO_SERVER_NAME", "0.0.0.0")
215
+ server_port = int(os.getenv("GRADIO_SERVER_PORT", "7860"))
216
+
217
+ print(f"Iniciando a interface Gradio em http://{server_name}:{server_port}")
218
  demo.launch(
219
+ server_name=server_name,
220
+ server_port=server_port,
221
+ # allowed_paths é importante para que o Gradio possa servir os arquivos de resultado
222
+ allowed_paths=["/app/outputs", "/app/ckpt"],
223
+ show_error=True, # Exibe tracebacks de erro detalhados no navegador (bom para depuração)
224
+ )