Skip to content

Long text as an image ​

Turn it on with "textImage": { "enabled": true }; no browser is needed. Rendering uses the Typst compiler (@myriaddreamin/typst-ts-node-compiler) to lay the page out as SVG and @resvg/resvg-js to rasterise it to PNG. The server needs a system CJK font (Debian/Ubuntu: sudo apt install fonts-noto-cjk), and the first render downloads and caches version-pinned packages (cmarker, mitex; plus cetz, cetz-plot and plotsy-3d once a graph is drawn) from the official Typst registry. Startup does one trial render; if it fails the bot only logs text_image_unavailable and the feature stays off.

  • Length gate: a character's final text longer than thresholdChars (default 300) is not sent. In the same turn the model is told the reply exceeded the limit and was not sent, and must resend it as a text_image part of send_reply; that costs one extra model call and is retried only once. If it still answers over the limit (or rendering fails), that text is sent as ordinary split messages, never dropped.
  • text_image part: one kind of send_reply part, taking Markdown (≤ 8000 characters) and an optional short caption; it can go out in order with text, voice and pictures. A character may also use it without waiting for the gate when the content has formulas, tables or pictures. Each text part of send_reply is held to thresholdChars too: an over-long one rejects the whole reply before anything is sent, and the character moves it into a text_image. The system prompt states the limit so the model usually gets it right the first time.
  • Supported: headings, bold, lists, quotes, tables, code blocks; LaTeX math as $…$ (inline) and $$…$$ (display); ![alt](https://…) public images (PNG/JPEG/GIF/WebP, ≤ 4 MiB each, at most 4).
  • Graphs: a fenced block with language plot holds JSON. 2D example: {"x":[-3,3],"y":[-2,2],"plots":[{"y":"sin(x)","label":"sin x"},{"implicit":"x^2+y^2=1"},{"x":"cos(t)","y":"sin(2t)","t":[0,6.28]},{"points":[[1,2],[2,3]]}]}. Each item is a function of x, an implicit equation in x and y, a parametric curve in t, or points. Ranges are optional (chosen automatically; implicit curves are located automatically, and plots without a function of x use equal axis scales); poles such as tan or 1/x break the curve. 3D surface: {"z":"sin(x)*cos(y)","x":[-3,3],"y":[-3,3]} with whole-number ranges. Expressions accept only numbers, x/y/t, + - * / ^, implicit multiplication such as 2x, pi, e and common functions (sin cos tan asin acos atan sinh cosh tanh exp ln log sqrt cbrt abs floor ceil). Sampling happens in JS, so only numbers and escaped legend labels reach Typst; model-written expressions never run as code. Drawing uses the Typst packages cetz-plot (2D) and plotsy-3d (3D), downloaded on first use. At most 3 graphs per image; a broken spec shows its reason in place and the rest still renders.
  • AI pictures: when the character has drawing enabled (see Drawing), a fenced block with language image holding an English description is drawn by the same image model and embedded (at most 2 per image, about 15 s each); on failure, or without drawing, "图片无法生成" is shown in its place.
  • Limits: images are fetched only from public http(s) URLs, redirects are refused, and private/loopback hosts and local paths show "图片无法显示" in place of the picture; raw Typst, <svg>, <a> and similar inside the Markdown are never executed. The image is 720 px wide (dropping to 1x, 360 px, when a page is very tall); more than 8000 px tall or over 9 MB makes rendering fail and that turn falls back to text.
  • Stored content: on both Discord and Telegram the stored message is the caption (the Markdown's first heading when none is given), not the full text, so a long answer does not fill every later turn's context; the character's own session keeps the full Markdown of the call.

BSD 2-Clause License