fix: enable shiki highlighting for t2i templates#7501
fix: enable shiki highlighting for t2i templates#7501camera-2018 wants to merge 1 commit intoAstrBotDevs:masterfrom
Conversation
There was a problem hiding this comment.
Sorry @camera-2018, your pull request is larger than the review limit of 150000 diff characters
There was a problem hiding this comment.
Pull request overview
Adds Shiki-based syntax highlighting support for T2I HTML templates by bundling a browser-ready Shiki runtime and wiring it into the network render flow/templates.
Changes:
- Introduces a Vite build script to generate an IIFE Shiki runtime bundle for a curated set of languages/themes.
- Updates T2I templates to inline the Shiki runtime, base64-decode the markdown source, render with
marked, and then highlight code blocks + render KaTeX. - Updates the network T2I renderer to inject the Shiki runtime and provide
text_base64for templates.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| dashboard/scripts/build-t2i-shiki-runtime.mjs | Builds the bundled Shiki runtime used by T2I templates. |
| dashboard/package.json | Adds a script entry to run the Shiki runtime build. |
| astrbot/core/utils/t2i/template/base.html | Uses injected Shiki runtime to highlight rendered markdown code blocks; switches to text_base64. |
| astrbot/core/utils/t2i/template/astrbot_powershell.html | Same as base template, tuned for dark theme and PowerShell styling. |
| astrbot/core/utils/t2i/network_strategy.py | Injects Shiki runtime into template data and provides text_base64 when using the default render path. |
Comments suppressed due to low confidence (2)
astrbot/core/utils/t2i/network_strategy.py:98
render_custom_template()now always injectsshiki_runtimeintotmpldata, which substantially increases the payload sent to/generate(the runtime is ~MBs). This can noticeably increase latency and may exceed request-size limits on some deployments/proxies. If possible, consider hosting the runtime once (e.g., served by the render endpoint or a static URL) and referencing it via<script src>in the template, or otherwise enabling endpoint-side caching/deduplication.
tmpl_data = {"shiki_runtime": get_shiki_runtime()} | tmpl_data
post_data = {
"tmpl": tmpl_str,
"json": return_url,
"tmpldata": tmpl_data,
"options": default_options,
}
astrbot/core/utils/t2i/network_strategy.py:98
NetworkRenderStrategy.render()providestext_base64, butrender_custom_template()does not. Since the updated built-in templates (base.html,astrbot_powershell.html) now consumetext_base64(and ignoretext), callers usingHtmlRenderer.render_custom_template()with those templates and only providingtextwill render blank content. Consider generatingtext_base64insiderender_custom_template()whentextis present andtext_base64is missing to keep the API usable for custom callers.
async def render_custom_template(
self,
tmpl_str: str,
tmpl_data: dict,
return_url: bool = True,
options: dict | None = None,
) -> str:
"""使用自定义文转图模板"""
default_options = {"full_page": True, "type": "jpeg", "quality": 40}
if options:
default_options |= options
tmpl_data = {"shiki_runtime": get_shiki_runtime()} | tmpl_data
post_data = {
"tmpl": tmpl_str,
"json": return_url,
"tmpldata": tmpl_data,
"options": default_options,
}
| const languageSpecs = [ | ||
| ["bash", "bash"], | ||
| ["css", "css"], | ||
| ["html", "html"], | ||
| ["javascript", "javascript"], | ||
| ["json", "json"], | ||
| ["jsx", "jsx"], | ||
| ["markdown", "markdown"], | ||
| ["powershell", "powershell"], | ||
| ["python", "python"], | ||
| ["sql", "sql"], | ||
| ["tsx", "tsx"], | ||
| ["typescript", "typescript"], | ||
| ["xml", "xml"], | ||
| ["yaml", "yaml"], | ||
| ]; |
There was a problem hiding this comment.
normalizeLanguage() can return "text" for empty/unknown languages, but "text" is not included in languageSpecs / supportedLanguages. If the Shiki core runtime doesn't have a built-in "text" grammar, codeToHtml() will throw both in the try path and in the catch fallback, breaking rendering for unlabeled code fences (common). Add the "text" language to the bundled languageSpecs (and thus supportedLanguages), similar to how the dashboard runtime initializes Shiki with langs: ["text"] (see dashboard/src/utils/shiki.js).
| runtime_path = ( | ||
| Path(__file__).resolve().parent / "template" / "shiki_runtime.iife.js" | ||
| ) | ||
| runtime = runtime_path.read_text(encoding="utf-8") |
There was a problem hiding this comment.
get_shiki_runtime() reads shiki_runtime.iife.js unconditionally; if that file is missing/corrupted in a given installation/package, all network T2I renders will fail before even contacting endpoints. Consider catching OSError/FileNotFoundError, logging a warning, and returning an empty string (or a small stub that disables highlighting) so rendering can still proceed without code highlighting.
| runtime = runtime_path.read_text(encoding="utf-8") | |
| try: | |
| runtime = runtime_path.read_text(encoding="utf-8") | |
| except OSError as e: | |
| logger.warning( | |
| f"Failed to load Shiki runtime from {runtime_path}: {e}. " | |
| "Continuing without code highlighting runtime.", | |
| ) | |
| return "" |
There was a problem hiding this comment.
Code Review
This pull request implements Shiki-based syntax highlighting for the text-to-image (T2I) rendering strategy. Key changes include a new build script for the Shiki runtime, backend updates to serve this runtime and base64-encoded content, and a refactor of the HTML templates to utilize the new highlighting system. Review feedback suggests adding error handling for missing runtime files and optimizing the template data by removing redundant fields and unnecessary escaping logic.
| def get_shiki_runtime() -> str: | ||
| runtime_path = ( | ||
| Path(__file__).resolve().parent / "template" / "shiki_runtime.iife.js" | ||
| ) | ||
| runtime = runtime_path.read_text(encoding="utf-8") | ||
| return runtime.replace("</script", "<\\/script") |
There was a problem hiding this comment.
The get_shiki_runtime function will raise a FileNotFoundError if the shiki_runtime.iife.js file is missing (e.g., if the build script wasn't run or the file wasn't included in the distribution). Since this file is a build artifact from the dashboard directory, it's safer to handle the missing file gracefully and log a clear error message.
@lru_cache(maxsize=1)
def get_shiki_runtime() -> str:
runtime_path = (
Path(__file__).resolve().parent / "template" / "shiki_runtime.iife.js"
)
if not runtime_path.exists():
logger.error(f"Shiki runtime not found at {runtime_path}. Please run the build script in the dashboard directory.")
return ""
runtime = runtime_path.read_text(encoding="utf-8")
return runtime.replace("</script", "<\\/script")| text_base64 = base64.b64encode(text.encode("utf-8")).decode("ascii") | ||
| text = text.replace("`", "\\`") | ||
| return await self.render_custom_template( | ||
| tmpl_str, | ||
| {"text": text, "version": f"v{VERSION}"}, | ||
| {"text": text, "text_base64": text_base64, "version": f"v{VERSION}"}, |
There was a problem hiding this comment.
The text variable is being passed to the template alongside text_base64. However, the updated HTML templates (base.html and astrbot_powershell.html) now use text_base64 for rendering and no longer reference {{ text }}. Passing both redundant data increases the payload size unnecessarily. Additionally, the backtick escaping on line 146 is no longer needed when using base64 encoding.
text_base64 = base64.b64encode(text.encode("utf-8")).decode("ascii")
return await self.render_custom_template(
tmpl_str,
{"text_base64": text_base64, "version": f"v{VERSION}"},
return_url,
)
Modifications / 改动点
给t2i模板添加代码高亮
2.改动后


Screenshots or Test Results / 运行截图或测试结果
Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。
🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。