Merge branch 'user-api-key'
Browse files- src/plan/app_text2plan.py +76 -14
src/plan/app_text2plan.py
CHANGED
|
@@ -97,9 +97,18 @@ class MarkdownBuilder:
|
|
| 97 |
class SessionState:
|
| 98 |
"""
|
| 99 |
In a multi-user environment (e.g. Hugging Face Spaces), this class hold each users state.
|
| 100 |
-
In a single-user environment, this class is used to hold the state of
|
|
|
|
|
|
|
|
|
|
| 101 |
"""
|
| 102 |
def __init__(self):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
# Holds the subprocess.Popen object for the currently running pipeline process.
|
| 104 |
self.active_proc = None
|
| 105 |
# A threading.Event used to signal that the running process should stop.
|
|
@@ -117,14 +126,11 @@ class SessionState:
|
|
| 117 |
"""
|
| 118 |
return self
|
| 119 |
|
| 120 |
-
def run_planner(submit_or_retry_button, plan_prompt,
|
| 121 |
"""
|
| 122 |
Generator function for launching the pipeline process and streaming updates.
|
| 123 |
The session state is carried in a SessionState instance.
|
| 124 |
"""
|
| 125 |
-
# Initialize session_state if needed.
|
| 126 |
-
if session_state is None:
|
| 127 |
-
session_state = SessionState()
|
| 128 |
|
| 129 |
# Clear any previous stop signal.
|
| 130 |
session_state.stop_event.clear()
|
|
@@ -163,8 +169,15 @@ def run_planner(submit_or_retry_button, plan_prompt, llm_model, speedvsdetail, s
|
|
| 163 |
# Set environment variables for the pipeline.
|
| 164 |
env = os.environ.copy()
|
| 165 |
env["RUN_ID"] = run_id
|
| 166 |
-
env["LLM_MODEL"] = llm_model
|
| 167 |
-
env["SPEED_VS_DETAIL"] = speedvsdetail
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
start_time = time.perf_counter()
|
| 170 |
# Initialize the last zip creation time to be ZIP_INTERVAL_SECONDS in the past
|
|
@@ -278,8 +291,6 @@ def stop_planner(session_state: SessionState):
|
|
| 278 |
"""
|
| 279 |
Sets a stop flag in the session_state and attempts to terminate the active process.
|
| 280 |
"""
|
| 281 |
-
if session_state is None:
|
| 282 |
-
session_state = SessionState()
|
| 283 |
|
| 284 |
session_state.stop_event.set()
|
| 285 |
|
|
@@ -299,8 +310,6 @@ def open_output_dir(session_state: SessionState):
|
|
| 299 |
"""
|
| 300 |
Opens the latest output directory in the native file explorer.
|
| 301 |
"""
|
| 302 |
-
if session_state is None:
|
| 303 |
-
session_state = SessionState()
|
| 304 |
|
| 305 |
latest_run_dir = session_state.latest_run_dir
|
| 306 |
if not latest_run_dir or not os.path.exists(latest_run_dir):
|
|
@@ -317,6 +326,29 @@ def open_output_dir(session_state: SessionState):
|
|
| 317 |
except Exception as e:
|
| 318 |
return f"Failed to open directory: {e}", session_state
|
| 319 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
# Build the Gradio UI using Blocks.
|
| 321 |
with gr.Blocks(title="PlanExe") as demo_text2plan:
|
| 322 |
gr.Markdown("# PlanExe: crack open pandora’s box of ideas")
|
|
@@ -363,24 +395,31 @@ with gr.Blocks(title="PlanExe") as demo_text2plan:
|
|
| 363 |
label="Speed vs Detail",
|
| 364 |
interactive=True
|
| 365 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
|
| 367 |
with gr.Tab("Join the community"):
|
| 368 |
gr.Markdown("""
|
| 369 |
- [GitHub](https://github.com/neoneye/PlanExe) the source code.
|
| 370 |
- [Discord](https://neoneye.github.io/PlanExe-web/discord) join the community. Suggestions, feedback, and questions are welcome.
|
| 371 |
""")
|
| 372 |
-
|
|
|
|
| 373 |
session_state = gr.State(SessionState())
|
| 374 |
|
| 375 |
# Submit and Retry buttons call run_planner and update the state.
|
| 376 |
submit_btn.click(
|
| 377 |
fn=run_planner,
|
| 378 |
-
inputs=[submit_btn, prompt_input,
|
| 379 |
outputs=[output_markdown, download_output, session_state]
|
| 380 |
)
|
| 381 |
retry_btn.click(
|
| 382 |
fn=run_planner,
|
| 383 |
-
inputs=[retry_btn, prompt_input,
|
| 384 |
outputs=[output_markdown, download_output, session_state]
|
| 385 |
)
|
| 386 |
# The Stop button uses the state to terminate the running process.
|
|
@@ -396,6 +435,29 @@ with gr.Blocks(title="PlanExe") as demo_text2plan:
|
|
| 396 |
outputs=[status_markdown, session_state]
|
| 397 |
)
|
| 398 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
if __name__ == "__main__":
|
| 400 |
# print("Environment variables Gradio:\n" + get_env_as_string() + "\n\n\n")
|
| 401 |
|
|
|
|
| 97 |
class SessionState:
|
| 98 |
"""
|
| 99 |
In a multi-user environment (e.g. Hugging Face Spaces), this class hold each users state.
|
| 100 |
+
In a single-user environment, this class is used to hold the state of that lonely user.
|
| 101 |
+
|
| 102 |
+
IDEA: Persist the user settings for longer. The settings survive a page refresh, but not a server restart.
|
| 103 |
+
The browser has a local storage. Can Gradio access that, so that the settings are remembered between sessions?
|
| 104 |
"""
|
| 105 |
def __init__(self):
|
| 106 |
+
# Settings: the user's OpenRouter API key.
|
| 107 |
+
self.openrouter_api_key = "" # Initialize to empty string
|
| 108 |
+
# Settings: The model that the user has picked.
|
| 109 |
+
self.llm_model = available_model_names[0]
|
| 110 |
+
# Settings: The speedvsdetail that the user has picked.
|
| 111 |
+
self.speedvsdetail = SpeedVsDetailEnum.ALL_DETAILS_BUT_SLOW
|
| 112 |
# Holds the subprocess.Popen object for the currently running pipeline process.
|
| 113 |
self.active_proc = None
|
| 114 |
# A threading.Event used to signal that the running process should stop.
|
|
|
|
| 126 |
"""
|
| 127 |
return self
|
| 128 |
|
| 129 |
+
def run_planner(submit_or_retry_button, plan_prompt, session_state: SessionState):
|
| 130 |
"""
|
| 131 |
Generator function for launching the pipeline process and streaming updates.
|
| 132 |
The session state is carried in a SessionState instance.
|
| 133 |
"""
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
# Clear any previous stop signal.
|
| 136 |
session_state.stop_event.clear()
|
|
|
|
| 169 |
# Set environment variables for the pipeline.
|
| 170 |
env = os.environ.copy()
|
| 171 |
env["RUN_ID"] = run_id
|
| 172 |
+
env["LLM_MODEL"] = session_state.llm_model
|
| 173 |
+
env["SPEED_VS_DETAIL"] = session_state.speedvsdetail
|
| 174 |
+
|
| 175 |
+
# If there is a non-empty OpenRouter API key, set it as an environment variable.
|
| 176 |
+
if session_state.openrouter_api_key and len(session_state.openrouter_api_key) > 0:
|
| 177 |
+
print("Setting OpenRouter API key as environment variable.")
|
| 178 |
+
env["OPENROUTER_API_KEY"] = session_state.openrouter_api_key
|
| 179 |
+
else:
|
| 180 |
+
print("No OpenRouter API key provided.")
|
| 181 |
|
| 182 |
start_time = time.perf_counter()
|
| 183 |
# Initialize the last zip creation time to be ZIP_INTERVAL_SECONDS in the past
|
|
|
|
| 291 |
"""
|
| 292 |
Sets a stop flag in the session_state and attempts to terminate the active process.
|
| 293 |
"""
|
|
|
|
|
|
|
| 294 |
|
| 295 |
session_state.stop_event.set()
|
| 296 |
|
|
|
|
| 310 |
"""
|
| 311 |
Opens the latest output directory in the native file explorer.
|
| 312 |
"""
|
|
|
|
|
|
|
| 313 |
|
| 314 |
latest_run_dir = session_state.latest_run_dir
|
| 315 |
if not latest_run_dir or not os.path.exists(latest_run_dir):
|
|
|
|
| 326 |
except Exception as e:
|
| 327 |
return f"Failed to open directory: {e}", session_state
|
| 328 |
|
| 329 |
+
def update_openrouter_api_key(openrouter_api_key, session_state: SessionState):
|
| 330 |
+
"""Updates the OpenRouter API key in the session state."""
|
| 331 |
+
session_state.openrouter_api_key = openrouter_api_key
|
| 332 |
+
return openrouter_api_key, session_state
|
| 333 |
+
|
| 334 |
+
def update_model_radio(llm_model, session_state: SessionState):
|
| 335 |
+
"""Updates the llm_model in the session state"""
|
| 336 |
+
session_state.llm_model = llm_model
|
| 337 |
+
return llm_model, session_state
|
| 338 |
+
|
| 339 |
+
def update_speedvsdetail_radio(speedvsdetail, session_state: SessionState):
|
| 340 |
+
"""Updates the speedvsdetail in the session state"""
|
| 341 |
+
session_state.speedvsdetail = speedvsdetail
|
| 342 |
+
return speedvsdetail, session_state
|
| 343 |
+
|
| 344 |
+
def initialize_settings(session_state: SessionState):
|
| 345 |
+
"""Initializes the settings from session_state, if available."""
|
| 346 |
+
return (session_state.openrouter_api_key,
|
| 347 |
+
session_state.llm_model,
|
| 348 |
+
session_state.speedvsdetail,
|
| 349 |
+
session_state)
|
| 350 |
+
|
| 351 |
+
|
| 352 |
# Build the Gradio UI using Blocks.
|
| 353 |
with gr.Blocks(title="PlanExe") as demo_text2plan:
|
| 354 |
gr.Markdown("# PlanExe: crack open pandora’s box of ideas")
|
|
|
|
| 395 |
label="Speed vs Detail",
|
| 396 |
interactive=True
|
| 397 |
)
|
| 398 |
+
openrouter_api_key_text = gr.Textbox(
|
| 399 |
+
label="OpenRouter API Key",
|
| 400 |
+
type="password",
|
| 401 |
+
placeholder="Enter your OpenRouter API key (optional)",
|
| 402 |
+
info="Sign up at [OpenRouter](https://openrouter.ai/) to get an API key. A small top-up (e.g. 5 USD) is needed to access paid models."
|
| 403 |
+
)
|
| 404 |
|
| 405 |
with gr.Tab("Join the community"):
|
| 406 |
gr.Markdown("""
|
| 407 |
- [GitHub](https://github.com/neoneye/PlanExe) the source code.
|
| 408 |
- [Discord](https://neoneye.github.io/PlanExe-web/discord) join the community. Suggestions, feedback, and questions are welcome.
|
| 409 |
""")
|
| 410 |
+
|
| 411 |
+
# Manage the state of the current user
|
| 412 |
session_state = gr.State(SessionState())
|
| 413 |
|
| 414 |
# Submit and Retry buttons call run_planner and update the state.
|
| 415 |
submit_btn.click(
|
| 416 |
fn=run_planner,
|
| 417 |
+
inputs=[submit_btn, prompt_input, session_state],
|
| 418 |
outputs=[output_markdown, download_output, session_state]
|
| 419 |
)
|
| 420 |
retry_btn.click(
|
| 421 |
fn=run_planner,
|
| 422 |
+
inputs=[retry_btn, prompt_input, session_state],
|
| 423 |
outputs=[output_markdown, download_output, session_state]
|
| 424 |
)
|
| 425 |
# The Stop button uses the state to terminate the running process.
|
|
|
|
| 435 |
outputs=[status_markdown, session_state]
|
| 436 |
)
|
| 437 |
|
| 438 |
+
openrouter_api_key_text.change(
|
| 439 |
+
fn=update_openrouter_api_key,
|
| 440 |
+
inputs=[openrouter_api_key_text, session_state],
|
| 441 |
+
outputs=[openrouter_api_key_text, session_state]
|
| 442 |
+
)
|
| 443 |
+
model_radio.change(
|
| 444 |
+
fn=update_model_radio,
|
| 445 |
+
inputs=[model_radio, session_state],
|
| 446 |
+
outputs=[model_radio, session_state]
|
| 447 |
+
)
|
| 448 |
+
speedvsdetail_radio.change(
|
| 449 |
+
fn=update_speedvsdetail_radio,
|
| 450 |
+
inputs=[speedvsdetail_radio, session_state],
|
| 451 |
+
outputs=[speedvsdetail_radio, session_state]
|
| 452 |
+
)
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
demo_text2plan.load(
|
| 456 |
+
fn=initialize_settings,
|
| 457 |
+
inputs=[session_state],
|
| 458 |
+
outputs=[openrouter_api_key_text, model_radio, speedvsdetail_radio, session_state]
|
| 459 |
+
)
|
| 460 |
+
|
| 461 |
if __name__ == "__main__":
|
| 462 |
# print("Environment variables Gradio:\n" + get_env_as_string() + "\n\n\n")
|
| 463 |
|