返回 Pixelle-Video
faq.py
根目录 / web / components / faq.py
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 """
14 FAQ component for displaying frequently asked questions
15 """
16
17 import re
18 from pathlib import Path
19 from typing import Optional
20
21 import streamlit as st
22 from loguru import logger
23
24 from web.i18n import get_language, tr
25
26
27 def load_faq_content(language: str) -> Optional[str]:
28 """
29 Load FAQ content based on current language
30
31 Args:
32 language: Current language code (e.g., "zh_CN", "en_US")
33
34 Returns:
35 FAQ content as markdown string, or None if file not found
36 """
37 # Determine which FAQ file to load based on language
38 # For Chinese (zh_CN), use FAQ_CN.md
39 # For all other languages, use FAQ.md (English)
40 project_root = Path(__file__).resolve().parent.parent.parent
41
42 if language.startswith("zh"):
43 faq_file = project_root / "docs" / "FAQ_CN.md"
44 else:
45 faq_file = project_root / "docs" / "FAQ.md"
46
47 try:
48 if faq_file.exists():
49 with open(faq_file, "r", encoding="utf-8") as f:
50 content = f.read()
51 logger.debug(f"Loaded FAQ from: {faq_file}")
52 return content
53 else:
54 logger.warning(f"FAQ file not found: {faq_file}")
55 return None
56 except Exception as e:
57 logger.error(f"Failed to load FAQ file {faq_file}: {e}")
58 return None
59
60
61 def parse_faq_sections(content: str) -> list[tuple[str, str]]:
62 """
63 Parse FAQ content into sections by ### headings
64
65 Args:
66 content: Raw markdown content
67
68 Returns:
69 List of (question, answer) tuples
70 """
71 # Remove the first main heading (starts with #, not ###)
72 lines = content.split('\n')
73 if lines and lines[0].startswith('#') and not lines[0].startswith('##'):
74 content = '\n'.join(lines[1:])
75
76 # Split by ### headings (top-level questions)
77 # Pattern matches ### at start of line followed by question text
78 pattern = r'^###\s+(.+?)$'
79
80 sections = []
81 current_question = None
82 current_answer_lines = []
83
84 for line in content.split('\n'):
85 match = re.match(pattern, line)
86 if match:
87 # Save previous section if exists
88 if current_question is not None:
89 answer = '\n'.join(current_answer_lines).strip()
90 sections.append((current_question, answer))
91 # Start new section
92 current_question = match.group(1).strip()
93 current_answer_lines = []
94 else:
95 current_answer_lines.append(line)
96
97 # Save last section
98 if current_question is not None:
99 answer = '\n'.join(current_answer_lines).strip()
100 sections.append((current_question, answer))
101
102 return sections
103
104
105 def render_faq_sidebar():
106 """
107 Render FAQ in the sidebar
108
109 This component displays frequently asked questions in the sidebar,
110 allowing users to quickly find answers without leaving the main interface.
111 """
112 with st.sidebar:
113 # FAQ header with icon
114 # st.markdown(f"### 🙋‍♀️ {tr('faq.title', fallback='FAQ')}")
115
116 # Get current language
117 current_language = get_language()
118
119 # Load FAQ content
120 faq_content = load_faq_content(current_language)
121
122 if faq_content:
123 # Display FAQ in an expander, expanded by default
124 with st.expander(tr('faq.expand_to_view', fallback='FAQ'), expanded=True):
125 # Parse FAQ into sections
126 sections = parse_faq_sections(faq_content)
127
128 # Display each question in its own collapsible expander
129 for question, answer in sections:
130 with st.expander(question, expanded=False):
131 st.markdown(answer, unsafe_allow_html=True)
132
133 # Add a link to GitHub issues for more help
134 st.markdown(
135 f"💡 {tr('faq.more_help', fallback='Need more help?')} "
136 f"[GitHub Issues](https://github.com/AIDC-AI/Pixelle-Video/issues)"
137 )
138 else:
139 # If FAQ cannot be loaded, only show the GitHub link
140 st.markdown(f"### 💡 {tr('faq.more_help', fallback='Need help?')}")
141 st.markdown(
142 f"[GitHub Issues](https://github.com/AIDC-AI/Pixelle-Video/issues) | "
143 f"[Documentation](https://aidc-ai.github.io/Pixelle-Video)"
144 )
145
145 lines PYTHON