Spaces:
Runtime error
Runtime error
| """ | |
| Handbook Generator — Gradio UI (legacy fallback). | |
| Primary UI is streamlit_app.py. | |
| Run: python app.py | |
| """ | |
| import asyncio | |
| import shutil | |
| from pathlib import Path | |
| import gradio as gr | |
| from config import GROK_API_KEY, UPLOADS_DIR, BASE_DIR | |
| from handbook_generator import build_handbook | |
| from rag import get_context_for_query, index_pdf, reset_index | |
| HANDBOOK_EXPORT_PATH = BASE_DIR / "handbook_export.md" | |
| def _run_async(coro): | |
| """Run an async coroutine from sync Gradio code.""" | |
| try: | |
| return asyncio.run(coro) | |
| except RuntimeError: | |
| import concurrent.futures | |
| with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: | |
| return pool.submit(asyncio.run, coro).result(timeout=300) | |
| def ensure_api_key(): | |
| if not GROK_API_KEY: | |
| raise gr.Error( | |
| "GROK_API_KEY is not set. Create a .env file in the ass2 folder with: GROK_API_KEY=your-key" | |
| ) | |
| def _file_path(f): | |
| """Get path from Gradio file input (path string or object with .name).""" | |
| if f is None: | |
| return None | |
| if isinstance(f, (str, Path)): | |
| return Path(f) | |
| return Path(getattr(f, "name", str(f))) | |
| def upload_and_index(files): | |
| """Handle PDF upload(s) and index into LightRAG.""" | |
| if not files: | |
| return "No files selected." | |
| ensure_api_key() | |
| reset_index() | |
| saved = [] | |
| for f in (files if isinstance(files, list) else [files]): | |
| path = _file_path(f) | |
| if path is None or not path.exists(): | |
| continue | |
| dest = UPLOADS_DIR / path.name | |
| try: | |
| shutil.copy(str(path), str(dest)) | |
| except Exception: | |
| dest = path | |
| try: | |
| n = _run_async(index_pdf(dest, source_name=path.name)) | |
| saved.append(f"{path.name}: indexed") | |
| except Exception as e: | |
| saved.append(f"{path.name}: Error - {e}") | |
| return "\n".join(saved) if saved else "No PDFs processed." | |
| def chat(message, history): | |
| """RAG chat: retrieve context and answer using Grok via LiteLLM.""" | |
| ensure_api_key() | |
| from litellm import completion | |
| from config import CHAT_MODEL | |
| context = _run_async(get_context_for_query(message)) | |
| if not context or not context.strip(): | |
| context = "No documents have been uploaded yet. Ask the user to upload PDFs first." | |
| system = ( | |
| "You are a helpful assistant. Answer based ONLY on the following context " | |
| "from the user's uploaded documents. If the answer is not in the context, say so clearly." | |
| ) | |
| user_content = f"Context from uploaded documents:\n\n{context}\n\n---\n\nUser question: {message}" | |
| resp = completion( | |
| model=CHAT_MODEL, | |
| messages=[ | |
| {"role": "system", "content": system}, | |
| {"role": "user", "content": user_content}, | |
| ], | |
| api_key=GROK_API_KEY, | |
| max_tokens=1500, | |
| temperature=0.3, | |
| ) | |
| return (resp.choices[0].message.content or "").strip() | |
| def run_handbook_simple(topic): | |
| """Generate handbook and return (status, markdown).""" | |
| ensure_api_key() | |
| if not (topic and topic.strip()): | |
| return "Enter a topic first.", "" | |
| status_msgs = [] | |
| try: | |
| full_md = _run_async(build_handbook(topic.strip(), on_progress=status_msgs.append)) | |
| status = "\n".join(status_msgs) if status_msgs else "Done." | |
| return status, full_md | |
| except Exception as e: | |
| return f"Error: {e}", "" | |
| with gr.Blocks(title="Handbook Generator", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# Handbook Generator\nUpload PDFs, chat about them, and generate a 20,000+ word handbook.") | |
| with gr.Tab("Upload PDFs"): | |
| file_input = gr.File( | |
| file_count="multiple", | |
| file_types=[".pdf"], | |
| label="Upload one or more PDFs", | |
| ) | |
| index_btn = gr.Button("Index PDFs") | |
| index_out = gr.Textbox(label="Index result", lines=4) | |
| with gr.Tab("Chat"): | |
| chatbot = gr.ChatInterface( | |
| fn=chat, | |
| type="messages", | |
| title="Ask questions about your uploaded documents", | |
| ) | |
| with gr.Tab("Generate Handbook"): | |
| gr.Markdown( | |
| "Enter a topic (e.g. *Create a handbook on Retrieval-Augmented Generation*). " | |
| "Generation may take several minutes." | |
| ) | |
| topic_in = gr.Textbox( | |
| label="Handbook topic", | |
| placeholder="e.g. Retrieval-Augmented Generation", | |
| lines=1, | |
| ) | |
| gen_btn = gr.Button("Generate 20k-word handbook") | |
| status_out = gr.Textbox(label="Status", lines=4, interactive=False) | |
| handbook_out = gr.Markdown(label="Handbook (Markdown)") | |
| export_btn = gr.DownloadButton("Export as Markdown", visible=False) | |
| index_btn.click( | |
| fn=lambda files: upload_and_index(files) if files else "No files selected.", | |
| inputs=[file_input], | |
| outputs=[index_out], | |
| ) | |
| def do_handbook(topic): | |
| status, md = run_handbook_simple(topic) | |
| if md: | |
| HANDBOOK_EXPORT_PATH.write_text(md, encoding="utf-8") | |
| return ( | |
| status, | |
| md, | |
| gr.update(visible=bool(md), value=str(HANDBOOK_EXPORT_PATH) if md else None), | |
| ) | |
| gen_btn.click( | |
| fn=do_handbook, | |
| inputs=[topic_in], | |
| outputs=[status_out, handbook_out, export_btn], | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(server_name="127.0.0.1", server_port=7860) | |