Tonic commited on
Commit
36b8703
·
1 Parent(s): 68b0972

adds a lot of improvements to launch.sh and demo spaces and secures read token at demo launch

Browse files
launch.sh CHANGED
@@ -1454,14 +1454,23 @@ export HF_USERNAME="$HF_USERNAME"
1454
 
1455
  print_info "Deploying demo space for model: $DEMO_MODEL_ID"
1456
  print_info "Using subfolder: $DEMO_SUBFOLDER"
 
 
 
 
 
 
 
1457
 
1458
  python scripts/deploy_demo_space.py \
1459
- --hf-token "$HF_TOKEN" \
 
1460
  --hf-username "$HF_USERNAME" \
1461
  --model-id "$DEMO_MODEL_ID" \
1462
  --subfolder "$DEMO_SUBFOLDER" \
1463
  --space-name "${REPO_SHORT}-demo" \
1464
- --config-file "$CONFIG_FILE"
 
1465
 
1466
  if [ $? -eq 0 ]; then
1467
  DEMO_SPACE_URL="https://huggingface.co/spaces/$HF_USERNAME/${REPO_SHORT}-demo"
 
1454
 
1455
  print_info "Deploying demo space for model: $DEMO_MODEL_ID"
1456
  print_info "Using subfolder: $DEMO_SUBFOLDER"
1457
+
1458
+ # Additional demo parameters
1459
+ DEMO_EXTRA_ARGS=""
1460
+ if [ "$MODEL_FAMILY" = "GPT-OSS" ]; then
1461
+ # Prebuilt medical example for GPT-OSS demo
1462
+ DEMO_EXTRA_ARGS="--examples-type medical"
1463
+ fi
1464
 
1465
  python scripts/deploy_demo_space.py \
1466
+ --hf-token "$HF_WRITE_TOKEN" \
1467
+ --space-secret-token "$HF_READ_TOKEN" \
1468
  --hf-username "$HF_USERNAME" \
1469
  --model-id "$DEMO_MODEL_ID" \
1470
  --subfolder "$DEMO_SUBFOLDER" \
1471
  --space-name "${REPO_SHORT}-demo" \
1472
+ --config-file "$CONFIG_FILE" \
1473
+ $DEMO_EXTRA_ARGS
1474
 
1475
  if [ $? -eq 0 ]; then
1476
  DEMO_SPACE_URL="https://huggingface.co/spaces/$HF_USERNAME/${REPO_SHORT}-demo"
scripts/deploy_demo_space.py CHANGED
@@ -37,10 +37,38 @@ logger = logging.getLogger(__name__)
37
  class DemoSpaceDeployer:
38
  """Deploy demo space to Hugging Face Spaces"""
39
 
40
- def __init__(self, hf_token: str, hf_username: str, model_id: str,
41
- subfolder: str = "int4", space_name: Optional[str] = None,
42
- demo_type: Optional[str] = None, config_file: Optional[str] = None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  self.hf_token = hf_token
 
 
44
  self.hf_username = hf_username
45
  # Allow passing just a repo name without username and auto-prefix
46
  self.model_id = model_id if "/" in model_id else f"{hf_username}/{model_id}"
@@ -55,6 +83,10 @@ class DemoSpaceDeployer:
55
  self.developer_message: Optional[str] = None
56
  self.model_identity: Optional[str] = None
57
  self.reasoning_effort: Optional[str] = None
 
 
 
 
58
 
59
  # Determine demo type from model_id if not provided
60
  if demo_type is None:
@@ -78,6 +110,24 @@ class DemoSpaceDeployer:
78
  except Exception as e:
79
  logger.warning(f"Could not load config messages: {e}")
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  def _load_config_messages(self) -> None:
82
  """Load system/developer/model_identity from a training config file if provided."""
83
  if not self.config_file:
@@ -148,6 +198,23 @@ os.environ['MODEL_IDENTITY'] = {_json.dumps(self.model_identity or "")}
148
  os.environ['SYSTEM_MESSAGE'] = {_json.dumps(self.system_message or (self.model_identity or ""))}
149
  os.environ['DEVELOPER_MESSAGE'] = {_json.dumps(self.developer_message or "")}
150
  os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
  """
153
  else:
@@ -163,6 +230,23 @@ os.environ['MODEL_IDENTITY'] = {_json.dumps(self.model_identity or "")}
163
  os.environ['SYSTEM_MESSAGE'] = {_json.dumps(self.system_message or (self.model_identity or ""))}
164
  os.environ['DEVELOPER_MESSAGE'] = {_json.dumps(self.developer_message or "")}
165
  os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
  """
168
  return env_setup
@@ -251,6 +335,56 @@ os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"
251
  description="Default reasoning effort (low|medium|high)"
252
  )
253
  logger.info("✅ Set REASONING_EFFORT variable")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  except Exception as e:
256
  logger.error(f"❌ Failed to set model variables: {e}")
@@ -542,7 +676,7 @@ os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"
542
  self.api.add_space_secret(
543
  repo_id=self.space_id,
544
  key="HF_TOKEN",
545
- value=self.hf_token,
546
  description="Hugging Face token for model access"
547
  )
548
  logger.info("✅ Successfully set HF_TOKEN secret via API")
@@ -583,6 +717,27 @@ os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"
583
  logger.info(f" SYSTEM_MESSAGE={self.system_message}")
584
  if self.developer_message:
