Jensin commited on
Commit
c92c8f7
Β·
verified Β·
1 Parent(s): 7c95e00

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +141 -85
app.py CHANGED
@@ -1,62 +1,97 @@
 
1
  from datasets import load_dataset
2
  import gradio as gr
3
  from gradio_client import Client
4
- import json
5
- import torch
6
  from diffusers import FluxPipeline, AutoencoderKL
7
  from live_preview_helpers import flux_pipe_call_that_returns_an_iterable_of_images
8
  import spaces
9
 
 
10
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
11
 
12
- pipe = FluxPipeline.from_pretrained("black-forest-labs/FLUX.1-dev", torch_dtype=torch.bfloat16).to(device)
13
- good_vae = AutoencoderKL.from_pretrained("black-forest-labs/FLUX.1-dev", subfolder="vae", torch_dtype=torch.bfloat16).to(device)
14
- # pipe.enable_sequential_cpu_offload()
15
- # pipe.vae.enable_slicing()
16
- # pipe.vae.enable_tiling()
17
- # pipe.to(torch.float16)
18
- pipe.flux_pipe_call_that_returns_an_iterable_of_images = flux_pipe_call_that_returns_an_iterable_of_images.__get__(pipe)
19
-
20
- llm_client = Client("HuggingFaceH4/zephyr-7b-beta") # or any other public gradio space/model
21
- # t2i_client = Client("black-forest-labs/FLUX.1-dev")
22
- # t2i_client = Client("black-forest-labs/FLUX.1-schnell")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
 
24
  ds = load_dataset("MohamedRashad/FinePersonas-Lite", split="train")
25
 
26
- prompt_template = """Generate a character with this persona description: {persona_description}
27
- In a world with this description: {world_description}
28
-
29
- Write the character in json format with the following fields:
30
- - name: The name of the character
31
- - background: The background of the character
32
- - appearance: The appearance of the character
33
- - personality: The personality of the character
34
- - skills_and_abilities: The skills and abilities of the character
35
- - goals: The goals of the character
36
- - conflicts: The conflicts of the character
37
- - backstory: The backstory of the character
38
- - current_situation: The current situation of the character
39
- - spoken_lines: The spoken lines of the character (list of strings)
40
-
41
- Don't write anything else except the character description in json format and don't include '```'.
42
- """
43
 
44
- world_description_prompt = "Generate a unique and random world description (Don't Write anything else except the world description)."
 
45
 
46
- def get_random_world_description():
47
- result = llm_client.predict(
48
- query=world_description_prompt,
49
- history=[],
50
- system="You are Qwen, created by Alibaba Cloud. You are a helpful assistant.",
51
- api_name="/model_chat",
52
- )
53
- return result[1][0][-1]
54
 
55
- def get_random_persona_description():
56
- return ds.shuffle().select([100])[0]["persona"]
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  @spaces.GPU(duration=75)
59
  def infer_flux(character_json):
 
60
  for image in pipe.flux_pipe_call_that_returns_an_iterable_of_images(
61
  prompt=character_json["appearance"],
62
  guidance_scale=3.5,
@@ -69,59 +104,80 @@ def infer_flux(character_json):
69
  ):
70
  yield image
71
 
72
- def generate_character(world_description, persona_description, progress=gr.Progress(track_tqdm=True)):
73
- result = llm_client.predict(
74
- query=prompt_template.format(
75
- persona_description=persona_description, world_description=world_description
 
 
 
76
  ),
77
- history=[],
78
- system="You are Qwen, created by Alibaba Cloud. You are a helpful assistant.",
79
- api_name="/model_chat",
80
  )
