Chan Meng
commited on
Commit
·
d750212
1
Parent(s):
a66d76b
update
Browse files- app.py +151 -162
- 互动式故事生成器开发规范文档.md +0 -101
app.py
CHANGED
|
@@ -6,61 +6,58 @@ import logging
|
|
| 6 |
import os
|
| 7 |
from dotenv import load_dotenv
|
| 8 |
|
| 9 |
-
#
|
| 10 |
load_dotenv()
|
| 11 |
|
| 12 |
-
#
|
| 13 |
logging.basicConfig(level=logging.INFO)
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
| 16 |
|
| 17 |
STORY_THEMES = [
|
| 18 |
-
"
|
| 19 |
-
"
|
| 20 |
-
"
|
| 21 |
-
"
|
| 22 |
-
"
|
| 23 |
-
"
|
| 24 |
]
|
| 25 |
|
| 26 |
CHARACTER_TEMPLATES = {
|
| 27 |
-
"
|
| 28 |
-
"
|
| 29 |
-
"
|
| 30 |
-
"
|
| 31 |
-
"
|
| 32 |
}
|
| 33 |
|
|
|
|
|
|
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
关键要求:
|
| 41 |
-
1. 故事必须具有连续性,每次回应都要基于之前的所有情节发展
|
| 42 |
-
2. 认真分析对话历史,保持人物性格、情节走向的一致性
|
| 43 |
-
3. 当用户补充新的细节或提供新的发展方向时,自然地将其整合到现有故事中
|
| 44 |
-
4. 注意因果关系,确保每个情节的发生都有合理的铺垫和解释
|
| 45 |
-
5. 通过环境描写、人物对话等手法,让故事更加生动
|
| 46 |
-
6. 在故事发展的关键节点,可以给出一些暗示,引导用户参与情节推进
|
| 47 |
-
|
| 48 |
-
你不应该:
|
| 49 |
-
1. 重新开始新的故事
|
| 50 |
-
2. 忽视之前提到的重要情节或细节
|
| 51 |
-
3. 生成与已建立设定相矛盾的内容
|
| 52 |
-
4. 突兀地引入未经铺垫的重大转折
|
| 53 |
-
|
| 54 |
-
请记住:你正在创作一个持续发展的故事,而不是独立的片段。"""
|
| 55 |
|
| 56 |
|
| 57 |
STORY_STYLES = [
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
-
"
|
| 61 |
-
"
|
| 62 |
-
"
|
| 63 |
-
"
|
| 64 |
]
|
| 65 |
|
| 66 |
MAX_RETRIES = 3
|
|
@@ -69,7 +66,7 @@ RETRY_DELAY = 2
|
|
| 69 |
def create_client() -> InferenceClient:
|
| 70 |
hf_token = os.getenv('HF_TOKEN')
|
| 71 |
if not hf_token:
|
| 72 |
-
raise ValueError("HF_TOKEN
|
| 73 |
return InferenceClient(
|
| 74 |
"HuggingFaceH4/zephyr-7b-beta",
|
| 75 |
token=hf_token
|
|
@@ -86,60 +83,60 @@ def generate_story(
|
|
| 86 |
top_p: float = 0.95,
|
| 87 |
) -> Generator[str, None, None]:
|
| 88 |
"""
|
| 89 |
-
|
| 90 |
"""
|
| 91 |
if history is None:
|
| 92 |
history = []
|
| 93 |
|
| 94 |
-
#
|
| 95 |
context_summary = ""
|
| 96 |
story_content = []
|
| 97 |
|
| 98 |
-
#
|
| 99 |
for msg in history:
|
| 100 |
if msg["role"] == "assistant":
|
| 101 |
story_content.append(msg["content"])
|
| 102 |
|
| 103 |
if story_content:
|
| 104 |
context_summary = "\n".join([
|
| 105 |
-
"
|
| 106 |
"---",
|
| 107 |
"\n".join(story_content),
|
| 108 |
"---"
|
| 109 |
])
|
| 110 |
|
| 111 |
-
#
|
| 112 |
if not history:
|
| 113 |
-
#
|
| 114 |
prompt = f"""
|
| 115 |
-
|
| 116 |
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
|
| 122 |
-
|
| 123 |
"""
|
| 124 |
else:
|
| 125 |
-
#
|
| 126 |
prompt = f"""
|
| 127 |
{context_summary}
|
| 128 |
|
| 129 |
-
|
| 130 |
-
-
|
| 131 |
-
-
|
| 132 |
-
-
|
| 133 |
|
| 134 |
-
|
| 135 |
|
| 136 |
-
|
| 137 |
-
1.
|
| 138 |
-
2.
|
| 139 |
-
3.
|
| 140 |
-
4.
|
| 141 |
|
| 142 |
-
|
| 143 |
"""
|
| 144 |
|
| 145 |
messages = [
|
|
@@ -164,111 +161,106 @@ def generate_story(
|
|
| 164 |
response += token
|
| 165 |
yield response
|
| 166 |
except Exception as e:
|
| 167 |
-
logger.error(f"
|
| 168 |
-
yield f"
|
| 169 |
-
|
| 170 |
-
|
| 171 |
|
| 172 |
def summarize_story_context(history: list) -> str:
|
| 173 |
"""
|
| 174 |
-
|
| 175 |
"""
|
| 176 |
if not history:
|
| 177 |
return ""
|
| 178 |
|
| 179 |
summary_parts = []
|
| 180 |
key_elements = {
|
| 181 |
-
"characters": set(), #
|
| 182 |
-
"locations": set(), #
|
| 183 |
-
"events": [], #
|
| 184 |
-
"objects": set() #
|
| 185 |
}
|
| 186 |
|
| 187 |
for msg in history:
|
| 188 |
content = msg.get("content", "")
|
| 189 |
-
# TODO:
|
| 190 |
-
#
|
| 191 |
if content:
|
| 192 |
summary_parts.append(content)
|
| 193 |
|
| 194 |
return "\n".join(summary_parts)
|
| 195 |
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
# 创建故事生成器界面
|
| 199 |
-
|
| 200 |
def create_demo():
|
| 201 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 202 |
gr.Markdown(
|
| 203 |
"""
|
| 204 |
-
# 🎭
|
| 205 |
-
|
| 206 |
-
|
| 207 |
"""
|
| 208 |
)
|
| 209 |
|
| 210 |
with gr.Tabs():
|
| 211 |
-
#
|
| 212 |
-
with gr.Tab("✍️
|
| 213 |
with gr.Row(equal_height=True):
|
| 214 |
-
#
|
| 215 |
with gr.Column(scale=1):
|
| 216 |
with gr.Group():
|
| 217 |
style_select = gr.Dropdown(
|
| 218 |
choices=STORY_STYLES,
|
| 219 |
-
value="
|
| 220 |
-
label="
|
| 221 |
-
info="
|
| 222 |
)
|
| 223 |
|
| 224 |
theme_select = gr.Dropdown(
|
| 225 |
choices=STORY_THEMES,
|
| 226 |
-
value="
|
| 227 |
-
label="
|
| 228 |
-
info="
|
| 229 |
)
|
| 230 |
|
| 231 |
with gr.Group():
|
| 232 |
-
gr.Markdown("### 👤
|
| 233 |
character_select = gr.Dropdown(
|
| 234 |
choices=list(CHARACTER_TEMPLATES.keys()),
|
| 235 |
-
value="
|
| 236 |
-
label="
|
| 237 |
-
info="
|
| 238 |
)
|
| 239 |
|
| 240 |
character_desc = gr.Textbox(
|
| 241 |
lines=3,
|
| 242 |
-
value=CHARACTER_TEMPLATES["
|
| 243 |
-
label="
|
| 244 |
-
info="
|
| 245 |
)
|
| 246 |
|
| 247 |
with gr.Group():
|
| 248 |
scene_input = gr.Textbox(
|
| 249 |
lines=3,
|
| 250 |
-
placeholder="
|
| 251 |
-
label="
|
| 252 |
-
info="
|
| 253 |
)
|
| 254 |
|
| 255 |
with gr.Row():
|
| 256 |
-
submit_btn = gr.Button("✨
|
| 257 |
-
clear_btn = gr.Button("🗑️
|
| 258 |
-
save_btn = gr.Button("💾
|
| 259 |
|
| 260 |
-
#
|
| 261 |
with gr.Column(scale=2):
|
| 262 |
chatbot = gr.Chatbot(
|
| 263 |
-
label="
|
| 264 |
height=600,
|
| 265 |
show_label=True
|
| 266 |
)
|
| 267 |
|
| 268 |
status_msg = gr.Markdown("")
|
| 269 |
|
| 270 |
-
#
|
| 271 |
-
with gr.Tab("⚙️
|
| 272 |
with gr.Group():
|
| 273 |
with gr.Row():
|
| 274 |
with gr.Column():
|
|
@@ -277,8 +269,8 @@ def create_demo():
|
|
| 277 |
maximum=2.0,
|
| 278 |
value=0.7,
|
| 279 |
step=0.1,
|
| 280 |
-
label="
|
| 281 |
-
info="
|
| 282 |
)
|
| 283 |
|
| 284 |
max_tokens = gr.Slider(
|
|
@@ -286,8 +278,8 @@ def create_demo():
|
|
| 286 |
maximum=1024,
|
| 287 |
value=512,
|
| 288 |
step=64,
|
| 289 |
-
label="
|
| 290 |
-
info="
|
| 291 |
)
|
| 292 |
|
| 293 |
top_p = gr.Slider(
|
|
@@ -295,35 +287,35 @@ def create_demo():
|
|
| 295 |
maximum=1.0,
|
| 296 |
value=0.95,
|
| 297 |
step=0.05,
|
| 298 |
-
label="
|
| 299 |
-
info="
|
| 300 |
)
|
| 301 |
|
| 302 |
-
#
|
| 303 |
-
with gr.Accordion("📖
|
| 304 |
gr.Markdown(
|
| 305 |
"""
|
| 306 |
-
##
|
| 307 |
-
1.
|
| 308 |
-
2.
|
| 309 |
-
3.
|
| 310 |
-
4.
|
| 311 |
-
5.
|
| 312 |
|
| 313 |
-
##
|
| 314 |
-
-
|
| 315 |
-
-
|
| 316 |
-
-
|
| 317 |
-
-
|
| 318 |
|
| 319 |
-
##
|
| 320 |
-
-
|
| 321 |
-
-
|
| 322 |
-
-
|
| 323 |
"""
|
| 324 |
)
|
| 325 |
|
| 326 |
-
#
|
| 327 |
def update_character_desc(template):
|
| 328 |
return CHARACTER_TEMPLATES[template]
|
| 329 |
|
|
@@ -333,53 +325,52 @@ def create_demo():
|
|
| 333 |
character_desc
|
| 334 |
)
|
| 335 |
|
| 336 |
-
#
|
| 337 |
save_btn.click(
|
| 338 |
save_story,
|
| 339 |
chatbot,
|
| 340 |
status_msg,
|
| 341 |
)
|
| 342 |
|
| 343 |
-
#
|
| 344 |
def user_input(user_message, history):
|
| 345 |
"""
|
| 346 |
-
|
| 347 |
Args:
|
| 348 |
-
user_message:
|
| 349 |
-
history:
|
| 350 |
"""
|
| 351 |
if history is None:
|
| 352 |
history = []
|
| 353 |
-
history.append([user_message, None]) #
|
| 354 |
return "", history
|
| 355 |
|
| 356 |
-
# AI
|
| 357 |
def bot_response(history, style, theme, character_desc, temperature, max_tokens, top_p):
|
| 358 |
"""
|
| 359 |
-
|
| 360 |
Args:
|
| 361 |
-
history:
|
| 362 |
-
style:
|
| 363 |
-
theme:
|
| 364 |
-
character_desc:
|
| 365 |
-
temperature:
|
| 366 |
-
max_tokens:
|
| 367 |
-
top_p:
|
| 368 |
"""
|
| 369 |
try:
|
| 370 |
-
|
| 371 |
-
# 获取用户的最后一条消息
|
| 372 |
user_message = history[-1][0]
|
| 373 |
|
| 374 |
-
#
|
| 375 |
message_history = []
|
| 376 |
-
for user_msg, bot_msg in history[:-1]: #
|
| 377 |
if user_msg:
|
| 378 |
message_history.append({"role": "user", "content": user_msg})
|
| 379 |
if bot_msg:
|
| 380 |
message_history.append({"role": "assistant", "content": bot_msg})
|
| 381 |
|
| 382 |
-
#
|
| 383 |
current_response = ""
|
| 384 |
for text in generate_story(
|
| 385 |
user_message,
|
|
@@ -392,21 +383,21 @@ def create_demo():
|
|
| 392 |
top_p
|
| 393 |
):
|
| 394 |
current_response = text
|
| 395 |
-
history[-1][1] = current_response #
|
| 396 |
yield history
|
| 397 |
|
| 398 |
except Exception as e:
|
| 399 |
-
logger.error(f"
|
| 400 |
-
error_msg = f"
|
| 401 |
history[-1][1] = error_msg
|
| 402 |
yield history
|
| 403 |
|
| 404 |
|
| 405 |
-
#
|
| 406 |
def clear_chat():
|
| 407 |
return [], ""
|
| 408 |
|
| 409 |
-
#
|
| 410 |
scene_input.submit(
|
| 411 |
user_input,
|
| 412 |
[scene_input, chatbot],
|
|
@@ -437,9 +428,9 @@ def create_demo():
|
|
| 437 |
|
| 438 |
|
| 439 |
def save_story(chatbot):
|
| 440 |
-
"""
|
| 441 |
if not chatbot:
|
| 442 |
-
return "
|
| 443 |
|
| 444 |
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
| 445 |
filename = f"stories/story_{timestamp}.txt"
|
|
@@ -450,14 +441,12 @@ def save_story(chatbot):
|
|
| 450 |
with open(filename, "w", encoding="utf-8") as f:
|
| 451 |
for user_msg, bot_msg in chatbot:
|
| 452 |
if user_msg:
|
| 453 |
-
f.write(f"
|
| 454 |
if bot_msg:
|
| 455 |
f.write(f"AI: {bot_msg}\n\n")
|
| 456 |
-
return f"
|
| 457 |
except Exception as e:
|
| 458 |
-
return f"
|
| 459 |
-
|
| 460 |
-
|
| 461 |
|
| 462 |
if __name__ == "__main__":
|
| 463 |
demo = create_demo()
|
|
|
|
| 6 |
import os
|
| 7 |
from dotenv import load_dotenv
|
| 8 |
|
| 9 |
+
# Load environment variables
|
| 10 |
load_dotenv()
|
| 11 |
|
| 12 |
+
# Set up logging
|
| 13 |
logging.basicConfig(level=logging.INFO)
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
| 16 |
|
| 17 |
STORY_THEMES = [
|
| 18 |
+
"Adventure",
|
| 19 |
+
"Mystery",
|
| 20 |
+
"Romance",
|
| 21 |
+
"Historical",
|
| 22 |
+
"Slice of Life",
|
| 23 |
+
"Fairy Tale"
|
| 24 |
]
|
| 25 |
|
| 26 |
CHARACTER_TEMPLATES = {
|
| 27 |
+
"Adventurer": "A brave and fearless explorer who loves adventure and challenges.",
|
| 28 |
+
"Detective": "A keen and observant detective skilled in observation and deduction.",
|
| 29 |
+
"Artist": "A creative artist with unique perspectives on beauty.",
|
| 30 |
+
"Scientist": "A curious scientist dedicated to exploring the unknown.",
|
| 31 |
+
"Ordinary Person": "An ordinary person with a rich inner world."
|
| 32 |
}
|
| 33 |
|
| 34 |
+
# Initialize story generator system prompt
|
| 35 |
+
STORY_SYSTEM_PROMPT = """You are a professional story generator. Your task is to generate coherent and engaging stories based on user settings and real-time input.
|
| 36 |
|
| 37 |
+
Key requirements:
|
| 38 |
+
1. The story must maintain continuity, with each response building upon all previous plot developments
|
| 39 |
+
2. Carefully analyze dialogue history to maintain consistency in character personalities and plot progression
|
| 40 |
+
3. Naturally integrate new details or development directions when provided by the user
|
| 41 |
+
4. Pay attention to cause and effect, ensuring each plot point has reasonable setup and explanation
|
| 42 |
+
5. Make the story more vivid through environmental descriptions and character dialogues
|
| 43 |
+
6. At key story points, provide hints to guide user participation in plot progression
|
| 44 |
|
| 45 |
+
You should not:
|
| 46 |
+
1. Start a new story
|
| 47 |
+
2. Ignore previously mentioned important plots or details
|
| 48 |
+
3. Generate content that contradicts established settings
|
| 49 |
+
4. Introduce major turns without proper setup
|
| 50 |
|
| 51 |
+
Remember: You are creating an ongoing story, not independent fragments."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
|
| 54 |
STORY_STYLES = [
|
| 55 |
+
"Fantasy",
|
| 56 |
+
"Science Fiction",
|
| 57 |
+
"Mystery",
|
| 58 |
+
"Adventure",
|
| 59 |
+
"Romance",
|
| 60 |
+
"Horror"
|
| 61 |
]
|
| 62 |
|
| 63 |
MAX_RETRIES = 3
|
|
|
|
| 66 |
def create_client() -> InferenceClient:
|
| 67 |
hf_token = os.getenv('HF_TOKEN')
|
| 68 |
if not hf_token:
|
| 69 |
+
raise ValueError("HF_TOKEN environment variable not set")
|
| 70 |
return InferenceClient(
|
| 71 |
"HuggingFaceH4/zephyr-7b-beta",
|
| 72 |
token=hf_token
|
|
|
|
| 83 |
top_p: float = 0.95,
|
| 84 |
) -> Generator[str, None, None]:
|
| 85 |
"""
|
| 86 |
+
Generate continuous story plot
|
| 87 |
"""
|
| 88 |
if history is None:
|
| 89 |
history = []
|
| 90 |
|
| 91 |
+
# Build context summary
|
| 92 |
context_summary = ""
|
| 93 |
story_content = []
|
| 94 |
|
| 95 |
+
# Extract previous story content
|
| 96 |
for msg in history:
|
| 97 |
if msg["role"] == "assistant":
|
| 98 |
story_content.append(msg["content"])
|
| 99 |
|
| 100 |
if story_content:
|
| 101 |
context_summary = "\n".join([
|
| 102 |
+
"Previously in the story:",
|
| 103 |
"---",
|
| 104 |
"\n".join(story_content),
|
| 105 |
"---"
|
| 106 |
])
|
| 107 |
|
| 108 |
+
# Use different prompt templates based on whether there's history
|
| 109 |
if not history:
|
| 110 |
+
# First generation, use complete settings
|
| 111 |
prompt = f"""
|
| 112 |
+
Please start a story based on the following settings:
|
| 113 |
|
| 114 |
+
Style: {style}
|
| 115 |
+
Theme: {theme}
|
| 116 |
+
Character: {character_desc}
|
| 117 |
+
Initial Scene: {scene}
|
| 118 |
|
| 119 |
+
Please begin from this scene and set up the story's opening. Leave room for future developments.
|
| 120 |
"""
|
| 121 |
else:
|
| 122 |
+
# Subsequent generation, focus on plot continuation
|
| 123 |
prompt = f"""
|
| 124 |
{context_summary}
|
| 125 |
|
| 126 |
+
Story settings reminder:
|
| 127 |
+
- Style: {style}
|
| 128 |
+
- Theme: {theme}
|
| 129 |
+
- Main Character: {character_desc}
|
| 130 |
|
| 131 |
+
User's new input: {scene}
|
| 132 |
|
| 133 |
+
Please continue the story based on the previous plot and user's new input. Note:
|
| 134 |
+
1. New developments must maintain continuity with previous plot
|
| 135 |
+
2. Rationalize new elements provided by the user
|
| 136 |
+
3. Maintain consistency in character personalities
|
| 137 |
+
4. Leave possibilities for future developments
|
| 138 |
|
| 139 |
+
Continue the story:
|
| 140 |
"""
|
| 141 |
|
| 142 |
messages = [
|
|
|
|
| 161 |
response += token
|
| 162 |
yield response
|
| 163 |
except Exception as e:
|
| 164 |
+
logger.error(f"Error occurred while generating story: {str(e)}")
|
| 165 |
+
yield f"Sorry, encountered an error while generating the story: {str(e)}\nPlease try again later."
|
|
|
|
|
|
|
| 166 |
|
| 167 |
def summarize_story_context(history: list) -> str:
|
| 168 |
"""
|
| 169 |
+
Summarize current story context for generation assistance
|
| 170 |
"""
|
| 171 |
if not history:
|
| 172 |
return ""
|
| 173 |
|
| 174 |
summary_parts = []
|
| 175 |
key_elements = {
|
| 176 |
+
"characters": set(), # Characters appeared
|
| 177 |
+
"locations": set(), # Scene locations
|
| 178 |
+
"events": [], # Key events
|
| 179 |
+
"objects": set() # Important items
|
| 180 |
}
|
| 181 |
|
| 182 |
for msg in history:
|
| 183 |
content = msg.get("content", "")
|
| 184 |
+
# TODO: More complex NLP processing can be added here to extract key information
|
| 185 |
+
# Currently using simple text accumulation
|
| 186 |
if content:
|
| 187 |
summary_parts.append(content)
|
| 188 |
|
| 189 |
return "\n".join(summary_parts)
|
| 190 |
|
| 191 |
+
# Create story generator interface
|
|
|
|
|
|
|
|
|
|
| 192 |
def create_demo():
|
| 193 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 194 |
gr.Markdown(
|
| 195 |
"""
|
| 196 |
+
# 🎭 Interactive Story Generator
|
| 197 |
+
Let AI create a unique storytelling experience for you. Choose your story style, theme, add character settings,
|
| 198 |
+
then describe a scene to start your story. Interact with AI to continue developing the plot!
|
| 199 |
"""
|
| 200 |
)
|
| 201 |
|
| 202 |
with gr.Tabs():
|
| 203 |
+
# Story Creation Tab
|
| 204 |
+
with gr.Tab("✍️ Story Creation"):
|
| 205 |
with gr.Row(equal_height=True):
|
| 206 |
+
# Left Control Panel
|
| 207 |
with gr.Column(scale=1):
|
| 208 |
with gr.Group():
|
| 209 |
style_select = gr.Dropdown(
|
| 210 |
choices=STORY_STYLES,
|
| 211 |
+
value="Fantasy",
|
| 212 |
+
label="Choose Story Style",
|
| 213 |
+
info="Select an overall style to define the story's tone"
|
| 214 |
)
|
| 215 |
|
| 216 |
theme_select = gr.Dropdown(
|
| 217 |
choices=STORY_THEMES,
|
| 218 |
+
value="Adventure",
|
| 219 |
+
label="Choose Story Theme",
|
| 220 |
+
info="Select the main thematic elements to focus on"
|
| 221 |
)
|
| 222 |
|
| 223 |
with gr.Group():
|
| 224 |
+
gr.Markdown("### 👤 Character Settings")
|
| 225 |
character_select = gr.Dropdown(
|
| 226 |
choices=list(CHARACTER_TEMPLATES.keys()),
|
| 227 |
+
value="Adventurer",
|
| 228 |
+
label="Select Character Template",
|
| 229 |
+
info="Choose a preset character type or customize description"
|
| 230 |
)
|
| 231 |
|
| 232 |
character_desc = gr.Textbox(
|
| 233 |
lines=3,
|
| 234 |
+
value=CHARACTER_TEMPLATES["Adventurer"],
|
| 235 |
+
label="Character Description",
|
| 236 |
+
info="Describe character's personality, background, traits, etc."
|
| 237 |
)
|
| 238 |
|
| 239 |
with gr.Group():
|
| 240 |
scene_input = gr.Textbox(
|
| 241 |
lines=3,
|
| 242 |
+
placeholder="Describe the scene, environment, time, etc. here...",
|
| 243 |
+
label="Scene Description",
|
| 244 |
+
info="Detailed scene description will make the story more vivid"
|
| 245 |
)
|
| 246 |
|
| 247 |
with gr.Row():
|
| 248 |
+
submit_btn = gr.Button("✨ Start Story", variant="primary", scale=2)
|
| 249 |
+
clear_btn = gr.Button("🗑️ Clear Chat", scale=1)
|
| 250 |
+
save_btn = gr.Button("💾 Save Story", scale=1)
|
| 251 |
|
| 252 |
+
# Right Chat Area
|
| 253 |
with gr.Column(scale=2):
|
| 254 |
chatbot = gr.Chatbot(
|
| 255 |
+
label="Story Dialogue",
|
| 256 |
height=600,
|
| 257 |
show_label=True
|
| 258 |
)
|
| 259 |
|
| 260 |
status_msg = gr.Markdown("")
|
| 261 |
|
| 262 |
+
# Settings Tab
|
| 263 |
+
with gr.Tab("⚙️ Advanced Settings"):
|
| 264 |
with gr.Group():
|
| 265 |
with gr.Row():
|
| 266 |
with gr.Column():
|
|
|
|
| 269 |
maximum=2.0,
|
| 270 |
value=0.7,
|
| 271 |
step=0.1,
|
| 272 |
+
label="Creativity (Temperature)",
|
| 273 |
+
info="Higher values make story more creative but potentially less coherent"
|
| 274 |
)
|
| 275 |
|
| 276 |
max_tokens = gr.Slider(
|
|
|
|
| 278 |
maximum=1024,
|
| 279 |
value=512,
|
| 280 |
step=64,
|
| 281 |
+
label="Maximum Generation Length",
|
| 282 |
+
info="Control the length of each generated text"
|
| 283 |
)
|
| 284 |
|
| 285 |
top_p = gr.Slider(
|
|
|
|
| 287 |
maximum=1.0,
|
| 288 |
value=0.95,
|
| 289 |
step=0.05,
|
| 290 |
+
label="Sampling Range (Top-p)",
|
| 291 |
+
info="Control the diversity of word choice"
|
| 292 |
)
|
| 293 |
|
| 294 |
+
# Help Information
|
| 295 |
+
with gr.Accordion("📖 Usage Guide", open=False):
|
| 296 |
gr.Markdown(
|
| 297 |
"""
|
| 298 |
+
## How to Use the Story Generator
|
| 299 |
+
1. Choose story style and theme to set the overall tone
|
| 300 |
+
2. Select a preset character template or customize character description
|
| 301 |
+
3. Describe the story's scene and environment
|
| 302 |
+
4. Click "Start Story" to generate the opening
|
| 303 |
+
5. Continue inputting content to interact with AI and advance the story
|
| 304 |
|
| 305 |
+
## Tips
|
| 306 |
+
- Detailed scene and character descriptions will make the generated story richer
|
| 307 |
+
- Use the "Save Story" function to save memorable story plots
|
| 308 |
+
- Adjust parameters in settings to affect story creativity and coherence
|
| 309 |
+
- Use "Clear Chat" to start over if you're not satisfied with the plot
|
| 310 |
|
| 311 |
+
## Parameter Explanation
|
| 312 |
+
- Creativity: Controls the story's creativity level, higher values increase creativity
|
| 313 |
+
- Sampling Range: Controls vocabulary richness, higher values increase word diversity
|
| 314 |
+
- Maximum Length: Controls the length of each generated text
|
| 315 |
"""
|
| 316 |
)
|
| 317 |
|
| 318 |
+
# Update character description
|
| 319 |
def update_character_desc(template):
|
| 320 |
return CHARACTER_TEMPLATES[template]
|
| 321 |
|
|
|
|
| 325 |
character_desc
|
| 326 |
)
|
| 327 |
|
| 328 |
+
# Save story dialogue
|
| 329 |
save_btn.click(
|
| 330 |
save_story,
|
| 331 |
chatbot,
|
| 332 |
status_msg,
|
| 333 |
)
|
| 334 |
|
| 335 |
+
# User input processing
|
| 336 |
def user_input(user_message, history):
|
| 337 |
"""
|
| 338 |
+
Process user input
|
| 339 |
Args:
|
| 340 |
+
user_message: User's input message
|
| 341 |
+
history: Chat history [(user_msg, bot_msg), ...]
|
| 342 |
"""
|
| 343 |
if history is None:
|
| 344 |
history = []
|
| 345 |
+
history.append([user_message, None]) # Add user message, bot message temporarily None
|
| 346 |
return "", history
|
| 347 |
|
| 348 |
+
# AI response processing
|
| 349 |
def bot_response(history, style, theme, character_desc, temperature, max_tokens, top_p):
|
| 350 |
"""
|
| 351 |
+
Generate AI response
|
| 352 |
Args:
|
| 353 |
+
history: Chat history [(user_msg, bot_msg), ...]
|
| 354 |
+
style: Story style
|
| 355 |
+
theme: Story theme
|
| 356 |
+
character_desc: Character description
|
| 357 |
+
temperature: Generation parameter
|
| 358 |
+
max_tokens: Generation parameter
|
| 359 |
+
top_p: Generation parameter
|
| 360 |
"""
|
| 361 |
try:
|
| 362 |
+
# Get user's last message
|
|
|
|
| 363 |
user_message = history[-1][0]
|
| 364 |
|
| 365 |
+
# Convert history format for generate_story
|
| 366 |
message_history = []
|
| 367 |
+
for user_msg, bot_msg in history[:-1]: # Excluding the last one
|
| 368 |
if user_msg:
|
| 369 |
message_history.append({"role": "user", "content": user_msg})
|
| 370 |
if bot_msg:
|
| 371 |
message_history.append({"role": "assistant", "content": bot_msg})
|
| 372 |
|
| 373 |
+
# Start generating story
|
| 374 |
current_response = ""
|
| 375 |
for text in generate_story(
|
| 376 |
user_message,
|
|
|
|
| 383 |
top_p
|
| 384 |
):
|
| 385 |
current_response = text
|
| 386 |
+
history[-1][1] = current_response # Update bot reply for the last message
|
| 387 |
yield history
|
| 388 |
|
| 389 |
except Exception as e:
|
| 390 |
+
logger.error(f"Error occurred while processing response: {str(e)}")
|
| 391 |
+
error_msg = f"Sorry, encountered an error while generating the story. Please try again later."
|
| 392 |
history[-1][1] = error_msg
|
| 393 |
yield history
|
| 394 |
|
| 395 |
|
| 396 |
+
# Clear chat
|
| 397 |
def clear_chat():
|
| 398 |
return [], ""
|
| 399 |
|
| 400 |
+
# Bind events
|
| 401 |
scene_input.submit(
|
| 402 |
user_input,
|
| 403 |
[scene_input, chatbot],
|
|
|
|
| 428 |
|
| 429 |
|
| 430 |
def save_story(chatbot):
|
| 431 |
+
"""Save story dialogue record"""
|
| 432 |
if not chatbot:
|
| 433 |
+
return "Story is empty, cannot save"
|
| 434 |
|
| 435 |
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
| 436 |
filename = f"stories/story_{timestamp}.txt"
|
|
|
|
| 441 |
with open(filename, "w", encoding="utf-8") as f:
|
| 442 |
for user_msg, bot_msg in chatbot:
|
| 443 |
if user_msg:
|
| 444 |
+
f.write(f"User: {user_msg}\n")
|
| 445 |
if bot_msg:
|
| 446 |
f.write(f"AI: {bot_msg}\n\n")
|
| 447 |
+
return f"Story has been saved to {filename}"
|
| 448 |
except Exception as e:
|
| 449 |
+
return f"Save failed: {str(e)}"
|
|
|
|
|
|
|
| 450 |
|
| 451 |
if __name__ == "__main__":
|
| 452 |
demo = create_demo()
|
互动式故事生成器开发规范文档.md
DELETED
|
@@ -1,101 +0,0 @@
|
|
| 1 |
-
# 互动式故事生成器 - 开发规范文档 (使用Meta Llama)
|
| 2 |
-
|
| 3 |
-
## 1. 项目概述
|
| 4 |
-
创建一个基于AI的互动式故事生成器,使用Meta Llama模型,允许用户提供初始场景或角色,然后与AI进行对话式交互来发展故事情节。该项目将部署在Hugging Face Space上。
|
| 5 |
-
|
| 6 |
-
## 2. 技术栈
|
| 7 |
-
- Python 3.8+
|
| 8 |
-
- Gradio (用于创建Web界面)
|
| 9 |
-
- Hugging Face Transformers (用于访问和使用Llama模型)
|
| 10 |
-
- PyTorch (作为Transformers的后端)
|
| 11 |
-
|
| 12 |
-
## 3. 开发阶段
|
| 13 |
-
|
| 14 |
-
### 阶段1: 基本功能实现
|
| 15 |
-
|
| 16 |
-
#### 1.1 设置项目环境
|
| 17 |
-
- 创建`requirements.txt`文件,包含必要的依赖
|
| 18 |
-
- 设置虚拟环境
|
| 19 |
-
|
| 20 |
-
#### 1.2 配置Llama模型
|
| 21 |
-
- 使用Hugging Face Transformers库加载Llama模型
|
| 22 |
-
- 设置适当的模型参数(如温度、top-k等)
|
| 23 |
-
|
| 24 |
-
#### 1.3 实现基本的故事生成
|
| 25 |
-
- 创建`app.py`文件
|
| 26 |
-
- 导入必要的库和Llama模型
|
| 27 |
-
- 实现使用Llama的文本生成函数
|
| 28 |
-
|
| 29 |
-
#### 1.4 创建基本的Gradio界面
|
| 30 |
-
- 设计输入框供用户输入初始场景
|
| 31 |
-
- 添加"生成故事"按钮
|
| 32 |
-
- 创建输出区域显示生成的故事
|
| 33 |
-
|
| 34 |
-
#### 1.5 整合模型和界面
|
| 35 |
-
- 将Llama文本生成函数与Gradio界面连接
|
| 36 |
-
- 测试基本功能
|
| 37 |
-
|
| 38 |
-
### 阶段2: 增强交互性
|
| 39 |
-
|
| 40 |
-
#### 2.1 实现对话式交互
|
| 41 |
-
- 修改Llama生成函数,支持接收用户的后续输入
|
| 42 |
-
- 更新Gradio界面,添加对话历史显示
|
| 43 |
-
- 实现回合制的故事发展机制
|
| 44 |
-
|
| 45 |
-
#### 2.2 添加故事风格选择
|
| 46 |
-
- 创建预定义的故事风格列表(如科幻、奇幻、悬疑等)
|
| 47 |
-
- 在界面中添加风格选择下拉菜单
|
| 48 |
-
- 修改Llama生成函数,考虑所选风格
|
| 49 |
-
|
| 50 |
-
#### 2.3 角色管理
|
| 51 |
-
- 添加角色创建功能
|
| 52 |
-
- 在故事生成过程中整合角色信息到Llama输入
|
| 53 |
-
|
| 54 |
-
### 阶段3: 高级功能
|
| 55 |
-
|
| 56 |
-
#### 3.1 故事分支
|
| 57 |
-
- 使用Llama生成多个可能的故事发展方向
|
| 58 |
-
- 允许用户选择喜欢的方向继续发展
|
| 59 |
-
|
| 60 |
-
#### 3.2 保存和加载功能
|
| 61 |
-
- 实现将生成的故事保存为文件的功能
|
| 62 |
-
- 添加加载已保存故事的选项
|
| 63 |
-
|
| 64 |
-
#### 3.3 上下文管理
|
| 65 |
-
- 实现有效的上下文管理,确保Llama模型能够理解长期上下文
|
| 66 |
-
|
| 67 |
-
## 4. 优化Llama模型使用
|
| 68 |
-
- 实现高效的模型缓存机制
|
| 69 |
-
- 优化推理速度,考虑使用量化技术
|
| 70 |
-
- 实现批处理以提高效率
|
| 71 |
-
|
| 72 |
-
## 5. Hugging Face Space部署准备
|
| 73 |
-
- 确保`requirements.txt`包含所有必要的依赖
|
| 74 |
-
- 创建`README.md`文件,提供项目描述和使用说明
|
| 75 |
-
- 配置Hugging Face Space的环境变量(如需要)
|
| 76 |
-
|
| 77 |
-
## 6. 测试
|
| 78 |
-
- 为每个主要功能编写单元测试
|
| 79 |
-
- 进行用户体验测试,收集反馈
|
| 80 |
-
- 测试在Hugging Face Space环境中的性能
|
| 81 |
-
|
| 82 |
-
## 7. 部署
|
| 83 |
-
- 在Hugging Face Space上部署应用
|
| 84 |
-
- 确保模型和所有必要文件都正确上传
|
| 85 |
-
- 测试部署后的应用功能
|
| 86 |
-
|
| 87 |
-
## 8. 后续优化
|
| 88 |
-
- 基于用户反馈进行界面优化
|
| 89 |
-
- 探索使用Llama模型的最新版本或变体
|
| 90 |
-
- 考虑添加多语言支持
|
| 91 |
-
- 优化模型响应时间和资源使用
|
| 92 |
-
|
| 93 |
-
## 9. 文档和用户指南
|
| 94 |
-
- 编写详细的项目文档,包括技术细节和架构说明
|
| 95 |
-
- 创建用户指南,解释如何使用互动式故事生成器
|
| 96 |
-
- 在Hugging Face Space项目页面上提供清晰的使用说明
|
| 97 |
-
|
| 98 |
-
注意:
|
| 99 |
-
1. 在开发过程中,请确保遵循Python的PEP 8编码规范,并为函数和类添加适当的文档字符串。
|
| 100 |
-
2. 使用Llama模型时,请确保遵守相关的使用条款和许可协议。
|
| 101 |
-
3. 考虑到Hugging Face Space的资源限制,可能需要对模型进行优化或选择较小的Llama变体。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|