Jakephobia commited on
Commit
b8a8724
·
verified ·
1 Parent(s): f035472

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +485 -81
app.py CHANGED
@@ -1,87 +1,491 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
- from PIL import Image
3
- import spaces
4
  import torch
 
 
5
  from transformers import AutoProcessor, Qwen2_5_VLForConditionalGeneration
6
 
7
- # 1. Definiamo il modello a 7B parametri
8
- MODEL_ID = "Qwen/Qwen2.5-VL-7B-Instruct"
9
-
10
- print("Caricamento del modello in corso... Attendere qualche istante.")
11
-
12
- # 2. Carichiamo il processore e il modello con ottimizzazione della memoria
13
- processor = AutoProcessor.from_pretrained(MODEL_ID)
14
- model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
15
- MODEL_ID, torch_dtype=torch.float16, device_map="auto"
16
- )
17
-
18
-
19
- # 3. La funzione che analizza la foto usando ZeroGPU
20
- @spaces.GPU
21
- def analizza_cibo_e_ricette(immagine: Image.Image, dieta_utente: str):
22
- if immagine is None:
23
- return "Per favore, carica un'immagine del tuo frigorifero, della dispensa o di uno scontrino."
24
-
25
- # Messaggio di istruzioni inviato al modello
26
- prompt = f"""
27
- Sei un assistente nutrizionale esperto.
28
- 1. Identifica chiaramente tutti gli alimenti ed ingredienti visibili nell'immagine.
29
- 2. Considera le seguenti preferenze/restrizioni di dieta dell'utente: "{dieta_utente}".
30
- 3. Suggerisci 2 ricette pratiche da cucinare usando principalmente gli ingredienti rilevati.
31
-
32
- Rispondi in modo chiaro, ordinato e sempre in lingua italiana.
33
- """
34
-
35
- messages = [
36
- {
37
- "role": "user",
38
- "content": [
39
- {"type": "image", "image": immagine},
40
- {"type": "text", "text": prompt},
41
- ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  }
43
- ]
44
-
45
- # Preparazione dei dati per la scheda grafica
46
- text_prompt = processor.apply_chat_template(
47
- messages, tokenize=False, add_generation_prompt=True
48
- )
49
- inputs = processor(
50
- text=[text_prompt], images=[immagine], padding=True, return_tensors="pt"
51
- ).to("cuda")
52
-
53
- # Generazione della risposta
54
- generated_ids = model.generate(**inputs, max_new_tokens=600)
55
- generated_ids_trimmed = [
56
- out_ids[len(in_ids) :]
57
- for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
58
- ]
59
-
60
- risposta = processor.batch_decode(
61
- generated_ids_trimmed,
62
- skip_special_tokens=True,
63
- clean_up_tokenization_spaces=False,
64
- )[0]
65
-
66
- return risposta
67
-
68
-
69
- # 4. Creazione dell'interfaccia web e dell'API automatica
70
- demo = gr.Interface(
71
- fn=analizza_cibo_e_ricette,
72
- inputs=[
73
- gr.Image(type="pil", label="Foto Frigo / Dispensa"),
74
- gr.Textbox(
75
- label="Preferenze Dieta",
76
- value="Iperproteica, senza lattosio",
77
- placeholder="Es: Vegetariana, Keto, Basso contenuto di sale...",
78
- ),
79
- ],
80
- outputs=gr.Textbox(label="Risposta Assistente Cibo"),
81
- title="API Gestione Cibo & Ricette (Qwen 7B)",
82
- description="Carica la foto degli alimenti e indica la tua dieta per ottenere l'analisi e le ricette.",
83
- )
84
-
85
- # Avvio del server
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  if __name__ == "__main__":
87
- demo.launch()
 
1
+ """
2
+ Pantry API — Qwen2.5-VL-7B-Instruct su Hugging Face Spaces.
3
+
4
+ Stateless: nessun gr.State, nessuna cronologia. Il contesto della dispensa arriva
5
+ a ogni chiamata e non viene conservato dal codice applicativo.
6
+
7
+ Endpoint:
8
+ /genera proxy 1:1 con AiRequest dell'app Android
9
+ /analizza_foto foto + contesto -> {"prodotti":[...]}
10
+ /piano contesto -> {"giorni":[...]}
11
+ /comando contesto + messaggio -> {"azione":...,"prodotti":[...]}
12
+ /chat contesto + domanda -> testo
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import functools
18
+ import json
19
+ import os
20
+ import re
21
+ import threading
22
+ from typing import Optional
23
+
24
+ os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
25
+
26
  import gradio as gr
 
 
27
  import torch
28
+ from PIL import Image
29
+ from qwen_vl_utils import process_vision_info
30
  from transformers import AutoProcessor, Qwen2_5_VLForConditionalGeneration
31
 
32
+ try:
33
+ import spaces
34
+
35
+ def gpu(fn=None, **kwargs):
36
+ return spaces.GPU(**kwargs)(fn) if fn is not None else spaces.GPU(**kwargs)
37
+ except ImportError:
38
+ # Permette di aprire il file in locale senza il pacchetto spaces.
39
+ def gpu(fn=None, **kwargs):
40
+ def deco(f):
41
+ @functools.wraps(f)
42
+ def wrapper(*args, **kw):
43
+ return f(*args, **kw)
44
+ return wrapper
45
+ return deco(fn) if fn is not None else deco
46
+
47
+
48
+ MODEL_ID = os.getenv("PANTRY_MODEL_ID", "Qwen/Qwen2.5-VL-7B-Instruct")
49
+ QUANTIZZAZIONE = os.getenv("PANTRY_QUANTIZZAZIONE", "bf16").lower()
50
+ MAX_TOKENS_CAP = 8192
51
+ DEFAULT_MAX_TOKENS = 2048
52
+
53
+ _modello = None
54
+ _processore = None
55
+ _lock_modello = threading.Lock()
56
+
57
+
58
+ def _carica_modello():
59
+ """Caricamento pigro: la GPU di ZeroGPU esiste solo dentro @gpu."""
60
+ global _modello, _processore
61
+ if _modello is not None:
62
+ return _modello, _processore
63
+ with _lock_modello:
64
+ if _modello is not None:
65
+ return _modello, _processore
66
+ if not torch.cuda.is_available():
67
+ raise gr.Error(
68
+ "Questo Space richiede una GPU. Imposta Hardware su GPU "
69
+ "(ZeroGPU o dedicata) nelle impostazioni dello Space."
70
+ )
71
+ dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
72
+ _processore = AutoProcessor.from_pretrained(MODEL_ID, trust_remote_code=True)
73
+ parametri = {
74
+ "torch_dtype": dtype,
75
+ "device_map": "auto",
76
+ "trust_remote_code": True,
77
+ }
78
+ if QUANTIZZAZIONE == "4bit":
79
+ from transformers import BitsAndBytesConfig
80
+
81
+ parametri.pop("torch_dtype", None)
82
+ parametri["quantization_config"] = BitsAndBytesConfig(
83
+ load_in_4bit=True,
84
+ bnb_4bit_compute_dtype=dtype,
85
+ bnb_4bit_quant_type="nf4",
86
+ )
87
+ _modello = Qwen2_5_VLForConditionalGeneration.from_pretrained(
88
+ MODEL_ID, **parametri
89
+ ).eval()
90
+ return _modello, _processore
91
+
92
+
93
+ # ---------------------------------------------------------------------------
94
+ # Schemi: devono restare allineati a Schemi.kt dell'app.
95
+ # ---------------------------------------------------------------------------
96
+
97
+ SCHEMA_PRODOTTI = r"""
98
+ {
99
+ "type": "object",
100
+ "properties": {
101
+ "azione": {
102
+ "type": "string",
103
+ "enum": ["aggiungi", "finito", "usa", "nessuna"]
104
+ },
105
+ "prodotti": {
106
+ "type": "array",
107
+ "items": {
108
+ "type": "object",
109
+ "properties": {
110
+ "nome": {"type": "string"},
111
+ "quantita": {"type": "number"},
112
+ "unita": {"type": "string", "enum": ["g", "ml", "pz"]},
113
+ "posto": {"type": "string"},
114
+ "scadenza": {"type": "string"},
115
+ "sicurezza": {"type": "number"}
116
+ },
117
+ "required": ["nome"]
118
+ }
119
+ }
120
+ },
121
+ "required": ["azione", "prodotti"]
122
+ }
123
+ """
124
+
125
+ SCHEMA_PIANO = r"""
126
+ {
127
+ "type": "object",
128
+ "properties": {
129
+ "giorni": {
130
+ "type": "array",
131
+ "items": {
132
+ "type": "object",
133
+ "properties": {
134
+ "numero": {"type": "integer"},
135
+ "pasti": {
136
+ "type": "array",
137
+ "items": {
138
+ "type": "object",
139
+ "properties": {
140
+ "tipo": {
141
+ "type": "string",
142
+ "enum": ["colazione", "pranzo", "cena", "spuntino"]
143
+ },
144
+ "nome": {"type": "string"},
145
+ "ingredienti": {
146
+ "type": "array",
147
+ "items": {
148
+ "type": "object",
149
+ "properties": {
150
+ "voceId": {"type": "integer"},
151
+ "nome": {"type": "string"},
152
+ "quantita": {"type": "number"},
153
+ "unita": {"type": "string", "enum": ["g", "ml", "pz"]}
154
+ },
155
+ "required": ["voceId", "nome", "quantita", "unita"]
156
+ }
157
+ }
158
+ },
159
+ "required": ["tipo", "nome", "ingredienti"]
160
+ }
161
+ }
162
+ },
163
+ "required": ["numero", "pasti"]
164
  }
165
+ }
166
+ },
167
+ "required": ["giorni"]
168
+ }
169
+ """
170
+
171
+
172
+ # ---------------------------------------------------------------------------
173
+ # Prompt e generazione
174
+ # ---------------------------------------------------------------------------
175
+
176
+ def _componi_sistema(system_instruction: str, json_output: bool, schema_json: str) -> str:
177
+ sistema = (system_instruction or "").strip()
178
+ if json_output:
179
+ sistema += (
180
+ "\n\nRispondi SOLO con un oggetto JSON valido, senza markdown, "
181
+ "senza commenti e senza testo prima o dopo. Usa esattamente le "
182
+ "chiavi e i tipi dello schema."
183
+ )
184
+ if schema_json and schema_json.strip():
185
+ sistema += "\n\nSchema JSON da rispettare:\n" + schema_json.strip()
186
+ return sistema
187
+
188
+
189
+ def _prepara_ingressi(
190
+ system_instruction: str,
191
+ user_text: str,
192
+ immagine: Optional[Image.Image],
193
+ json_output: bool,
194
+ schema_json: str,
195
+ ):
196
+ modello, processore = _carica_modello()
197
+ sistema = _componi_sistema(system_instruction, json_output, schema_json)
198
+
199
+ contenuto = []
200
+ if immagine is not None:
201
+ contenuto.append({"type": "image", "image": immagine})
202
+ contenuto.append({"type": "text", "text": user_text})
203
+
204
+ messaggi = [
205
+ {"role": "system", "content": [{"type": "text", "text": sistema}]},
206
+ {"role": "user", "content": contenuto},
207
+ ]
208
+
209
+ testo_prompt = processore.apply_chat_template(
210
+ messaggi, tokenize=False, add_generation_prompt=True
211
+ )
212
+ immagini, video = process_vision_info(messaggi)
213
+ ingressi = processore(
214
+ text=[testo_prompt],
215
+ images=immagini,
216
+ videos=video,
217
+ padding=True,
218
+ return_tensors="pt",
219
+ ).to(modello.device)
220
+ return modello, processore, ingressi
221
+
222
+
223
+ def _genera_grezzo(
224
+ system_instruction: str,
225
+ user_text: str,
226
+ immagine: Optional[Image.Image],
227
+ json_output: bool,
228
+ schema_json: str,
229
+ max_tokens: int,
230
+ temperatura: float,
231
+ ) -> str:
232
+ modello, processore, ingressi = _prepara_ingressi(
233
+ system_instruction, user_text, immagine, json_output, schema_json
234
+ )
235
+ token_max = max(16, min(int(max_tokens or DEFAULT_MAX_TOKENS), MAX_TOKENS_CAP))
236
+ parametri = dict(max_new_tokens=token_max)
237
+ if temperatura > 0:
238
+ parametri.update(
239
+ do_sample=True,
240
+ temperature=max(0.0, min(float(temperatura), 1.5)),
241
+ top_p=0.95,
242
+ )
243
+ else:
244
+ parametri.update(do_sample=False)
245
+
246
+ with torch.inference_mode():
247
+ generati = modello.generate(**ingressi, **parametri)
248
+
249
+ ritagliati = [
250
+ uscita[len(ingresso):]
251
+ for ingresso, uscita in zip(ingressi.input_ids, generati)
252
+ ]
253
+ testo = processore.batch_decode(
254
+ ritagliati,
255
+ skip_special_tokens=True,
256
+ clean_up_tokenization_spaces=False,
257
+ )[0]
258
+ return testo.strip()
259
+
260
+
261
+ def _estrai_json(testo: str) -> str:
262
+ testo = (testo or "").strip()
263
+ if testo.startswith("```"):
264
+ testo = re.sub(r"^```(?:json)?\s*", "", testo, flags=re.IGNORECASE)
265
+ testo = re.sub(r"\s*```$", "", testo)
266
+ inizio = testo.find("{")
267
+ fine = testo.rfind("}")
268
+ if inizio >= 0 and fine > inizio:
269
+ return testo[inizio:fine + 1]
270
+ return testo
271
+
272
+
273
+ def _valida_json(testo: str, schema_json: str) -> str:
274
+ if not (schema_json or "").strip():
275
+ return testo
276
+ import jsonschema
277
+
278
+ schema = json.loads(schema_json)
279
+ dati = json.loads(testo)
280
+ jsonschema.validate(dati, schema)
281
+ return json.dumps(dati, ensure_ascii=False)
282
+
283
+
284
+ def _genera_con_validazione(
285
+ system_instruction: str,
286
+ user_text: str,
287
+ immagine: Optional[Image.Image],
288
+ json_output: bool,
289
+ schema_json: str,
290
+ max_tokens: int,
291
+ temperatura: float,
292
+ ) -> str:
293
+ primo = _genera_grezzo(
294
+ system_instruction, user_text, immagine, json_output,
295
+ schema_json, max_tokens, temperatura,
296
+ )
297
+ if not json_output:
298
+ return primo
299
+
300
+ candidato = _estrai_json(primo)
301
+ try:
302
+ return _valida_json(candidato, schema_json)
303
+ except Exception as errore:
304
+ # Un solo tentativo di riparazione: il modello vede l'errore e riscrive.
305
+ riparazione = _genera_grezzo(
306
+ system_instruction=system_instruction,
307
+ user_text=(
308
+ f"{user_text}\n\nLa risposta precedente era:\n{primo}\n\n"
309
+ f"Non è valida: {errore}. Riscrivi l'oggetto completo, solo JSON."
310
+ ),
311
+ immagine=immagine,
312
+ json_output=True,
313
+ schema_json=schema_json,
314
+ max_tokens=max_tokens,
315
+ temperatura=0.0,
316
+ )
317
+ try:
318
+ return _valida_json(_estrai_json(riparazione), schema_json)
319
+ except Exception as secondo:
320
+ raise gr.Error(f"Il modello non ha prodotto un JSON valido: {secondo}") from secondo
321
+
322
+
323
+ # ---------------------------------------------------------------------------
324
+ # Endpoint
325
+ # ---------------------------------------------------------------------------
326
+
327
+ @gpu(duration=180)
328
+ def genera(
329
+ system_instruction: str,
330
+ user_text: str,
331
+ immagine: Optional[Image.Image],
332
+ json_output: bool,
333
+ schema_json: str,
334
+ max_tokens: int = DEFAULT_MAX_TOKENS,
335
+ temperatura: float = 0.2,
336
+ ) -> str:
337
+ return _genera_con_validazione(
338
+ system_instruction, user_text, immagine, json_output,
339
+ schema_json, max_tokens, temperatura,
340
+ )
341
+
342
+
343
+ @gpu(duration=180)
344
+ def analizza_foto(immagine: Optional[Image.Image], contesto: str) -> str:
345
+ if immagine is None:
346
+ raise gr.Error("Carica una foto del frigo, della dispensa o dello scontrino.")
347
+ sistema = (
348
+ "Sei l'assistente della dispensa di casa.\n"
349
+ "Leggi la foto della spesa e riconosci i prodotti acquistati.\n"
350
+ "Per ogni prodotto scrivi nome, quantita' e unita' (solo g, ml o pz).\n"
351
+ "Scegli il posto fra quelli elencati nel contesto.\n"
352
+ "Aggiungi sicurezza da 0 a 1: quanto sei sicuro della lettura.\n"
353
+ "Per una foto, azione vale sempre 'nessuna'.\n"
354
+ "Non inventare prodotti che nella foto non si vedono.\n"
355
+ "Rispondi solo con il JSON dello schema, senza spiegazioni.\n\n"
356
+ + (contesto or "")
357
+ )
358
+ return _genera_con_validazione(
359
+ sistema,
360
+ "Leggi questa foto della spesa e riconosci i prodotti.",
361
+ immagine,
362
+ True,
363
+ SCHEMA_PRODOTTI,
364
+ 1024,
365
+ 0.0,
366
+ )
367
+
368
+
369
+ @gpu(duration=180)
370
+ def piano(contesto: str) -> str:
371
+ sistema = (
372
+ "Sei il coach della dispensa di casa.\n"
373
+ "Prepara un piano di 3 giorni: colazione, pranzo e cena per ogni giorno.\n"
374
+ "Aggiungi gli spuntini solo se il profilo prevede piu' di 3 pasti al giorno.\n"
375
+ "Rispetta calorie, macro, tempo ai fornelli e livello di cucina del profilo.\n"
376
+ "Sale, olio e acqua si considerano sempre presenti.\n"
377
+ "Per ogni altro ingrediente usa SOLO le voci dell'elenco, indicando il loro "
378
+ "identificativo in voceId.\n"
379
+ "Rispondi solo con il JSON dello schema, senza spiegazioni.\n\n"
380
+ + (contesto or "")
381
+ )
382
+ return _genera_con_validazione(
383
+ sistema,
384
+ "Prepara il piano dei 3 giorni.",
385
+ None,
386
+ True,
387
+ SCHEMA_PIANO,
388
+ MAX_TOKENS_CAP,
389
+ 0.2,
390
+ )
391
+
392
+
393
+ @gpu(duration=120)
394
+ def comando(contesto: str, messaggio: str) -> str:
395
+ sistema = (
396
+ "Sei l'assistente della dispensa di casa.\n"
397
+ "Capisci se il messaggio dell'utente modifica la dispensa e lo traduci "
398
+ "nei dati richiesti.\n"
399
+ "- azione 'aggiungi': ha comprato qualcosa (serve quantita' e unita').\n"
400
+ "- azione 'finito': una cosa e' finita (basta il nome).\n"
401
+ "- azione 'usa': ne ha consumata una quantita' (serve quantita' e unita').\n"
402
+ "- azione 'nessuna': non e' un comando sulla dispensa.\n"
403
+ "Usa solo i posti e le unita' elencati; non convertire mai fra g, ml e pz.\n"
404
+ "Rispondi solo con il JSON dello schema, senza spiegazioni.\n\n"
405
+ + (contesto or "")
406
+ )
407
+ return _genera_con_validazione(
408
+ sistema, messaggio, None, True, SCHEMA_PRODOTTI, 1024, 0.0
409
+ )
410
+
411
+
412
+ @gpu(duration=180)
413
+ def chat(contesto: str, domanda: str) -> str:
414
+ sistema = (
415
+ "Sei il coach della dispensa di casa.\n"
416
+ "Rispondi in italiano, in modo concreto e breve.\n"
417
+ "Usa SOLO le voci elencate nel contesto, con le quantita' indicate.\n"
418
+ "Sale, olio e acqua si considerano sempre presenti.\n"
419
+ "Se un piatto richiede qualcosa che non c'e', non proporlo.\n\n"
420
+ + (contesto or "")
421
+ )
422
+ return _genera_con_validazione(
423
+ sistema, domanda, None, False, "", 2048, 0.4
424
+ )
425
+
426
+
427
+ # ---------------------------------------------------------------------------
428
+ # Interfaccia
429
+ # ---------------------------------------------------------------------------
430
+
431
+ with gr.Blocks(title="Pantry API — Qwen 7B") as demo:
432
+ gr.Markdown(
433
+ "# Pantry API\n"
434
+ "Qwen2.5-VL-7B-Instruct. Lo Space è senza stato: a ogni chiamata "
435
+ "arriva il contesto completo della dispensa."
436
+ )
437
+
438
+ with gr.Tab("Generico"):
439
+ g_sistema = gr.Textbox(label="System instruction", lines=6)
440
+ g_utente = gr.Textbox(label="User text", lines=4)
441
+ g_immagine = gr.Image(type="pil", label="Immagine (opzionale)")
442
+ g_json = gr.Checkbox(label="JSON output", value=False)
443
+ g_schema = gr.Textbox(label="Schema JSON", lines=4)
444
+ g_max = gr.Slider(64, MAX_TOKENS_CAP, value=DEFAULT_MAX_TOKENS, step=64,
445
+ label="Max new tokens")
446
+ g_temp = gr.Slider(0.0, 1.0, value=0.2, step=0.1, label="Temperatura")
447
+ g_out = gr.Textbox(label="Risposta", lines=14)
448
+ gr.Button("Genera").click(
449
+ genera,
450
+ [g_sistema, g_utente, g_immagine, g_json, g_schema, g_max, g_temp],
451
+ g_out,
452
+ api_name="/genera",
453
+ )
454
+
455
+ with gr.Tab("Foto"):
456
+ f_immagine = gr.Image(type="pil", label="Foto frigo / dispensa / scontrino")
457
+ f_contesto = gr.Textbox(label="Contesto dispensa", lines=8)
458
+ f_out = gr.Code(label="Prodotti (JSON)", language="json")
459
+ gr.Button("Analizza").click(
460
+ analizza_foto, [f_immagine, f_contesto], f_out,
461
+ api_name="/analizza_foto",
462
+ )
463
+
464
+ with gr.Tab("Piano"):
465
+ p_contesto = gr.Textbox(label="Contesto dispensa", lines=10)
466
+ p_out = gr.Code(label="Piano (JSON)", language="json")
467
+ gr.Button("Prepara piano").click(
468
+ piano, [p_contesto], p_out, api_name="/piano"
469
+ )
470
+
471
+ with gr.Tab("Comando"):
472
+ c_contesto = gr.Textbox(label="Contesto dispensa", lines=8)
473
+ c_messaggio = gr.Textbox(label="Messaggio")
474
+ c_out = gr.Code(label="Comando (JSON)", language="json")
475
+ gr.Button("Interpreta").click(
476
+ comando, [c_contesto, c_messaggio], c_out, api_name="/comando"
477
+ )
478
+
479
+ with gr.Tab("Chat"):
480
+ h_contesto = gr.Textbox(label="Contesto dispensa", lines=8)
481
+ h_domanda = gr.Textbox(label="Domanda")
482
+ h_out = gr.Textbox(label="Risposta", lines=12)
483
+ gr.Button("Chiedi").click(
484
+ chat, [h_contesto, h_domanda], h_out, api_name="/chat"
485
+ )
486
+
487
+
488
+ demo.queue(default_concurrency_limit=1, max_size=8)
489
+
490
  if __name__ == "__main__":
491
+ demo.launch(delete_cache=(60, 600))