81
- output = json.loads(result[1][0][-1])
82
- return output
 
 
 
 
 
 
 
 
 
 
83
 
 
84
  app_description = """
85
- - This app generates a character in JSON format based on a persona description and a world description.
86
- - The character's appearance is generated using [FLUX-dev](https://huggingface.co/black-forest-labs/FLUX.1-dev) and the character description is generated using [Qwen2.5-72B-Instruct](https://huggingface.co/Qwen/Qwen2.5-72B-Instruct).
87
- - The persona description is randomly selected from the [FinePersonas-Lite](https://huggingface.co/datasets/MohamedRashad/FinePersonas-Lite) dataset.
 
88
 
89
- **Note:** I recommend starting with the world description (you can write one or loop over randomly generated ones) and then try different persona descriptions to generate interesting characters for the world you created.
 
90
  """
91
 
92
- with gr.Blocks(title="Character Generator", theme="Nymbo/Nymbo_Theme") as app:
93
- with gr.Column():
94
- gr.HTML("<center><h1>Character Generator</h1></center>")
95
- gr.Markdown(app_description.strip())
96
- with gr.Column():
97
- with gr.Row():
98
- world_description = gr.Textbox(lines=10, label="World Description", scale=4)
99
- persona_description = gr.Textbox(lines=10, label="Persona Description", value=get_random_persona_description(), scale=1)
100
- with gr.Row():
101
- random_world_button = gr.Button(value="Get Random World Description", variant="secondary", scale=1)
102
- submit_button = gr.Button(value="Generate Interesting Character!", variant="primary", scale=5)
103
- random_persona_button = gr.Button(value="Get Random Persona Description", variant="secondary", scale=1)
104
- with gr.Row():
105
- character_image = gr.Image(label="Character Image")
106
- character_json = gr.JSON(label="Character Description")
107
-
108
- examples = gr.Examples(
109
- [
110
- "In a world where magic is real and dragons roam the skies, a group of adventurers set out to find the legendary sword of the dragon king.",
111
- "Welcome to Aethoria, a vast and mysterious realm where the laws of physics bend to the will of ancient magic. This world is comprised of countless floating islands suspended in an endless sky, each one a unique ecosystem teeming with life and secrets. The islands of Aethoria range from lush, verdant jungles to barren, crystalline deserts. Some are no larger than a city block, while others span hundreds of miles. Connecting these disparate landmasses are shimmering bridges of pure energy, and those brave enough to venture off the beaten path can find hidden portals that instantly transport them across great distances. Aethoria's inhabitants are as diverse as its landscapes. Humans coexist with ethereal beings of light, rock-skinned giants, and shapeshifting creatures that defy classification. Ancient ruins dot the islands, hinting at long-lost civilizations and forgotten technologies that blur the line between science and sorcery. The world is powered by Aether, a mystical substance that flows through everything. Those who can harness its power become formidable mages, capable of manipulating reality itself. However, Aether is a finite resource, and its scarcity has led to conflicts between the various factions vying for control. In the skies between the islands, magnificent airships sail on currents of magic, facilitating trade and exploration. Pirates and sky raiders lurk in the cloudy depths, always on the lookout for unsuspecting prey. Deep beneath the floating lands lies the Undervoid, a dark and treacherous realm filled with nightmarish creatures and untold riches. Only the bravest adventurers dare to plumb its depths, and fewer still return to tell the tale. As an ever-present threat, the Chaos Storms rage at the edges of the known world, threatening to consume everything in their path. It falls to the heroes of Aethoria to uncover the secrets of their world and find a way to push back the encroaching darkness before it's too late. In Aethoria, every island holds a story, every creature has a secret, and every adventure could change the fate of this wondrous, imperiled world.",
112
- "In a world from my imagination, there is a city called 'Orakis'. floating in the sky on pillars of pure light. The walls of the city are made of crystal glass, constantly reflecting the colors of dawn and dusk, giving it an eternal celestial glow. The buildings breathe and change their shapes according to the seasonsβ€”they grow in spring, strengthen in summer, and begin to fade in autumn until they become mist in winter.",
113
- ],
114
- world_description,
115
  )
116
 
