kikikita commited on
Commit
9ec6bf4
·
1 Parent(s): 0db7319

feat: enhance game interaction with custom choice input and update scene handling

Browse files
Files changed (4) hide show
  1. src/agent/runner.py +24 -2
  2. src/css.py +51 -50
  3. src/game_constructor.py +3 -1
  4. src/main.py +33 -12
src/agent/runner.py CHANGED
@@ -3,6 +3,10 @@
3
  import logging
4
  from dataclasses import asdict
5
  from typing import Dict, Optional
 
 
 
 
6
 
7
  from agent.llm_graph import GraphState, llm_game_graph
8
  from agent.models import UserState
@@ -41,15 +45,33 @@ async def process_step(
41
  if ending and ending.get("ending_reached"):
42
  ending_info = ending["ending"]
43
  if (
44
- ("description" not in ending_info
45
- or not ending_info["description"])
46
  and user_state.story_frame
47
  ):
48
  for e in user_state.story_frame.endings:
49
  if e.id == ending_info.get("id"):
50
  ending_info["description"] = e.description
51
  break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  response["ending"] = ending_info
 
53
  response["game_over"] = True
54
  else:
55
  if (
 
3
  import logging
4
  from dataclasses import asdict
5
  from typing import Dict, Optional
6
+ import uuid
7
+
8
+ from agent.image_agent import generate_image_prompt
9
+ from agent.tools import generate_scene_image
10
 
11
  from agent.llm_graph import GraphState, llm_game_graph
12
  from agent.models import UserState
 
45
  if ending and ending.get("ending_reached"):
46
  ending_info = ending["ending"]
47
  if (
48
+ ("description" not in ending_info or not ending_info["description"])
 
49
  and user_state.story_frame
50
  ):
51
  for e in user_state.story_frame.endings:
52
  if e.id == ending_info.get("id"):
53
  ending_info["description"] = e.description
54
  break
55
+
56
+ ending_desc = ending_info.get("description") or ending_info.get(
57
+ "condition", ""
58
+ )
59
+ change_scene = await generate_image_prompt(ending_desc, user_hash)
60
+ if change_scene.change_scene == "no_change":
61
+ change_scene.change_scene = "change_completely"
62
+ if not change_scene.scene_description:
63
+ change_scene.scene_description = ending_desc
64
+
65
+ image_path = await generate_scene_image.ainvoke(
66
+ {
67
+ "user_hash": user_hash,
68
+ "scene_id": f"ending_{uuid.uuid4()}",
69
+ "change_scene": change_scene,
70
+ }
71
+ )
72
+
73
  response["ending"] = ending_info
74
+ response["image"] = image_path
75
  response["game_over"] = True
76
  else:
77
  if (
src/css.py CHANGED
@@ -1,6 +1,6 @@
1
- # Custom CSS for fullscreen image with overlay
2
  custom_css = """
3
- /* Make the image container fullscreen */
4
  .image-container {
5
  position: fixed !important;
6
  top: 0 !important;
@@ -9,26 +9,28 @@ custom_css = """
9
  height: 100vh !important;
10
  z-index: 1 !important;
11
  }
12
-
13
  .image-container img {
14
  width: 100vw !important;
15
  height: 100vh !important;
16
  object-fit: cover !important;
17
  }
18
 
19
- /* Style the overlay content */
20
  .overlay-content {
21
  position: fixed !important;
22
  bottom: 0 !important;
23
  left: 0 !important;
24
  right: 0 !important;
25
- background: linear-gradient(transparent, rgba(0,0,0,0.8)) !important;
 
 
26
  padding: 40px 20px 20px !important;
27
  z-index: 10 !important;
28
  color: white !important;
29
  }
 
 
 
30
 
31
- /* Style the narrative text */
32
  .narrative-text {
33
  background: rgba(0,0,0,0.7) !important;
34
  border: none !important;
@@ -39,11 +41,6 @@ custom_css = """
39
  border-radius: 10px !important;
40
  margin-bottom: 10px !important;
41
  }
42
-
43
- img {
44
- pointer-events: none;
45
- }
46
-
47
  .narrative-text textarea {
48
  background: transparent !important;
49
  border: none !important;
@@ -52,84 +49,90 @@ img {
52
  font-size: 15px !important;
53
  resize: none !important;
54
  }
 
 
 
55
 
56
- /* Style the choice buttons */
57
- .choice-buttons {
58
  background: rgba(0,0,0,0.7) !important;
59
  border-radius: 10px !important;
60
  padding: 10px !important;
61
  }
62
 
63
- .choice-buttons label {
 
 
 
 
 
 
 
 
 
 
 
64
  color: white !important;
65
  font-size: 14px !important;
66
  }
67
-
68
- /* Fix radio button backgrounds */
69
  .choice-buttons input[type="radio"] {
70
  background: transparent !important;
71
  border: 2px solid white !important;
72
  }
73
-
74
  .choice-buttons input[type="radio"]:checked {
75
  background: white !important;
76
  }
77
-
78
  .choice-buttons .form-radio {
79
  background: transparent !important;
80
  }
81
-
82
- /* Style radio button containers */
83
- .choice-buttons > div {
84
- background: transparent !important;
85
- }
86
-
87
- .choice-buttons fieldset {
88
- background: transparent !important;
89
- border: none !important;
90
- }
91
-
92
- /* Remove any remaining white backgrounds */
93
  .choice-buttons * {
94
  background-color: transparent !important;
95
  }
96
-
97
  .choice-buttons input {
98
  background-color: transparent !important;
99
  border: 1px solid rgba(255,255,255,0.5) !important;
100
  color: white !important;
101
  }
102
 
103
- .choice-buttons label span {
 
 
 
104
  color: white !important;
 
105
  }
106
 
107
- /* Hide gradio header and footer */
108
- .gradio-header, .gradio-footer {
109
- display: none !important;
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
 
112
- /* Hide image control buttons using correct DOM selector */
113
- .image-container .icon-button-wrapper {
 
114
  display: none !important;
115
  }
116
-
117
  .image-container .icon-buttons {
118
  display: none !important;
119
  }
120
-
121
- /* Position the back button in the top-right corner */
122
  #back-btn {
123
  position: fixed !important;
124
  top: 10px !important;
125
  right: 10px !important;
126
  z-index: 20 !important;
127
  }
128
-
129
- /* Make form element transparent */
130
- .overlay-content .form {
131
- background: transparent !important;
132
- }
133
  """
134
 
135
  # CSS for the loading indicator
@@ -140,14 +143,12 @@ loading_css_styles = """
140
  left: 0;
141
  width: 100%;
142
  height: 100%;
143
- background-color: rgba(0, 0, 0, 0.8); /* Semi-transparent black */
144
- /* When Gradio makes this gr.Column visible, it will set display:flex !important; (or similar). */
145
- /* These properties will then apply to center the content of the Column: */
146
  justify-content: center;
147
  align-items: center;
148
- z-index: 9999; /* Ensure it's on top */
149
  }
150
- #loading-indicator .loading-text { /* Style for the text inside */
151
  color: white;
152
  font-size: 2em;
153
  text-align: center;
 
1
+ # Custom CSS for fullscreen image with overlay and styled form components
2
  custom_css = """
3
+ /* -------- FULLSCREEN BACKGROUND & NARRATIVE -------- */
4
  .image-container {
5
  position: fixed !important;
6
  top: 0 !important;
 
9
  height: 100vh !important;
10
  z-index: 1 !important;
11
  }
 
12
  .image-container img {
13
  width: 100vw !important;
14
  height: 100vh !important;
15
  object-fit: cover !important;
16
  }
17
 
 
18
  .overlay-content {
19
  position: fixed !important;
20
  bottom: 0 !important;
21
  left: 0 !important;
22
  right: 0 !important;
23
+ background: linear-gradient(
24
+ transparent, rgba(0,0,0,0.8)
25
+ ) !important;
26
  padding: 40px 20px 20px !important;
27
  z-index: 10 !important;
28
  color: white !important;
29
  }
30
+ .overlay-content .form {
31
+ background: transparent !important;
32
+ }
33
 
 
34
  .narrative-text {
35
  background: rgba(0,0,0,0.7) !important;
36
  border: none !important;
 
41
  border-radius: 10px !important;
42
  margin-bottom: 10px !important;
43
  }
 
 
 
 
 
44
  .narrative-text textarea {
45
  background: transparent !important;
46
  border: none !important;
 
49
  font-size: 15px !important;
50
  resize: none !important;
51
  }
52
+ img {
53
+ pointer-events: none;
54
+ }
55
 
56
+ /* -------- CHOICE SECTION -------- */
57
+ .choice-area {
58
  background: rgba(0,0,0,0.7) !important;
59
  border-radius: 10px !important;
60
  padding: 10px !important;
61
  }
62
 
63
+ .choice-buttons {
64
+ background: transparent !important;
65
+ border-radius: 10px !important;
66
+ padding: 10px !important;
67
+ }
68
+ .choice-buttons > div,
69
+ .choice-buttons fieldset {
70
+ background: transparent !important;
71
+ border: none !important;
72
+ }
73
+ .choice-buttons label,
74
+ .choice-buttons label span {
75
  color: white !important;
76
  font-size: 14px !important;
77
  }
 
 
78
  .choice-buttons input[type="radio"] {
79
  background: transparent !important;
80
  border: 2px solid white !important;
81
  }
 
82
  .choice-buttons input[type="radio"]:checked {
83
  background: white !important;
84
  }
 
85
  .choice-buttons .form-radio {
86
  background: transparent !important;
87
  }
 
 
 
 
 
 
 
 
 
 
 
 
88
  .choice-buttons * {
89
  background-color: transparent !important;
90
  }
 
91
  .choice-buttons input {
92
  background-color: transparent !important;
93
  border: 1px solid rgba(255,255,255,0.5) !important;
94
  color: white !important;
95
  }
96
 
97
+ /* Bold legend/label, no background */
98
+ .choice-area .form-label,
99
+ .choice-area legend {
100
+ background: transparent !important;
101
  color: white !important;
102
+ border: none !important;
103
  }
104
 
105
+ /* -------- CUSTOM INPUT FIELD -------- */
106
+ .choice-input textarea {
107
+ background: transparent !important;
108
+ border: none !important;
109
+ color: white !important;
110
+ border-radius: 10px !important;
111
+ outline: none !important;
112
+ box-shadow: none !important;
113
+ font-size: 15px !important;
114
+ padding: 10px !important;
115
+ }
116
+ .choice-input {
117
+ background: none !important;
118
+ margin-top: 10px !important;
119
  }
120
 
121
+ /* -------- UI MISCELLANEOUS -------- */
122
+ .gradio-header,
123
+ .gradio-footer {
124
  display: none !important;
125
  }
126
+ .image-container .icon-button-wrapper,
127
  .image-container .icon-buttons {
128
  display: none !important;
129
  }
 
 
130
  #back-btn {
131
  position: fixed !important;
132
  top: 10px !important;
133
  right: 10px !important;
134
  z-index: 20 !important;
135
  }
 
 
 
 
 
136
  """
137
 
138
  # CSS for the loading indicator
 
143
  left: 0;
144
  width: 100%;
145
  height: 100%;
146
+ background-color: rgba(0, 0, 0, 0.8);
 
 
147
  justify-content: center;
148
  align-items: center;
149
+ z-index: 9999;
150
  }
151
+ #loading-indicator .loading-text {
152
  color: white;
153
  font-size: 2em;
154
  text-align: center;
src/game_constructor.py CHANGED
@@ -136,6 +136,7 @@ async def start_game_with_settings(
136
  gr.update(),
137
  gr.update(),
138
  gr.update(), # game components unchanged
 
139
  )
140
 
141
  character = Character(
@@ -146,7 +147,7 @@ async def start_game_with_settings(
146
  )
147
 
148
  game_setting = GameSetting(character=character, setting=setting_desc, genre=genre)
149
-
150
  asyncio.create_task(start_music_generation(user_hash, "neutral"))
151
 
152
  # Запускаем LLM-граф для инициализации истории
@@ -171,4 +172,5 @@ async def start_game_with_settings(
171
  gr.update(value=scene_text), # game_text
172
  gr.update(value=scene_image), # game_image
173
  gr.update(choices=scene_choices, value=None), # game_choices
 
174
  )
 
136
  gr.update(),
137
  gr.update(),
138
  gr.update(), # game components unchanged
139
+ gr.update(), # custom choice
140
  )
141
 
142
  character = Character(
 
147
  )
148
 
149
  game_setting = GameSetting(character=character, setting=setting_desc, genre=genre)
150
+
151
  asyncio.create_task(start_music_generation(user_hash, "neutral"))
152
 
153
  # Запускаем LLM-граф для инициализации истории
 
172
  gr.update(value=scene_text), # game_text
173
  gr.update(value=scene_image), # game_image
174
  gr.update(choices=scene_choices, value=None), # game_choices
175
+ gr.update(value="", visible=True), # custom choice
176
  )
src/main.py CHANGED
@@ -44,7 +44,7 @@ async def return_to_constructor(user_hash: str):
44
  async def update_scene(user_hash: str, choice):
45
  logger.info(f"Updating scene with choice: {choice}")
46
  if not isinstance(choice, str):
47
- return gr.update(), gr.update(), gr.update()
48
 
49
  result = await process_step(
50
  user_hash=user_hash,
@@ -54,11 +54,15 @@ async def update_scene(user_hash: str, choice):
54
 
55
  if result.get("game_over"):
56
  ending = result["ending"]
57
- ending_text = ending.get("description") or ending.get("condition", "")
 
 
 
58
  return (
59
  gr.update(value=ending_text),
60
- gr.update(value=None),
61
- gr.Radio(choices=[], label="", value=None),
 
62
  )
63
 
64
  scene = result["scene"]
@@ -67,10 +71,11 @@ async def update_scene(user_hash: str, choice):
67
  scene.get("image", ""),
68
  gr.Radio(
69
  choices=[ch["text"] for ch in scene.get("choices", [])],
70
- label="What do you choose?",
71
  value=None,
72
  elem_classes=["choice-buttons"],
73
  ),
 
74
  )
75
 
76
 
@@ -107,6 +112,7 @@ async def start_game_with_music(
107
  gr.update(),
108
  gr.update(),
109
  gr.update(), # game components unchanged
 
110
  )
111
 
112
  # First, get the game interface updates
@@ -254,12 +260,20 @@ with gr.Blocks(
254
  elem_classes=["narrative-text"],
255
  lines=3,
256
  )
257
- game_choices = gr.Radio(
258
- choices=[],
259
- label="What do you choose?",
260
- value=None,
261
- elem_classes=["choice-buttons"],
262
- )
 
 
 
 
 
 
 
 
263
 
264
  # Event handlers for constructor interface
265
  setting_suggestions.change(
@@ -316,6 +330,7 @@ with gr.Blocks(
316
  game_text,
317
  game_image,
318
  game_choices,
 
319
  ],
320
  )
321
 
@@ -334,7 +349,13 @@ with gr.Blocks(
334
  game_choices.change(
335
  fn=update_scene,
336
  inputs=[local_storage, game_choices],
337
- outputs=[game_text, game_image, game_choices],
 
 
 
 
 
 
338
  )
339
 
340
  demo.unload(cleanup_music_session)
 
44
  async def update_scene(user_hash: str, choice):
45
  logger.info(f"Updating scene with choice: {choice}")
46
  if not isinstance(choice, str):
47
+ return gr.update(), gr.update(), gr.update(), gr.update()
48
 
49
  result = await process_step(
50
  user_hash=user_hash,
 
54
 
55
  if result.get("game_over"):
56
  ending = result["ending"]
57
+ ending_text = (
58
+ ending.get("description") or ending.get("condition", "")
59
+ ) + "\n[THE END]"
60
+ ending_image = result.get("image")
61
  return (
62
  gr.update(value=ending_text),
63
+ gr.update(value=ending_image),
64
+ gr.Radio(choices=[], label="", value=None, visible=False),
65
+ gr.update(value="", visible=False),
66
  )
67
 
68
  scene = result["scene"]
 
71
  scene.get("image", ""),
72
  gr.Radio(
73
  choices=[ch["text"] for ch in scene.get("choices", [])],
74
+ label="What do you choose? (select an option or write your own)",
75
  value=None,
76
  elem_classes=["choice-buttons"],
77
  ),
78
+ gr.update(value=""),
79
  )
80
 
81
 
 
112
  gr.update(),
113
  gr.update(),
114
  gr.update(), # game components unchanged
115
+ gr.update(), # custom choice unchanged
116
  )
117
 
118
  # First, get the game interface updates
 
260
  elem_classes=["narrative-text"],
261
  lines=3,
262
  )
263
+ with gr.Column(elem_classes=["choice-area"]):
264
+ game_choices = gr.Radio(
265
+ choices=[],
266
+ label="What do you choose? (select an option or write your own)",
267
+ value=None,
268
+ elem_classes=["choice-buttons"],
269
+ )
270
+ custom_choice = gr.Textbox(
271
+ label="",
272
+ show_label=False,
273
+ placeholder="Type your option and press Enter",
274
+ lines=1,
275
+ elem_classes=["choice-input"],
276
+ )
277
 
278
  # Event handlers for constructor interface
279
  setting_suggestions.change(
 
330
  game_text,
331
  game_image,
332
  game_choices,
333
+ custom_choice,
334
  ],
335
  )
336
 
 
349
  game_choices.change(
350
  fn=update_scene,
351
  inputs=[local_storage, game_choices],
352
+ outputs=[game_text, game_image, game_choices, custom_choice],
353
+ )
354
+
355
+ custom_choice.submit(
356
+ fn=update_scene,
357
+ inputs=[local_storage, custom_choice],
358
+ outputs=[game_text, game_image, game_choices, custom_choice],
359
  )
360
 
361
  demo.unload(cleanup_music_session)