585
  logger.info(f" DEVELOPER_MESSAGE={self.developer_message}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
586
 
587
  logger.info(f"\n🔧 To set secrets in your Space:")
588
  logger.info(f"1. Go to your Space settings: {self.space_url}/settings")
@@ -687,23 +842,59 @@ def main():
687
 
688
  parser = argparse.ArgumentParser(description="Deploy demo space to Hugging Face Spaces")
689
  parser.add_argument("--hf-token", required=True, help="Hugging Face token")
 
 
 
 
 
690
  parser.add_argument("--hf-username", required=True, help="Hugging Face username")
691
  parser.add_argument("--model-id", required=True, help="Model ID to deploy demo for")
692
  parser.add_argument("--subfolder", default="int4", help="Model subfolder (default: int4)")
693
  parser.add_argument("--space-name", help="Custom space name (optional)")
694
  parser.add_argument("--demo-type", choices=["smol", "gpt"], help="Demo type: 'smol' for SmolLM, 'gpt' for GPT-OSS (auto-detected if not specified)")
695
  parser.add_argument("--config-file", help="Path to the training config file to import context (system/developer/model_identity)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
696
 
697
  args = parser.parse_args()
698
 
699
  deployer = DemoSpaceDeployer(
700
  hf_token=args.hf_token,
 
701
  hf_username=args.hf_username,
702
  model_id=args.model_id,
703
  subfolder=args.subfolder,
704
  space_name=args.space_name,
705
  demo_type=args.demo_type,
706
  config_file=args.config_file,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
707
  )
708
 
709
  success = deployer.deploy()
 
37
  class DemoSpaceDeployer:
38
  """Deploy demo space to Hugging Face Spaces"""
39
 
40
+ def __init__(
41
+ self,
42
+ hf_token: str,
43
+ # Token used for API actions that create/update the Space (write perms)
44
+ hf_username: str,
45
+ model_id: str,
46
+ subfolder: str = "int4",
47
+ space_name: Optional[str] = None,
48
+ demo_type: Optional[str] = None,
49
+ config_file: Optional[str] = None,
50
+ # Optional token used as the Space's HF_TOKEN secret (read-only recommended)
51
+ space_secret_token: Optional[str] = None,
52
+ # Examples configuration
53
+ examples_type: Optional[str] = None,
54
+ disable_examples: Optional[bool] = None,
55
+ examples_json: Optional[str] = None,
56
+ # Branding overrides
57
+ brand_owner_name: Optional[str] = None,
58
+ brand_team_name: Optional[str] = None,
59
+ brand_discord_url: Optional[str] = None,
60
+ brand_hf_org: Optional[str] = None,
61
+ brand_hf_label: Optional[str] = None,
62
+ brand_hf_url: Optional[str] = None,
63
+ brand_gh_org: Optional[str] = None,
64
+ brand_gh_label: Optional[str] = None,
65
+ brand_gh_url: Optional[str] = None,
66
+ brand_project_name: Optional[str] = None,
67
+ brand_project_url: Optional[str] = None,
68
+ ):
69
  self.hf_token = hf_token
70
+ # The token we will store in the Space secrets. Defaults to hf_token if not provided
71
+ self.space_secret_token = space_secret_token or hf_token
72
  self.hf_username = hf_username
73
  # Allow passing just a repo name without username and auto-prefix
74
  self.model_id = model_id if "/" in model_id else f"{hf_username}/{model_id}"
 
83
  self.developer_message: Optional[str] = None
84
  self.model_identity: Optional[str] = None
85
  self.reasoning_effort: Optional[str] = None
86
+ # Examples context
87
+ self.examples_type: Optional[str] = (examples_type or None)
88
+ self.disable_examples: Optional[bool] = (disable_examples if disable_examples is not None else None)
89
+ self.examples_json: Optional[str] = (examples_json or None)
90
 
91
  # Determine demo type from model_id if not provided
92
  if demo_type is None:
 
110
  except Exception as e:
111
  logger.warning(f"Could not load config messages: {e}")
112
 
113
+ # Branding defaults (can be overridden via CLI)
114
+ self.brand_owner_name = brand_owner_name or self.hf_username or "Tonic"
115
+ self.brand_team_name = brand_team_name or f"Team{self.brand_owner_name}"
116
+ self.brand_discord_url = brand_discord_url or "https://discord.gg/qdfnvSPcqP"
117
+ # HF org/link
118
+ _default_hf_org = brand_hf_org or self.hf_username or "MultiTransformer"
119
+ self.brand_hf_org = _default_hf_org
120
+ self.brand_hf_label = brand_hf_label or self.brand_hf_org
121
+ self.brand_hf_url = brand_hf_url or f"https://huggingface.co/{self.brand_hf_org}"
122
+ # GitHub org/link
123
+ _default_gh_org = brand_gh_org or self.hf_username or "tonic-ai"
124
+ self.brand_gh_org = _default_gh_org
125
+ self.brand_gh_label = brand_gh_label or self.brand_gh_org
126
+ self.brand_gh_url = brand_gh_url or f"https://github.com/{self.brand_gh_org}"
127
+ # Project link
128
+ self.brand_project_name = brand_project_name or "MultiTonic"
129
+ self.brand_project_url = brand_project_url or "https://github.com/MultiTonic"
130
+
131
  def _load_config_messages(self) -> None:
132
  """Load system/developer/model_identity from a training config file if provided."""
133
  if not self.config_file:
 
198
  os.environ['SYSTEM_MESSAGE'] = {_json.dumps(self.system_message or (self.model_identity or ""))}
199
  os.environ['DEVELOPER_MESSAGE'] = {_json.dumps(self.developer_message or "")}
200
  os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"))}
201
+ {"os.environ['EXAMPLES_TYPE'] = " + _json.dumps(self.examples_type) + "\n" if self.examples_type else ''}
202
+ {"os.environ['DISABLE_EXAMPLES'] = 'true'\n" if self.disable_examples else ("os.environ['DISABLE_EXAMPLES'] = 'false'\n" if self.disable_examples is not None else '')}
203
+ {"os.environ['EXAMPLES_JSON'] = " + _json.dumps(self.examples_json) + "\n" if self.examples_json else ''}
204
+
205
+ # Branding/owner variables
206
+ os.environ['HF_USERNAME'] = {_json.dumps(self.hf_username)}
207
+ os.environ['BRAND_OWNER_NAME'] = {_json.dumps(self.brand_owner_name)}
208
+ os.environ['BRAND_TEAM_NAME'] = {_json.dumps(self.brand_team_name)}
209
+ os.environ['BRAND_DISCORD_URL'] = {_json.dumps(self.brand_discord_url)}
210
+ os.environ['BRAND_HF_ORG'] = {_json.dumps(self.brand_hf_org)}
211
+ os.environ['BRAND_HF_LABEL'] = {_json.dumps(self.brand_hf_label)}
212
+ os.environ['BRAND_HF_URL'] = {_json.dumps(self.brand_hf_url)}
213
+ os.environ['BRAND_GH_ORG'] = {_json.dumps(self.brand_gh_org)}
214
+ os.environ['BRAND_GH_LABEL'] = {_json.dumps(self.brand_gh_label)}
215
+ os.environ['BRAND_GH_URL'] = {_json.dumps(self.brand_gh_url)}
216
+ os.environ['BRAND_PROJECT_NAME'] = {_json.dumps(self.brand_project_name)}
217
+ os.environ['BRAND_PROJECT_URL'] = {_json.dumps(self.brand_project_url)}
218
 
219
  """
220
  else:
 
230
  os.environ['SYSTEM_MESSAGE'] = {_json.dumps(self.system_message or (self.model_identity or ""))}
231
  os.environ['DEVELOPER_MESSAGE'] = {_json.dumps(self.developer_message or "")}
232
  os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"))}
233
+ {"os.environ['EXAMPLES_TYPE'] = " + _json.dumps(self.examples_type) + "\n" if self.examples_type else ''}
234
+ {"os.environ['DISABLE_EXAMPLES'] = 'true'\n" if self.disable_examples else ("os.environ['DISABLE_EXAMPLES'] = 'false'\n" if self.disable_examples is not None else '')}
235
+ {"os.environ['EXAMPLES_JSON'] = " + _json.dumps(self.examples_json) + "\n" if self.examples_json else ''}
236
+
237
+ # Branding/owner variables
238
+ os.environ['HF_USERNAME'] = {_json.dumps(self.hf_username)}
239
+ os.environ['BRAND_OWNER_NAME'] = {_json.dumps(self.brand_owner_name)}
240
+ os.environ['BRAND_TEAM_NAME'] = {_json.dumps(self.brand_team_name)}
241
+ os.environ['BRAND_DISCORD_URL'] = {_json.dumps(self.brand_discord_url)}
242
+ os.environ['BRAND_HF_ORG'] = {_json.dumps(self.brand_hf_org)}
243
+ os.environ['BRAND_HF_LABEL'] = {_json.dumps(self.brand_hf_label)}
244
+ os.environ['BRAND_HF_URL'] = {_json.dumps(self.brand_hf_url)}
245
+ os.environ['BRAND_GH_ORG'] = {_json.dumps(self.brand_gh_org)}
246
+ os.environ['BRAND_GH_LABEL'] = {_json.dumps(self.brand_gh_label)}
247
+ os.environ['BRAND_GH_URL'] = {_json.dumps(self.brand_gh_url)}
248
+ os.environ['BRAND_PROJECT_NAME'] = {_json.dumps(self.brand_project_name)}
249
+ os.environ['BRAND_PROJECT_URL'] = {_json.dumps(self.brand_project_url)}
250
 
251
  """
252
  return env_setup
 
335
  description="Default reasoning effort (low|medium|high)"
336
  )
337
  logger.info("✅ Set REASONING_EFFORT variable")
338
+
339
+ # Branding variables
340
+ branding_vars = {
341
+ "HF_USERNAME": self.hf_username,
342
+ "BRAND_OWNER_NAME": self.brand_owner_name,
343
+ "BRAND_TEAM_NAME": self.brand_team_name,
344
+ "BRAND_DISCORD_URL": self.brand_discord_url,
345
+ "BRAND_HF_ORG": self.brand_hf_org,
346
+ "BRAND_HF_LABEL": self.brand_hf_label,
347
+ "BRAND_HF_URL": self.brand_hf_url,
348
+ "BRAND_GH_ORG": self.brand_gh_org,
349
+ "BRAND_GH_LABEL": self.brand_gh_label,
350
+ "BRAND_GH_URL": self.brand_gh_url,
351
+ "BRAND_PROJECT_NAME": self.brand_project_name,
352
+ "BRAND_PROJECT_URL": self.brand_project_url,
353
+ }
354
+ for key, value in branding_vars.items():
355
+ self.api.add_space_variable(
356
+ repo_id=self.space_id,
357
+ key=key,
358
+ value=value,
359
+ description=f"Branding: {key}"
360
+ )
361
+ logger.info("✅ Set branding variables")
362
+
363
+ # Examples variables
364
+ if self.examples_type:
365
+ self.api.add_space_variable(
366
+ repo_id=self.space_id,
367
+ key="EXAMPLES_TYPE",
368
+ value=self.examples_type,
369
+ description="Examples pack type (e.g., general|medical)"
370
+ )
371
+ logger.info(f"✅ Set EXAMPLES_TYPE={self.examples_type}")
372
+ if self.disable_examples is not None:
373
+ self.api.add_space_variable(
374
+ repo_id=self.space_id,
375
+ key="DISABLE_EXAMPLES",
376
+ value=("true" if self.disable_examples else "false"),
377
+ description="Disable built-in examples"
378
+ )
379
+ logger.info(f"✅ Set DISABLE_EXAMPLES={self.disable_examples}")
380
+ if self.examples_json:
381
+ self.api.add_space_variable(
382
+ repo_id=self.space_id,
383
+ key="EXAMPLES_JSON",
384
+ value=self.examples_json,
385
+ description="Custom examples JSON override"
386
+ )
387
+ logger.info("✅ Set EXAMPLES_JSON override")
388
 
389
  except Exception as e:
390
  logger.error(f"❌ Failed to set model variables: {e}")
 
676
  self.api.add_space_secret(
677
  repo_id=self.space_id,
678
  key="HF_TOKEN",
679
+ value=self.space_secret_token,
680
  description="Hugging Face token for model access"
681
  )
682
  logger.info("✅ Successfully set HF_TOKEN secret via API")
 
717
  logger.info(f" SYSTEM_MESSAGE={self.system_message}")
718
  if self.developer_message:
719
  logger.info(f" DEVELOPER_MESSAGE={self.developer_message}")
720
+ # Branding variables
721
+ logger.info(f" HF_USERNAME={self.hf_username}")
722
+ logger.info(f" BRAND_OWNER_NAME={self.brand_owner_name}")
723
+ logger.info(f" BRAND_TEAM_NAME={self.brand_team_name}")
724
+ logger.info(f" BRAND_DISCORD_URL={self.brand_discord_url}")
725
+ logger.info(f" BRAND_HF_ORG={self.brand_hf_org}")
726
+ logger.info(f" BRAND_HF_LABEL={self.brand_hf_label}")
727
+ logger.info(f" BRAND_HF_URL={self.brand_hf_url}")
728
+ logger.info(f" BRAND_GH_ORG={self.brand_gh_org}")
729
+ logger.info(f" BRAND_GH_LABEL={self.brand_gh_label}")
730
+ logger.info(f" BRAND_GH_URL={self.brand_gh_url}")
731
+ logger.info(f" BRAND_PROJECT_NAME={self.brand_project_name}")
732
+ logger.info(f" BRAND_PROJECT_URL={self.brand_project_url}")
733
+
734
+ # Examples variables
735
+ if self.examples_type:
736
+ logger.info(f" EXAMPLES_TYPE={self.examples_type}")
737
+ if self.disable_examples is not None:
738
+ logger.info(f" DISABLE_EXAMPLES={'true' if self.disable_examples else 'false'}")
739
+ if self.examples_json:
740
+ logger.info(f" EXAMPLES_JSON={self.examples_json}")
741
 
742
  logger.info(f"\n🔧 To set secrets in your Space:")
743
  logger.info(f"1. Go to your Space settings: {self.space_url}/settings")
 
842
 
843
  parser = argparse.ArgumentParser(description="Deploy demo space to Hugging Face Spaces")
844
  parser.add_argument("--hf-token", required=True, help="Hugging Face token")
845
+ parser.add_argument(
846
+ "--space-secret-token",
847
+ required=False,
848
+ help="Token to store as Space secret HF_TOKEN (defaults to --hf-token). Use a READ token here for least privilege.",
849
+ )
850
  parser.add_argument("--hf-username", required=True, help="Hugging Face username")
851
  parser.add_argument("--model-id", required=True, help="Model ID to deploy demo for")
852
  parser.add_argument("--subfolder", default="int4", help="Model subfolder (default: int4)")
853
  parser.add_argument("--space-name", help="Custom space name (optional)")
854
  parser.add_argument("--demo-type", choices=["smol", "gpt"], help="Demo type: 'smol' for SmolLM, 'gpt' for GPT-OSS (auto-detected if not specified)")
855
  parser.add_argument("--config-file", help="Path to the training config file to import context (system/developer/model_identity)")
856
+ # Examples configuration
857
+ parser.add_argument("--examples-type", choices=["general", "medical"], help="Examples pack to enable in the demo UI")
858
+ parser.add_argument("--disable-examples", action="store_true", help="Disable rendering of example prompts in the UI")
859
+ parser.add_argument("--examples-json", help="Custom examples JSON (list[str]) to override built-in examples")
860
+ # Branding customization
861
+ parser.add_argument("--brand-owner-name", help="Owner name shown in the UI title (defaults to HF username)")
862
+ parser.add_argument("--brand-team-name", help="Team name shown in Join Us (defaults to Team<owner>)")
863
+ parser.add_argument("--brand-discord-url", help="Discord invite URL for Join Us section")
864
+ parser.add_argument("--brand-hf-org", help="Hugging Face org/username to link in Join Us")
865
+ parser.add_argument("--brand-hf-label", help="Label for the HF link (defaults to org)")
866
+ parser.add_argument("--brand-hf-url", help="Custom HF link URL (defaults to https://huggingface.co/<org>)")
867
+ parser.add_argument("--brand-gh-org", help="GitHub org/username to link in Join Us")
868
+ parser.add_argument("--brand-gh-label", help="Label for the GitHub link (defaults to org)")
869
+ parser.add_argument("--brand-gh-url", help="Custom GitHub link URL (defaults to https://github.com/<org>)")
870
+ parser.add_argument("--brand-project-name", help="Project name to link in Join Us")
871
+ parser.add_argument("--brand-project-url", help="Project URL to link in Join Us")
872
 
873
  args = parser.parse_args()
874
 
875
  deployer = DemoSpaceDeployer(
876
  hf_token=args.hf_token,
877
+ space_secret_token=(args.space_secret_token or None),
878
  hf_username=args.hf_username,
879
  model_id=args.model_id,
880
  subfolder=args.subfolder,
881
  space_name=args.space_name,
882
  demo_type=args.demo_type,
883
  config_file=args.config_file,
884
+ examples_type=args.examples_type,
885
+ disable_examples=(True if getattr(args, 'disable_examples', False) else None),
886
+ examples_json=args.examples_json,
887
+ brand_owner_name=args.brand_owner_name,
888
+ brand_team_name=args.brand_team_name,
889
+ brand_discord_url=args.brand_discord_url,
890
+ brand_hf_org=args.brand_hf_org,
891
+ brand_hf_label=args.brand_hf_label,
892
+ brand_hf_url=args.brand_hf_url,
893
+ brand_gh_org=args.brand_gh_org,
894
+ brand_gh_label=args.brand_gh_label,
895
+ brand_gh_url=args.brand_gh_url,
896
+ brand_project_name=args.brand_project_name,
897
+ brand_project_url=args.brand_project_url,
898
  )
899
 
900
  success = deployer.deploy()
templates/spaces/demo_gpt/app.py CHANGED
@@ -6,7 +6,9 @@ import spaces
6
  import re
7
  import logging
8
  import os
 
9
  from peft import PeftModel
 
10
 
11
  # ----------------------------------------------------------------------
12
  # Environment Variables Configuration
@@ -34,6 +36,115 @@ print(f" Model Name: {MODEL_NAME}")
34
  print(f" Model Subfolder: {MODEL_SUBFOLDER}")
35
  print(f" Use LoRA: {USE_LORA}")
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  # ----------------------------------------------------------------------
38
  # KaTeX delimiter config for Gradio
39
  # ----------------------------------------------------------------------
@@ -94,8 +205,9 @@ except Exception as e:
94
  print(f"❌ Error loading model: {e}")
95
  raise e
96
 
97
- def format_conversation_history(chat_history):
98
- messages = []
 
99
  for item in chat_history:
100
  role = item["role"]
101
  content = item["content"]
@@ -104,7 +216,7 @@ def format_conversation_history(chat_history):
104
  messages.append({"role": role, "content": content})
105
  return messages
106
 
107
- def format_analysis_response(text):
108
  """Enhanced response formatting with better structure and LaTeX support."""
109
  # Look for analysis section followed by final response
110
  m = re.search(r"analysis(.*?)assistantfinal", text, re.DOTALL | re.IGNORECASE)
@@ -136,7 +248,20 @@ def format_analysis_response(text):
136
  return cleaned
137
 
138
  @spaces.GPU(duration=60)
139
- def generate_response(input_data, chat_history, max_new_tokens, model_identity, system_prompt, developer_prompt, reasoning_effort, temperature, top_p, top_k, repetition_penalty):
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  if not input_data.strip():
141
  yield "Please enter a prompt."
142
  return
@@ -236,74 +361,79 @@ def generate_response(input_data, chat_history, max_new_tokens, model_identity,
236
  logging.exception("Generation streaming failed")
237
  yield f"❌ Error during generation: {e}"
238
 
239
- demo = gr.ChatInterface(
240
- fn=generate_response,
241
- additional_inputs=[
242
- gr.Slider(label="Max new tokens", minimum=64, maximum=4096, step=1, value=2048),
243
- gr.Textbox(
244
- label="Model Identity",
245
- value=MODEL_IDENTITY,
246
- lines=3,
247
- placeholder="Optional identity/persona for the model"
248
- ),
249
- gr.Textbox(
250
- label="System Prompt",
251
- value=DEFAULT_SYSTEM_PROMPT,
252
- lines=4,
253
- placeholder="Change system prompt"
254
- ),
255
- gr.Textbox(
256
- label="Developer Prompt",
257
- value=DEFAULT_DEVELOPER_PROMPT,
258
- lines=4,
259
- placeholder="Optional developer instructions"
260
- ),
261
- gr.Dropdown(
262
- label="Reasoning Effort",
263
- choices=["low", "medium", "high"],
264
- value=DEFAULT_REASONING_EFFORT,
265
- interactive=True,
266
- ),
267
- gr.Slider(label="Temperature", minimum=0.1, maximum=2.0, step=0.1, value=0.7),
268
- gr.Slider(label="Top-p", minimum=0.05, maximum=1.0, step=0.05, value=0.9),
269
- gr.Slider(label="Top-k", minimum=1, maximum=100, step=1, value=50),
270
- gr.Slider(label="Repetition Penalty", minimum=1.0, maximum=2.0, step=0.05, value=1.0)
271
- ],
272
- examples=[
273
- [{"text": "Explain Newton's laws clearly and concisely with mathematical formulas"}],
274
- [{"text": "Write a Python function to calculate the Fibonacci sequence"}],
275
- [{"text": "What are the benefits of open weight AI models? Include analysis."}],
276
- [{"text": "Solve this equation: $x^2 + 5x + 6 = 0$"}],
277
- ],
278
- cache_examples=False,
279
- type="messages",
280
- description=f"""
281
-
282
- # 🙋🏻‍♂️Welcome to 🌟{MODEL_NAME} Demo !
283
 
284
- **Model**: `{LORA_MODEL_ID}`
285
- **Base**: `{BASE_MODEL_ID}`
 
 
286
 
287
- **Enhanced Features:**
288
- - 🧠 **Advanced Reasoning**: Detailed analysis and step-by-step thinking
289
- - 📊 **LaTeX Support**: Mathematical formulas rendered beautifully (use `$` or `$$`)
290
- - 🎯 **Improved Formatting**: Clear separation of reasoning and final responses
291
- - 📝 **Smart Logging**: Better error handling and request tracking
 
 
 
 
 
292
 
293
- 💡 **Usage Tips:**
294
- - Adjust reasoning level in system prompt (e.g., "Reasoning: high")
295
- - Use LaTeX for math: `$E = mc^2$` or `$$\\int x^2 dx$$`
296
- - Wait a couple of seconds initially for model loading
297
- """,
298
- fill_height=True,
299
- textbox=gr.Textbox(
300
- label="Query Input",
301
- placeholder="Type your prompt (supports LaTeX: $x^2 + y^2 = z^2$)"
302
- ),
303
- stop_btn="Stop Generation",
304
- multimodal=False,
305
- theme=gr.themes.Soft()
306
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
 
308
  if __name__ == "__main__":
309
- demo.launch(share=True)
 
6
  import re
7
  import logging
8
  import os
9
+ import json
10
  from peft import PeftModel
11
+ from typing import Any, Dict, List, Generator
12
 
13
  # ----------------------------------------------------------------------
14
  # Environment Variables Configuration
 
36
  print(f" Model Subfolder: {MODEL_SUBFOLDER}")
37
  print(f" Use LoRA: {USE_LORA}")
38
 
39
+ # ----------------------------------------------------------------------
40
+ # Branding/Owner customization (override via Space variables)
41
+ # ----------------------------------------------------------------------
42
+
43
+ HF_USERNAME = os.getenv("HF_USERNAME", "")
44
+ BRAND_OWNER_NAME = os.getenv("BRAND_OWNER_NAME", HF_USERNAME or "Tonic")
45
+ BRAND_TEAM_NAME = os.getenv("BRAND_TEAM_NAME", f"Team{BRAND_OWNER_NAME}" if BRAND_OWNER_NAME else "TeamTonic")
46
+
47
+ BRAND_DISCORD_URL = os.getenv("BRAND_DISCORD_URL", "https://discord.gg/qdfnvSPcqP")
48
+
49
+ # Hugging Face org/links
50
+ _default_hf_org = os.getenv("HF_ORG", HF_USERNAME) or "MultiTransformer"
51
+ BRAND_HF_ORG = os.getenv("BRAND_HF_ORG", _default_hf_org)
52
+ BRAND_HF_LABEL = os.getenv("BRAND_HF_LABEL", BRAND_HF_ORG)
53
+ BRAND_HF_URL = os.getenv("BRAND_HF_URL", f"https://huggingface.co/{BRAND_HF_ORG}")
54
+
55
+ # GitHub org/links
56
+ _default_gh_org = os.getenv("GITHUB_ORG", "tonic-ai")
57
+ BRAND_GH_ORG = os.getenv("BRAND_GH_ORG", _default_gh_org)
58
+ BRAND_GH_LABEL = os.getenv("BRAND_GH_LABEL", BRAND_GH_ORG)
59
+ BRAND_GH_URL = os.getenv("BRAND_GH_URL", f"https://github.com/{BRAND_GH_ORG}")
60
+
61
+ # Project link (optional)
62
+ BRAND_PROJECT_NAME = os.getenv("BRAND_PROJECT_NAME", "MultiTonic")
63
+ BRAND_PROJECT_URL = os.getenv("BRAND_PROJECT_URL", "https://github.com/MultiTonic")
64
+
65
+ # ----------------------------------------------------------------------
66
+ # Title/Description content (Markdown/HTML)
67
+ # ----------------------------------------------------------------------
68
+
69
+ TITLE_MD = f"# 🙋🏻‍♂️ Welcome to 🌟{BRAND_OWNER_NAME}'s ⚕️{MODEL_NAME} Demo !"
70
+
71
+ DESCRIPTION_MD = f"""
72
+ **Model**: `{LORA_MODEL_ID}`
73
+ **Base**: `{BASE_MODEL_ID}`
74
+
75
+ ✨ **Enhanced Features:**
76
+ - 🧠 **Advanced Reasoning**: Detailed analysis and step-by-step thinking
77
+ - 📊 **LaTeX Support**: Mathematical formulas rendered beautifully (use `$` or `$$`)
78
+ - 🎯 **Improved Formatting**: Clear separation of reasoning and final responses
79
+ - 📝 **Smart Logging**: Better error handling and request tracking
80
+
81
+ 💡 **Usage Tips:**
82
+ - Adjust reasoning level in system prompt (e.g., "Reasoning: high")
83
+ - Use LaTeX for math: `$E = mc^2$` or `$$\\int x^2 dx$$`
84
+ - Wait a couple of seconds initially for model loading
85
+ """
86
+
87
+ # ----------------------------------------------------------------------
88
+ # Examples configuration with robust fallbacks
89
+ # ----------------------------------------------------------------------
90
+
91
+ def _normalize_examples(string_items: List[str]) -> List[List[Dict[str, str]]]:
92
+ """Convert a list of strings to Gradio ChatInterface examples format."""
93
+ return [[{"text": s}] for s in string_items]
94
+
95
+ DEFAULT_EXAMPLES_GENERAL: List[str] = [
96
+ "Explain Newton's laws clearly and concisely with mathematical formulas",
97
+ "Write a Python function to calculate the Fibonacci sequence",
98
+ "What are the benefits of open weight AI models? Include analysis.",
99
+ "Solve this equation: $x^2 + 5x + 6 = 0$",
100
+ ]
101
+
102
+ DEFAULT_EXAMPLES_MEDICAL: List[str] = [
103
+ "A 68-year-old man complains of several blisters arising over the back and trunk for the preceding 2 weeks. He takes no medications and has not noted systemic symptoms such as fever, sore throat, weight loss, or fatigue. The general physical examination is normal. The oral mucosa and the lips are normal. Several 2- to 3-cm bullae are present over the trunk and back. A few excoriations where the blisters have ruptured are present. The remainder of the skin is normal, without erythema or scale. What is the best diagnostic approach at this time?",
104
+ "A 28-year-old woman, gravida 2, para 1, at 40 weeks of gestation is admitted to the hospital in active labor. The patient has attended many prenatal appointments and followed her physician's advice about screening for diseases, laboratory testing, diet, and exercise. Her pregnancy has been uncomplicated. She has no history of a serious illness. Her first child was delivered via normal vaginal delivery. Her vital signs are within normal limits. Cervical examination shows 100% effacement and 10 cm dilation. A cardiotocograph is shown. Which of the following is the most appropriate initial step in management?",
105
+ "An 18-year-old woman has eaten homemade preserves. Eighteen hours later, she develops diplopia, dysarthria, and dysphagia. She presents to the emergency room for assessment and on examination her blood pressure is 112/74 mmHg, heart rate 110/min, and respirations 20/min. The pertinent findings are abnormal extraocular movements due to cranial nerve palsies, difficulty swallowing and a change in her voice. The strength in her arms is 4/5 and 5/5 in her legs, and the reflexes are normal. Which of the following is the most likely causative organism?",
106
+ "What are you & who made you?",
107
+ ]
108
+
109
+ _examples_type_env = os.getenv("EXAMPLES_TYPE", "medical").strip().lower()
110
+ _disable_examples = os.getenv("DISABLE_EXAMPLES", "false").strip().lower() in {"1", "true", "yes"}
111
+ _examples_json_raw = os.getenv("EXAMPLES_JSON") or os.getenv("CUSTOM_EXAMPLES_JSON")
112
+
113
+ EXAMPLES_FINAL: List[List[Dict[str, str]]]
114
+ if _disable_examples:
115
+ EXAMPLES_FINAL = []
116
+ else:
117
+ custom_items: List[str] = []
118
+ if _examples_json_raw:
119
+ try:
120
+ parsed = json.loads(_examples_json_raw)
121
+ if isinstance(parsed, list) and parsed:
122
+ # Accept list[str]
123
+ if all(isinstance(x, str) for x in parsed):
124
+ custom_items = parsed # type: ignore[assignment]
125
+ # Accept list[dict{"text": str}]
126
+ elif all(isinstance(x, dict) and "text" in x and isinstance(x["text"], str) for x in parsed):
127
+ custom_items = [x["text"] for x in parsed]
128
+ except Exception:
129
+ custom_items = []
130
+
131
+ if custom_items:
132
+ EXAMPLES_FINAL = _normalize_examples(custom_items)
133
+ else:
134
+ if _examples_type_env in {"med", "medical", "health"}:
135
+ EXAMPLES_FINAL = _normalize_examples(DEFAULT_EXAMPLES_MEDICAL)
136
+ else:
137
+ EXAMPLES_FINAL = _normalize_examples(DEFAULT_EXAMPLES_GENERAL)
138
+
139
+ JOIN_US_MD = f"""
140
+ ## Join us :
141
+ 🌟{BRAND_TEAM_NAME}🌟 is always making cool demos! Join our active builder's 🛠️community 👻
142
+ [Join us on Discord]({BRAND_DISCORD_URL})
143
+ On 🤗Hugging Face: [{BRAND_HF_LABEL}]({BRAND_HF_URL})
144
+ On 🌐GitHub: [{BRAND_GH_LABEL}]({BRAND_GH_URL}) & contribute to 🌟 [{BRAND_PROJECT_NAME}]({BRAND_PROJECT_URL})
145
+ 🤗 Big thanks to the Hugging Face team for the community support 🤗
146
+ """
147
+
148
  # ----------------------------------------------------------------------
149
  # KaTeX delimiter config for Gradio
150
  # ----------------------------------------------------------------------
 
205
  print(f"❌ Error loading model: {e}")
206
  raise e
207
 
208
+ def format_conversation_history(chat_history: List[Dict[str, Any]]) -> List[Dict[str, str]]:
209
+ """Normalize Gradio chat history items into a list of role/content messages."""
210
+ messages: List[Dict[str, str]] = []
211
  for item in chat_history:
212
  role = item["role"]
213
  content = item["content"]
 
216
  messages.append({"role": role, "content": content})
217
  return messages
218
 
219
+ def format_analysis_response(text: str) -> str:
220
  """Enhanced response formatting with better structure and LaTeX support."""
221
  # Look for analysis section followed by final response
222
  m = re.search(r"analysis(.*?)assistantfinal", text, re.DOTALL | re.IGNORECASE)
 
248
  return cleaned
249
 
250
  @spaces.GPU(duration=60)
251
+ def generate_response(
252
+ input_data: str,
253
+ chat_history: List[Dict[str, Any]],
254
+ max_new_tokens: int,
255
+ model_identity: str,
256
+ system_prompt: str,
257
+ developer_prompt: str,
258
+ reasoning_effort: str,
259
+ temperature: float,
260
+ top_p: float,
261
+ top_k: int,
262
+ repetition_penalty: float,
263
+ ) -> Generator[str, None, None]:
264
+ """Stream tokens as they are generated, yielding formatted partial/final outputs."""
265
  if not input_data.strip():
266
  yield "Please enter a prompt."
267
  return
 
361
  logging.exception("Generation streaming failed")
362
  yield f"❌ Error during generation: {e}"
363
 
364
+ # ----------------------------------------------------------------------
365
+ # UI/Styling: CSS + custom Chatbot + two-column description
366
+ # ----------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
 
368
+ APP_CSS = """
369
+ #main_chatbot {height: calc(100vh - 120px);} /* Increase chatbot viewport height */
370
+ .gradio-container {min-height: 100vh;}
371
+ """
372
 
373
+ description_html = f"""
374
+ <div style=\"display:flex; gap: 16px; align-items:flex-start; flex-wrap: wrap\">
375
+ <div style=\"flex: 1 1 60%; min-width: 300px;\">
376
+ {DESCRIPTION_MD}
377
+ </div>
378
+ <div style=\"flex: 1 1 35%; min-width: 260px;\">
379
+ {JOIN_US_MD}
380
+ </div>
381
+ </div>
382
+ """
383
 
384
+ custom_chatbot = gr.Chatbot(label="Chatbot", elem_id="main_chatbot", latex_delimiters=LATEX_DELIMS)
385
+
386
+ demo = gr.ChatInterface(
387
+ fn=generate_response,
388
+ chatbot=custom_chatbot,
389
+ title=f"🙋🏻‍♂️ Welcome to 🌟{BRAND_OWNER_NAME}'s ⚕️{MODEL_NAME} Demo !",
390
+ description=description_html,
391
+ additional_inputs=[
392
+ gr.Slider(label="Max new tokens", minimum=64, maximum=4096, step=1, value=2048),
393
+ gr.Textbox(
394
+ label="🪪Model Identity",
395
+ value=MODEL_IDENTITY,
396
+ lines=1,
397
+ placeholder="Optional identity/persona for the model"
398
+ ),
399
+ gr.Textbox(
400
+ label="🤖System Prompt",
401
+ value=DEFAULT_SYSTEM_PROMPT,
402
+ lines=1,
403
+ placeholder="Change system prompt"
404
+ ),
405
+ gr.Textbox(
406
+ label="👨🏻‍💻Developer Prompt",
407
+ value=DEFAULT_DEVELOPER_PROMPT,
408
+ lines=1,
409
+ placeholder="Optional developer instructions"
410
+ ),
411
+ gr.Dropdown(
412
+ label="🧠Reasoning Effort",
413
+ choices=["low", "medium", "high"],
414
+ value=DEFAULT_REASONING_EFFORT,
415
+ interactive=True,
416
+ ),
417
+ gr.Slider(label="🌡️Temperature", minimum=0.1, maximum=2.0, step=0.1, value=0.7),
418
+ gr.Slider(label="↗️Top-p", minimum=0.05, maximum=1.0, step=0.05, value=0.9),
419
+ gr.Slider(label="🔝Top-k", minimum=1, maximum=100, step=1, value=50),
420
+ gr.Slider(label="🦜Repetition Penalty", minimum=1.0, maximum=2.0, step=0.05, value=1.0),
421
+ ],
422
+ additional_inputs_accordion=gr.Accordion(label="🔧Advanced Inputs", open=False),
423
+ examples=EXAMPLES_FINAL,
424
+ cache_examples=False,
425
+ type="messages",
426
+ fill_height=True,
427
+ fill_width=True,
428
+ textbox=gr.Textbox(
429
+ label="Query Input",
430
+ placeholder="Type your prompt (supports LaTeX: $x^2 + y^2 = z^2$)"
431
+ ),
432
+ stop_btn="Stop Generation",
433
+ multimodal=False,
434
+ theme=gr.themes.Soft(),
435
+ css=APP_CSS,
436
+ )
437
 
438
  if __name__ == "__main__":
439
+ demo.launch(mcp_server=True, share=True)
templates/spaces/demo_smol/app.py CHANGED
@@ -15,16 +15,44 @@ torch.set_default_dtype(torch.float32)
15
  logging.basicConfig(level=logging.INFO)
16
  logger = logging.getLogger(__name__)
17
 
 
 
 
 
18
  # Get model ID from environment variable or use default
19
  MAIN_MODEL_ID = os.getenv("HF_MODEL_ID", "Tonic/petite-elle-L-aime-3-sft")
20
  MODEL_SUBFOLDER = os.getenv("MODEL_SUBFOLDER", "int4") # Default to int4 for CPU deployment
21
  MODEL_NAME = os.getenv("MODEL_NAME", "SmolLM3 Fine-tuned Model")
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
24
  model = None
25
  tokenizer = None
26
- DEFAULT_SYSTEM_PROMPT = "Tu es TonicIA, un assistant francophone rigoureux et bienveillant."
27
- title = f"# 🤖 {MODEL_NAME} - Chat Interface"
 
 
 
28
  description = f"A fine-tuned version of SmolLM3-3B optimized for conversations. This is the {MODEL_SUBFOLDER} quantized version for efficient deployment."
29
  presentation1 = """
30
  ### 🎯 Features
@@ -68,6 +96,46 @@ def download_chat_template():
68
  return None
69
 
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  def load_model():
72
  """Load the model and tokenizer"""
73
  global model, tokenizer
@@ -225,6 +293,8 @@ with gr.Blocks() as demo:
225
  placeholder="Bonjour je m'appel Tonic!",
226
  lines=2
227
  )
 
 
228
  advanced_checkbox = gr.Checkbox(label="🧪 Advanced Settings", value=False)
229
  with gr.Column(visible=False) as advanced_settings:
230
  max_length = gr.Slider(
 
15
  logging.basicConfig(level=logging.INFO)
16
  logger = logging.getLogger(__name__)
17
 
18
+ # ----------------------------------------------------------------------
19
+ # Model & UI configuration (env-driven)
20
+ # ----------------------------------------------------------------------
21
+
22
  # Get model ID from environment variable or use default
23
  MAIN_MODEL_ID = os.getenv("HF_MODEL_ID", "Tonic/petite-elle-L-aime-3-sft")
24
  MODEL_SUBFOLDER = os.getenv("MODEL_SUBFOLDER", "int4") # Default to int4 for CPU deployment
25
  MODEL_NAME = os.getenv("MODEL_NAME", "SmolLM3 Fine-tuned Model")
26
 
27
+ # Branding/Owner customization
28
+ HF_USERNAME = os.getenv("HF_USERNAME", "")
29
+ BRAND_OWNER_NAME = os.getenv("BRAND_OWNER_NAME", HF_USERNAME or "Tonic")
30
+ BRAND_TEAM_NAME = os.getenv("BRAND_TEAM_NAME", f"Team{BRAND_OWNER_NAME}" if BRAND_OWNER_NAME else "TeamTonic")
31
+
32
+ BRAND_DISCORD_URL = os.getenv("BRAND_DISCORD_URL", "https://discord.gg/qdfnvSPcqP")
33
+
34
+ _default_hf_org = os.getenv("BRAND_HF_ORG") or HF_USERNAME or "MultiTransformer"
35
+ BRAND_HF_ORG = _default_hf_org
36
+ BRAND_HF_LABEL = os.getenv("BRAND_HF_LABEL", BRAND_HF_ORG)
37
+ BRAND_HF_URL = os.getenv("BRAND_HF_URL", f"https://huggingface.co/{BRAND_HF_ORG}")
38
+
39
+ _default_gh_org = os.getenv("BRAND_GH_ORG") or HF_USERNAME or "tonic-ai"
40
+ BRAND_GH_ORG = _default_gh_org
41
+ BRAND_GH_LABEL = os.getenv("BRAND_GH_LABEL", BRAND_GH_ORG)
42
+ BRAND_GH_URL = os.getenv("BRAND_GH_URL", f"https://github.com/{BRAND_GH_ORG}")
43
+
44
+ BRAND_PROJECT_NAME = os.getenv("BRAND_PROJECT_NAME", "MultiTonic")
45
+ BRAND_PROJECT_URL = os.getenv("BRAND_PROJECT_URL", "https://github.com/MultiTonic")
46
+
47
+
48
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
49
  model = None
50
  tokenizer = None
51
+ DEFAULT_SYSTEM_PROMPT = os.getenv(
52
+ "SYSTEM_MESSAGE",
53
+ "Tu es un assistant francophone rigoureux et bienveillant."
54
+ )
55
+ title = f"# 🙋🏻‍♂️ Bienvenue chez 🌟{BRAND_OWNER_NAME} — {MODEL_NAME}"
56
  description = f"A fine-tuned version of SmolLM3-3B optimized for conversations. This is the {MODEL_SUBFOLDER} quantized version for efficient deployment."
57
  presentation1 = """
58
  ### 🎯 Features
 
96
  return None
97
 
98
 
99
+ # ----------------------------------------------------------------------
100
+ # Examples configuration with robust fallbacks
101
+ # ----------------------------------------------------------------------
102
+
103
+ def _normalize_examples_flat(string_items: List[str]) -> List[str]:
104
+ """Convert list[str] to a flat list usable by gr.Examples targeting a single input."""
105
+ return list(string_items)
106
+
107
+ DEFAULT_EXAMPLES_GENERAL: List[str] = [
108
+ "Explique les lois de Newton avec des formules mathématiques",
109
+ "Écris une fonction Python pour calculer la suite de Fibonacci",
110
+ "Quels sont les avantages des modèles IA open-weight ?",
111
+ "Résous cette équation : $x^2 + 5x + 6 = 0$",
112
+ ]
113
+
114
+ DEFAULT_EXAMPLES_MEDICAL: List[str] = [
115
+ "Un homme de 68 ans présente plusieurs bulles sur le dos et le tronc depuis 2 semaines. Quel est le meilleur diagnostic à ce stade ?",
116
+ "Une femme de 28 ans en travail à 40 semaines de gestation : quelle est la première étape de prise en charge ?",
117
+ "Une femme de 18 ans a consommé des conserves maison et présente diplopie et dysphagie. Quel est l'agent causal le plus probable ?",
118
+ ]
119
+
120
+ _examples_type_env = (os.getenv("EXAMPLES_TYPE", "general") or "general").strip().lower()
121
+ _disable_examples = os.getenv("DISABLE_EXAMPLES", "false").strip().lower() in {"1", "true", "yes"}
122
+ _examples_json_raw = os.getenv("EXAMPLES_JSON") or os.getenv("CUSTOM_EXAMPLES_JSON")
123
+
124
+ if not _disable_examples and _examples_json_raw:
125
+ try:
126
+ parsed = json.loads(_examples_json_raw)
127
+ if isinstance(parsed, list) and parsed and all(isinstance(x, str) for x in parsed):
128
+ EXAMPLES_FLAT: List[str] = _normalize_examples_flat(parsed)
129
+ else:
130
+ EXAMPLES_FLAT = []
131
+ except Exception:
132
+ EXAMPLES_FLAT = []
133
+ else:
134
+ if _examples_type_env in {"med", "medical", "santé", "sante"}:
135
+ EXAMPLES_FLAT = _normalize_examples_flat(DEFAULT_EXAMPLES_MEDICAL)
136
+ else:
137
+ EXAMPLES_FLAT = _normalize_examples_flat(DEFAULT_EXAMPLES_GENERAL)
138
+
139
  def load_model():
140
  """Load the model and tokenizer"""
141
  global model, tokenizer
 
293
  placeholder="Bonjour je m'appel Tonic!",
294
  lines=2
295
  )
296
+ if EXAMPLES_FLAT:
297
+ gr.Examples(label="Exemples", examples=EXAMPLES_FLAT, inputs=user_input)
298
  advanced_checkbox = gr.Checkbox(label="🧪 Advanced Settings", value=False)
299
  with gr.Column(visible=False) as advanced_settings:
300
  max_length = gr.Slider(