117
- submit_button.click(
118
- generate_character, [world_description, persona_description], outputs=[character_json]
119
- ).then(fn=infer_flux, inputs=[character_json], outputs=[character_image])
120
- random_world_button.click(
121
- get_random_world_description, outputs=[world_description]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  )
123
- random_persona_button.click(
124
- get_random_persona_description, outputs=[persona_description]
 
125
  )
126
 
127
- app.queue().launch(share=False)
 
1
+ # app.py
2
  from datasets import load_dataset
3
  import gradio as gr
4
  from gradio_client import Client
5
+ import json, os, random, torch
 
6
  from diffusers import FluxPipeline, AutoencoderKL
7
  from live_preview_helpers import flux_pipe_call_that_returns_an_iterable_of_images
8
  import spaces
9
 
10
+ # ─────────────────────────────── 1. Device ────────────────────────────────
11
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
12
 
13
+ # ─────────────────────── 2. Image / FLUX pipeline ─────────────────────────
14
+ pipe = FluxPipeline.from_pretrained(
15
+ "black-forest-labs/FLUX.1-dev",
16
+ torch_dtype=torch.bfloat16
17
+ ).to(device)
18
+ good_vae = AutoencoderKL.from_pretrained(
19
+ "black-forest-labs/FLUX.1-dev",
20
+ subfolder="vae",
21
+ torch_dtype=torch.bfloat16
22
+ ).to(device)
23
+ pipe.flux_pipe_call_that_returns_an_iterable_of_images = (
24
+ flux_pipe_call_that_returns_an_iterable_of_images.__get__(pipe)
25
+ )
26
+
27
+ # ───────────────────────── 3. LLM (Zephyr-chat) ───────────────────────────
28
+ llm_client = Client("HuggingFaceH4/zephyr-chat") # public Space
29
+ CHAT_API = llm_client.view_api()[0]["api_name"] # e.g. "/chat"
30
+
31
+ def call_llm(
32
+ user_prompt: str,
33
+ system_prompt: str = "You are Zephyr, a helpful and creative assistant.",
34
+ history: list | None = None,
35
+ temperature: float = 0.7,
36
+ top_p: float = 0.9,
37
+ max_tokens: int = 1024,
38
+ ) -> str:
39
+ """
40
+ Robust wrapper around the Zephyr chat Space.
41
+ Falls back to '...' on any error so the Gradio UI never crashes.
42
+ """
43
+ history = history or []
44
+ try:
45
+ # Zephyr-chat expects: prompt, system_prompt, history, temperature, top_p, max_new_tokens
46
+ result = llm_client.predict(
47
+ user_prompt,
48
+ system_prompt,
49
+ history,
50
+ temperature,
51
+ top_p,
52
+ max_tokens,
53
+ api_name=CHAT_API,
54
+ )
55
+ # Some Spaces return a plain string, others return the old tuple format.
56
+ return result.strip() if isinstance(result, str) else result[1][0][-1].strip()
57
+ except Exception as e:
58
+ print(f"[LLM error] {e}")
59
+ return "..."
60
 
61
+ # ───────────────────────── 4. Persona dataset ────────────────────────────
62
  ds = load_dataset("MohamedRashad/FinePersonas-Lite", split="train")
63
 
