Spaces:
Sleeping
Sleeping
Download src/ui/callbacks.py from Jky71/doculearn-rag: direct link, hf CLI and curl.
- Browser
- Download file 9.85 kB
-
https://huggingface.co/spaces/Jky71/doculearn-rag/resolve/main/src/ui/callbacks.py
- Command line
-
hf download hf://spaces/Jky71/doculearn-rag/src/ui/callbacks.py
-
curl -L -o callbacks.py https://huggingface.co/spaces/Jky71/doculearn-rag/resolve/main/src/ui/callbacks.py
9.85 kB
| from __future__ import annotations | |
| import html | |
| import json | |
| import gradio as gr | |
| from src.export import export | |
| from src.learning import generate_flashcards, generate_quiz, summarize | |
| from src.llm import set_runtime_gemini_api_key | |
| from src.rag import answer | |
| from src.store import delete_document_by_filename, list_documents | |
| from src.ui.helpers import PROGRESS | |
| from src.ui.interactive import render_flashcard_html, render_quiz_html | |
| from src.ui.uploads import build_filters | |
| def _chat_message_text(content: object) -> str: | |
| if isinstance(content, str): | |
| return content | |
| if isinstance(content, list): | |
| parts: list[str] = [] | |
| for item in content: | |
| if isinstance(item, dict) and isinstance(item.get("text"), str): | |
| parts.append(item["text"]) | |
| return "".join(parts) | |
| return "" | |
| def _normalize_chat_history(history: list[dict[str, object]] | None) -> list[gr.ChatMessage]: | |
| normalized: list[gr.ChatMessage] = [] | |
| for message in history or []: | |
| if not isinstance(message, dict): | |
| continue | |
| role = message.get("role") | |
| text = _chat_message_text(message.get("content")) | |
| if role in {"user", "assistant", "system"} and text: | |
| normalized.append(gr.ChatMessage(role=str(role), content=text)) | |
| return normalized | |
| def ask_question( | |
| question: str, | |
| k: int, | |
| selected_docs: list[str], | |
| page: str, | |
| gemini_key: str, | |
| ) -> tuple[str, str]: | |
| if not question or not question.strip(): | |
| return "Please enter a question.", "" | |
| page_num = None if page == "All pages" else int(page) | |
| set_runtime_gemini_api_key(gemini_key) | |
| try: | |
| result = answer( | |
| question.strip(), | |
| k=int(k), | |
| filters=build_filters(selected_docs, page_num), | |
| ) | |
| except RuntimeError as error: | |
| message = _generation_message(error) | |
| return message, json.dumps({"error": message}, ensure_ascii=False, indent=2) | |
| return ( | |
| str(export(result, fmt="md")), | |
| json.dumps(result.model_dump(), ensure_ascii=False, indent=2), | |
| ) | |
| def ask_chat( | |
| message: str, | |
| history: list[dict[str, object]] | None, | |
| k: int, | |
| selected_docs: list[str], | |
| page: str, | |
| gemini_key: str, | |
| progress: gr.Progress = PROGRESS, | |
| ) -> tuple[list[gr.ChatMessage], str, str]: | |
| user_text = (message or "").strip() | |
| turns = _normalize_chat_history(history) | |
| if not user_text: | |
| return turns, "", "" | |
| progress(0.2, desc="Retrieving context…") | |
| answer_md, raw = ask_question(user_text, k, selected_docs, page, gemini_key) | |
| progress(0.9, desc="Formatting answer…") | |
| turns.append(gr.ChatMessage(role="user", content=user_text)) | |
| turns.append(gr.ChatMessage(role="assistant", content=answer_md)) | |
| return turns, raw, "" | |
| def summarize_documents( | |
| query: str, | |
| k: int, | |
| selected_docs: list[str], | |
| page: str, | |
| gemini_key: str, | |
| progress: gr.Progress = PROGRESS, | |
| ) -> tuple[str, str]: | |
| page_num = None if page == "All pages" else int(page) | |
| set_runtime_gemini_api_key(gemini_key) | |
| progress(0.3, desc="Retrieving context…") | |
| try: | |
| result = summarize( | |
| query=query.strip() or None, | |
| k=int(k), | |
| filters=build_filters(selected_docs, page_num), | |
| ) | |
| except RuntimeError as error: | |
| raise _generation_error(error) from error | |
| progress(0.9, desc="Formatting results…") | |
| return str(export(result, fmt="md")), result.model_dump_json(indent=2) | |
| def generate_quiz_set( | |
| query: str, | |
| count: int, | |
| k: int, | |
| selected_docs: list[str], | |
| page: str, | |
| gemini_key: str, | |
| progress: gr.Progress = PROGRESS, | |
| ) -> tuple[str, str]: | |
| page_num = None if page == "All pages" else int(page) | |
| set_runtime_gemini_api_key(gemini_key) | |
| progress(0.3, desc="Retrieving context…") | |
| try: | |
| result = generate_quiz( | |
| query=query.strip() or None, | |
| count=int(count), | |
| k=int(k), | |
| filters=build_filters(selected_docs, page_num), | |
| ) | |
| except RuntimeError as error: | |
| raise _generation_error(error) from error | |
| progress(0.9, desc="Formatting results…") | |
| return str(export(result, fmt="md")), result.model_dump_json(indent=2) | |
| def generate_flashcard_set( | |
| query: str, | |
| count: int, | |
| k: int, | |
| selected_docs: list[str], | |
| page: str, | |
| gemini_key: str, | |
| progress: gr.Progress = PROGRESS, | |
| ) -> tuple[str, str]: | |
| page_num = None if page == "All pages" else int(page) | |
| set_runtime_gemini_api_key(gemini_key) | |
| progress(0.3, desc="Retrieving context…") | |
| try: | |
| result = generate_flashcards( | |
| query=query.strip() or None, | |
| count=int(count), | |
| k=int(k), | |
| filters=build_filters(selected_docs, page_num), | |
| ) | |
| except RuntimeError as error: | |
| raise _generation_error(error) from error | |
| progress(0.9, desc="Formatting results…") | |
| return str(export(result, fmt="md")), result.model_dump_json(indent=2) | |
| def generate_quiz_set_interactive( | |
| query: str, | |
| count: int, | |
| k: int, | |
| selected_docs: list[str], | |
| page: str, | |
| gemini_key: str, | |
| progress: gr.Progress = PROGRESS, | |
| ) -> tuple[str, str, str]: | |
| """Generate quiz and return (interactive_html, markdown, json).""" | |
| page_num = None if page == "All pages" else int(page) | |
| set_runtime_gemini_api_key(gemini_key) | |
| progress(0.3, desc="Retrieving context…") | |
| try: | |
| result = generate_quiz( | |
| query=query.strip() or None, | |
| count=int(count), | |
| k=int(k), | |
| filters=build_filters(selected_docs, page_num), | |
| ) | |
| except RuntimeError as error: | |
| raise _generation_error(error) from error | |
| progress(0.9, desc="Formatting results…") | |
| html_str = render_quiz_html(result) | |
| md_str = str(export(result, fmt="md")) | |
| return html_str, md_str, result.model_dump_json(indent=2) | |
| def generate_flashcard_set_interactive( | |
| query: str, | |
| count: int, | |
| k: int, | |
| selected_docs: list[str], | |
| page: str, | |
| gemini_key: str, | |
| progress: gr.Progress = PROGRESS, | |
| ) -> tuple[str, str, str]: | |
| """Generate flashcards and return (interactive_html, markdown, json).""" | |
| page_num = None if page == "All pages" else int(page) | |
| set_runtime_gemini_api_key(gemini_key) | |
| progress(0.3, desc="Retrieving context…") | |
| try: | |
| result = generate_flashcards( | |
| query=query.strip() or None, | |
| count=int(count), | |
| k=int(k), | |
| filters=build_filters(selected_docs, page_num), | |
| ) | |
| except RuntimeError as error: | |
| raise _generation_error(error) from error | |
| progress(0.9, desc="Formatting results…") | |
| html_str = render_flashcard_html(result) | |
| md_str = str(export(result, fmt="md")) | |
| return html_str, md_str, result.model_dump_json(indent=2) | |
| def delete_selected_docs( | |
| selected: list[str], | |
| progress: gr.Progress = PROGRESS, | |
| ) -> tuple[list[str], str]: | |
| """Delete selected documents from the vector store.""" | |
| if not selected: | |
| raise gr.Error("No documents selected for deletion.") | |
| for fn in selected: | |
| progress(None, desc=f"Deleting {fn}…") | |
| delete_document_by_filename(str(fn)) | |
| remaining = list_documents() | |
| filenames = [str(d["filename"]) for d in remaining] | |
| return filenames, f"Deleted {len(selected)} document(s)." | |
| def prepare_delete( | |
| selected: list[str], | |
| ) -> tuple[str, dict[str, bool], dict[str, bool], dict[str, bool], dict[str, bool]]: | |
| if not selected: | |
| raise gr.Error("Select at least one document before deleting.") | |
| count = len(selected) | |
| names = ", ".join(html.escape(str(name), quote=False) for name in selected[:3]) | |
| if count > 3: | |
| names += f" and {count - 3} more" | |
| prompt = ( | |
| '<div class="delete-confirmation-message">' | |
| f"Delete <strong>{names}</strong>? This removes the indexed vectors." | |
| " The original PDF remains available for re-indexing.</div>" | |
| ) | |
| return ( | |
| prompt, | |
| gr.update(visible=True), | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| ) | |
| def reset_delete_confirmation() -> tuple[ | |
| str, dict[str, bool], dict[str, bool], dict[str, bool], dict[str, bool] | |
| ]: | |
| return ( | |
| "", | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| ) | |
| def clear_chat() -> tuple[list[list[str]], str]: | |
| """Clear the chat history.""" | |
| return [], "" | |
| def _empty_result_error() -> gr.Error: | |
| return gr.Error( | |
| "No relevant content found. Please upload documents, select a scope, and try again." | |
| ) | |
| def _generation_error(error: RuntimeError) -> gr.Error: | |
| return gr.Error(_generation_message(error)) | |
| def _generation_message(error: RuntimeError) -> str: | |
| if "Missing Gemini API key" in str(error): | |
| return ( | |
| "Gemini API key is missing. Open the Gemini API key section, enter a key, " | |
| "or set GEMINI_API_KEY before generating." | |
| ) | |
| error_text = str(error).lower() | |
| if "401" in error_text or "403" in error_text or "unauthorized" in error_text: | |
| return "Gemini rejected the API key. Check the key and try again." | |
| if "429" in error_text or "rate limit" in error_text: | |
| return "Gemini rate limit reached. Wait briefly, then click Generate or Send again." | |
| if "timeout" in error_text or "timed out" in error_text: | |
| return "Gemini timed out while generating. Check your connection and try again." | |
| if "No relevant content" in str(error): | |
| return "No relevant content found. Upload documents, select a scope, and try again." | |
| return "Generation failed. Check your connection and try Generate or Send again." | |