faq.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. # Copyright (C) 2025 AIDC-AI
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS,
  9. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. # See the License for the specific language governing permissions and
  11. # limitations under the License.
  12. """
  13. FAQ component for displaying frequently asked questions
  14. """
  15. import re
  16. from pathlib import Path
  17. from typing import Optional
  18. import streamlit as st
  19. from loguru import logger
  20. from web.i18n import get_language, tr
  21. def load_faq_content(language: str) -> Optional[str]:
  22. """
  23. Load FAQ content based on current language
  24. Args:
  25. language: Current language code (e.g., "zh_CN", "en_US")
  26. Returns:
  27. FAQ content as markdown string, or None if file not found
  28. """
  29. # Determine which FAQ file to load based on language
  30. # For Chinese (zh_CN), use FAQ_CN.md
  31. # For all other languages, use FAQ.md (English)
  32. project_root = Path(__file__).resolve().parent.parent.parent
  33. if language.startswith("zh"):
  34. faq_file = project_root / "docs" / "FAQ_CN.md"
  35. else:
  36. faq_file = project_root / "docs" / "FAQ.md"
  37. try:
  38. if faq_file.exists():
  39. with open(faq_file, "r", encoding="utf-8") as f:
  40. content = f.read()
  41. logger.debug(f"Loaded FAQ from: {faq_file}")
  42. return content
  43. else:
  44. logger.warning(f"FAQ file not found: {faq_file}")
  45. return None
  46. except Exception as e:
  47. logger.error(f"Failed to load FAQ file {faq_file}: {e}")
  48. return None
  49. def parse_faq_sections(content: str) -> list[tuple[str, str]]:
  50. """
  51. Parse FAQ content into sections by ### headings
  52. Args:
  53. content: Raw markdown content
  54. Returns:
  55. List of (question, answer) tuples
  56. """
  57. # Remove the first main heading (starts with #, not ###)
  58. lines = content.split('\n')
  59. if lines and lines[0].startswith('#') and not lines[0].startswith('##'):
  60. content = '\n'.join(lines[1:])
  61. # Split by ### headings (top-level questions)
  62. # Pattern matches ### at start of line followed by question text
  63. pattern = r'^###\s+(.+?)$'
  64. sections = []
  65. current_question = None
  66. current_answer_lines = []
  67. for line in content.split('\n'):
  68. match = re.match(pattern, line)
  69. if match:
  70. # Save previous section if exists
  71. if current_question is not None:
  72. answer = '\n'.join(current_answer_lines).strip()
  73. sections.append((current_question, answer))
  74. # Start new section
  75. current_question = match.group(1).strip()
  76. current_answer_lines = []
  77. else:
  78. current_answer_lines.append(line)
  79. # Save last section
  80. if current_question is not None:
  81. answer = '\n'.join(current_answer_lines).strip()
  82. sections.append((current_question, answer))
  83. return sections
  84. def render_faq_sidebar():
  85. """
  86. Render FAQ in the sidebar
  87. This component displays frequently asked questions in the sidebar,
  88. allowing users to quickly find answers without leaving the main interface.
  89. """
  90. with st.sidebar:
  91. # FAQ header with icon
  92. # st.markdown(f"### 🙋‍♀️ {tr('faq.title', fallback='FAQ')}")
  93. # Get current language
  94. current_language = get_language()
  95. # Load FAQ content
  96. faq_content = load_faq_content(current_language)
  97. if faq_content:
  98. # Display FAQ in an expander, expanded by default
  99. with st.expander(tr('faq.expand_to_view', fallback='FAQ'), expanded=True):
  100. # Parse FAQ into sections
  101. sections = parse_faq_sections(faq_content)
  102. # Display each question in its own collapsible expander
  103. for question, answer in sections:
  104. with st.expander(question, expanded=False):
  105. st.markdown(answer, unsafe_allow_html=True)
  106. # Add a link to GitHub issues for more help
  107. st.markdown(
  108. f"💡 {tr('faq.more_help', fallback='Need more help?')} "
  109. f"[GitHub Issues](https://github.com/AIDC-AI/Pixelle-Video/issues)"
  110. )
  111. else:
  112. # If FAQ cannot be loaded, only show the GitHub link
  113. st.markdown(f"### 💡 {tr('faq.more_help', fallback='Need help?')}")
  114. st.markdown(
  115. f"[GitHub Issues](https://github.com/AIDC-AI/Pixelle-Video/issues) | "
  116. f"[Documentation](https://aidc-ai.github.io/Pixelle-Video)"
  117. )