|
| 1 | +import hashlib |
1 | 2 | import importlib.metadata |
2 | 3 | import json |
3 | 4 | import logging |
@@ -186,35 +187,60 @@ def parse_section(section): |
186 | 187 | except Exception: |
187 | 188 | changelog_content = (pkgutil.get_data('open_webui', 'CHANGELOG.md') or b'').decode() |
188 | 189 |
|
189 | | -# Convert markdown content to HTML |
190 | | -html_content = markdown.markdown(changelog_content) |
| 190 | +# Cache parsed CHANGELOG to avoid markdown+BeautifulSoup overhead on every cold |
| 191 | +# start. The cache is content-addressed (MD5 of raw markdown) so it invalidates |
| 192 | +# automatically whenever CHANGELOG.md changes (e.g. on a version upgrade). |
| 193 | +_changelog_cache_dir = Path(os.getenv('DATA_DIR', BACKEND_DIR / 'data')) |
| 194 | +_changelog_cache_path = _changelog_cache_dir / 'changelog_cache.json' |
| 195 | +_changelog_hash = hashlib.md5(changelog_content.encode()).hexdigest() |
191 | 196 |
|
192 | | -# Parse the HTML content |
193 | | -soup = BeautifulSoup(html_content, 'html.parser') |
| 197 | +changelog_json = None |
| 198 | +try: |
| 199 | + _cached = json.loads(_changelog_cache_path.read_text(encoding='utf8')) |
| 200 | + if _cached.get('_hash') == _changelog_hash: |
| 201 | + changelog_json = _cached['data'] |
| 202 | +except Exception: |
| 203 | + pass |
| 204 | + |
| 205 | +if changelog_json is None: |
| 206 | + # Convert markdown content to HTML |
| 207 | + html_content = markdown.markdown(changelog_content) |
| 208 | + |
| 209 | + # Parse the HTML content |
| 210 | + soup = BeautifulSoup(html_content, 'html.parser') |
194 | 211 |
|
195 | | -# Initialize JSON structure |
196 | | -changelog_json = {} |
| 212 | + # Initialize JSON structure |
| 213 | + changelog_json = {} |
197 | 214 |
|
198 | | -# Iterate over each version |
199 | | -for version in soup.find_all('h2'): |
200 | | - version_number = version.get_text().strip().split(' - ')[0][1:-1] # Remove brackets |
201 | | - date = version.get_text().strip().split(' - ')[1] |
| 215 | + # Iterate over each version |
| 216 | + for version in soup.find_all('h2'): |
| 217 | + version_number = version.get_text().strip().split(' - ')[0][1:-1] # Remove brackets |
| 218 | + date = version.get_text().strip().split(' - ')[1] |
202 | 219 |
|
203 | | - version_data = {'date': date} |
| 220 | + version_data = {'date': date} |
204 | 221 |
|
205 | | - # Find the next sibling that is a h3 tag (section title) |
206 | | - current = version.find_next_sibling() |
| 222 | + # Find the next sibling that is a h3 tag (section title) |
| 223 | + current = version.find_next_sibling() |
207 | 224 |
|
208 | | - while current and current.name != 'h2': |
209 | | - if current.name == 'h3': |
210 | | - section_title = current.get_text().lower() # e.g., "added", "fixed" |
211 | | - section_items = parse_section(current.find_next_sibling('ul')) |
212 | | - version_data[section_title] = section_items |
| 225 | + while current and current.name != 'h2': |
| 226 | + if current.name == 'h3': |
| 227 | + section_title = current.get_text().lower() # e.g., "added", "fixed" |
| 228 | + section_items = parse_section(current.find_next_sibling('ul')) |
| 229 | + version_data[section_title] = section_items |
213 | 230 |
|
214 | | - # Move to the next element |
215 | | - current = current.find_next_sibling() |
| 231 | + # Move to the next element |
| 232 | + current = current.find_next_sibling() |
216 | 233 |
|
217 | | - changelog_json[version_number] = version_data |
| 234 | + changelog_json[version_number] = version_data |
| 235 | + |
| 236 | + try: |
| 237 | + _changelog_cache_dir.mkdir(parents=True, exist_ok=True) |
| 238 | + _changelog_cache_path.write_text( |
| 239 | + json.dumps({'_hash': _changelog_hash, 'data': changelog_json}), |
| 240 | + encoding='utf8', |
| 241 | + ) |
| 242 | + except Exception: |
| 243 | + pass |
218 | 244 |
|
219 | 245 | CHANGELOG = changelog_json |
220 | 246 |
|
|
0 commit comments