64
+ def get_random_persona_description() -> str:
65
+ idx = random.randint(0, len(ds) - 1)
66
+ return ds[idx]["persona"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ # ─────────────────────────── 5. Prompts ─────────────────────────────────
69
+ prompt_template = """Generate a character with this persona description:
70
 
71
+ {persona_description}
72
+
73
+ In a world with this description:
74
+
75
+ {world_description}
 
 
 
76
 
77
+ Write the character in JSON format with these keys:
78
+ name, background, appearance, personality, skills_and_abilities, goals,
79
+ conflicts, backstory, current_situation, spoken_lines (list of strings).
80
+
81
+ Respond with **only** the JSON (no markdown, no fencing)."""
82
+
83
+ world_description_prompt = (
84
+ "Invent a short, unique and vivid world description. "
85
+ "Respond with the description only."
86
+ )
87
+
88
+ # ─────────────────────── 6. Gradio helper funcs ─────────────────────────
89
+ def get_random_world_description() -> str:
90
+ return call_llm(world_description_prompt)
91
 
92
  @spaces.GPU(duration=75)
93
  def infer_flux(character_json):
94
+ """Stream intermediate images while FLUX denoises."""
95
  for image in pipe.flux_pipe_call_that_returns_an_iterable_of_images(
96
  prompt=character_json["appearance"],
97
  guidance_scale=3.5,
 
104
  ):
105
  yield image
106
 
107
+ def generate_character(world_description: str,
108
+ persona_description: str,
109
+ progress=gr.Progress(track_tqdm=True)):
110
+ raw = call_llm(
111
+ prompt_template.format(
112
+ persona_description=persona_description,
113
+ world_description=world_description,
114
  ),
115
+ max_tokens=1024,
 
 
116
  )
117
+ try:
118
+ return json.loads(raw)
119
+ except json.JSONDecodeError:
120
+ # One retry if the LLM hallucinated
121
+ raw = call_llm(
122
+ prompt_template.format(
123
+ persona_description=persona_description,
124
+ world_description=world_description,
125
+ ),
126
+ max_tokens=1024,
127
+ )
128
+ return json.loads(raw)
129
 
130
+ # ───────────────────────────── 7. UI ─────────────────────────────────────
131
  app_description = """
132
+ - Generates a character profile (JSON) from a world + persona description.
133
+ - **Appearance** images come from [FLUX-dev](https://huggingface.co/black-forest-labs/FLUX.1-dev).
134
+ - **Back-stories** come from [Zephyr-7B-Ξ²](https://huggingface.co/HuggingFaceH4/zephyr-7b-beta).
135
+ - Personas are sampled from [FinePersonas-Lite](https://huggingface.co/datasets/MohamedRashad/FinePersonas-Lite).
136
 
137
+ Tip β†’ Write or randomise a world, then spin the persona box to see how the same
138
+ world shapes different heroes.
139
  """
140
 
141
+ with gr.Blocks(title="Character Generator", theme="Nymbo/Nymbo_Theme") as demo:
142
+ gr.Markdown("<h1 style='text-align:center'>πŸ§™β€β™€οΈ Character Generator</h1>")
143
+ gr.Markdown(app_description.strip())
144
+
145
+ with gr.Row():
146
+ world_description = gr.Textbox(label="World Description", lines=10, scale=4)
147
+ persona_description = gr.Textbox(
148
+ label="Persona Description",
149
+ value=get_random_persona_description(),
150
+ lines=10,
151
+ scale=1,
 
 
 
 
 
 
 
 
 
 
 
 
152
  )
153
 
154
+ with gr.Row():
155
+ random_world_btn = gr.Button("πŸ”„ Random World", variant="secondary")
156
+ submit_btn = gr.Button("✨ Generate Character", variant="primary", scale=5)
157
+ random_persona_btn = gr.Button("πŸ”„ Random Persona", variant="secondary")
158
+
159
+ with gr.Row():
160
+ character_image = gr.Image(label="Character Image")
161
+ character_json = gr.JSON(label="Character Description")
162
+
163
+ # Hooks
164
+ submit_btn.click(
165
+ generate_character,
166
+ inputs=[world_description, persona_description],
167
+ outputs=[character_json],
168
+ ).then(
169
+ infer_flux,
170
+ inputs=[character_json],
171
+ outputs=[character_image],
172
+ )
173
+
174
+ random_world_btn.click(
175
+ get_random_world_description,
176
+ outputs=[world_description],
177
  )
178
+ random_persona_btn.click(
179
+ get_random_persona_description,
180
+ outputs=[persona_description],
181
  )
182
 
183
+ demo.queue().launch(share=False)