Skip to content
Merged

v2.16.5 #1781

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
d9a0d70
docs: add v2.17.0-pre.2 technical release notes
ionite34 Sep 16, 2026
3e5766c
docs: add env var override section to v2.16.4 technical notes
ionite34 Sep 16, 2026
a3c137d
Merge pull request #1407 from ionite34/fix/comfy-python313-numpy
mohnjiles Sep 30, 2026
c89c2bb
Resolve backport changelog conflict for Python 3.13 NumPy fix
mohnjiles Sep 30, 2026
1c3260f
Document NumPy backport under forthcoming v2.16.5
mohnjiles Sep 30, 2026
7d7ea36
Merge pull request #1409 from ionite34/fix/release-notes-overflow
mohnjiles Sep 30, 2026
6f2b13b
Resolve release notes backport changelog under v2.16.5
mohnjiles Sep 30, 2026
9042cf7
Merge pull request #1410 from ionite34/backport/main/pr-1407
mohnjiles Sep 30, 2026
551d5b6
Merge approved NumPy backport from main; retain both v2.16.5 fixes
mohnjiles Sep 30, 2026
7f94780
Merge pull request #1411 from ionite34/backport/main/pr-1409
mohnjiles Sep 30, 2026
78e3083
Backport base-model navigation filter fix for 2.16.5
mohnjiles Sep 30, 2026
2c95bb8
Downmerge public main through #1763 for 2.16.5
mohnjiles Oct 1, 2026
4d814c0
Merge branch 'main' into notes-2.17.0-pre.2
mohnjiles Oct 1, 2026
6c83ed7
Merge branch 'main' into notes-2.16.4-env-vars
mohnjiles Oct 1, 2026
01bfb33
Remove contributor shoutouts section from 2.16.5 notes
mohnjiles Oct 1, 2026
8cd1881
Match inline contributor credit convention
mohnjiles Oct 1, 2026
9b90c1f
Merge pull request #1403 from ionite34/notes-2.17.0-pre.2
mohnjiles Oct 1, 2026
8ed12bc
Merge branch 'main' into notes-2.16.4-env-vars
mohnjiles Oct 1, 2026
b9a1096
Merge branch 'main' into fix/1436-base-model-navigation
mohnjiles Oct 1, 2026
4e71f2b
Merge pull request #1404 from ionite34/notes-2.16.4-env-vars
mohnjiles Oct 1, 2026
160e5f6
Merge branch 'main' into fix/1436-base-model-navigation
mohnjiles Oct 1, 2026
6862394
Merge pull request #1414 from ionite34/fix/1436-base-model-navigation
mohnjiles Oct 1, 2026
c81cf75
Merge branch 'main' into downmerge/public-main-20261001
mohnjiles Oct 1, 2026
88ba3c8
Merge pull request #1416 from ionite34/downmerge/public-main-20261001
mohnjiles Oct 1, 2026
116f9f6
Require published ControlNet compatibility fixes in Inference
mohnjiles Oct 1, 2026
6c9020c
Merge pull request #1418 from ionite34/fix-controlnet-node-version
mohnjiles Oct 1, 2026
4d30b92
Backport ComfyUI SageAttention update fix for 2.16.5
mohnjiles Oct 2, 2026
ddef637
Merge pull request #1422 from ionite34/fix/civarchive-web-links-1765
mohnjiles Oct 2, 2026
f54b1cb
Resolve CivArchive backport changelog for v2.16.5
mohnjiles Oct 2, 2026
8f9386c
Merge pull request #1421 from ionite34/backport/2.16.5-sage-update-1764
mohnjiles Oct 2, 2026
8d22597
Merge main into CivArchive backport and retain both fixes
mohnjiles Oct 2, 2026
edf130e
Merge pull request #1423 from ionite34/backport/main/pr-1422
mohnjiles Oct 2, 2026
78d28cd
docs(changelog): add Fizgig and ROCm torch/torchvision pairing entries
mohnjiles Oct 10, 2026
14e357a
Merge pull request #1425 from ionite34/changelog/public-1761-1769-main
mohnjiles Oct 10, 2026
3fc8df1
Merge remote-tracking branch 'fork/main' into downmerg-from-public-1770
mohnjiles Oct 11, 2026
57a32da
docs(changelog): Forge Neo ROCm + --home-dir log path for v2.16.5
mohnjiles Oct 11, 2026
85e5861
Merge pull request #1428 from ionite34/downmerg-from-public-1770
mohnjiles Oct 11, 2026
208b6a8
docs(changelog): supporters shoutout for v2.16.5
mohnjiles Oct 12, 2026
71d80d3
Merge pull request #1430 from ionite34/supporters/v2.16.5
mohnjiles Oct 12, 2026
4158652
Merge pull request #1424 from ionite34/fix-node-tar-extraction-dev
mohnjiles Oct 12, 2026
ea12d66
fix chagenlog
mohnjiles Oct 12, 2026
7a198f4
Merge pull request #1433 from ionite34/backport/main/pr-1424
mohnjiles Oct 12, 2026
e62acc4
Merge pull request #1435 from ionite34/fix/package-delete-size
mohnjiles Oct 12, 2026
f16da95
fix chagenlog
mohnjiles Oct 12, 2026
2b61a89
Merge pull request #1436 from ionite34/backport/main/pr-1435
mohnjiles Oct 12, 2026
8db1640
Merge pull request #1437 from ionite34/fix/wan2gp-python-311
mohnjiles Oct 13, 2026
3e2086e
fix chagenlog
mohnjiles Oct 13, 2026
ff0fa88
Merge pull request #1438 from ionite34/backport/main/pr-1437
mohnjiles Oct 13, 2026
9aab6b0
Backport: Fix Image Lab text clipping, emoji, and generation flows
mohnjiles Oct 13, 2026
6363abc
Merge pull request #1441 from ionite34/backport/main/image-lab-fixes
mohnjiles Oct 13, 2026
a14ff98
docs: expand v2.17.0-pre.2 technical notes
mohnjiles Oct 13, 2026
67da06e
docs(changelog): tidy v2.16.5 to match the pre.2 wording
mohnjiles Oct 13, 2026
2408dd0
docs: add v2.16.5 technical notes
mohnjiles Oct 14, 2026
30a4664
Merge pull request #1443 from ionite34/chore/pre2-tech-notes
mohnjiles Oct 14, 2026
789341c
docs(changelog): refresh supporter shoutouts against the current roster
mohnjiles Oct 17, 2026
860bb1a
fix(packages): point Fizgig's preview image at its new upstream path
mohnjiles Oct 17, 2026
ae09a9a
Merge pull request #1445 from ionite34/chore/2165-shoutout-refresh
mohnjiles Oct 17, 2026
f6fa67a
Merge branch 'main' of https://github.com/LykosAI/StabilityMatrix-Dev…
mohnjiles Oct 17, 2026
3229bac
Merge pull request #1446 from ionite34/downmerg-from-public
mohnjiles Oct 17, 2026
d846438
Merge pull request #1451 from ionite34/fix/github-releases-single-page
mohnjiles Oct 21, 2026
180d668
fix chagenlog
mohnjiles Oct 21, 2026
1529119
Merge pull request #1453 from ionite34/backport/main/pr-1451
mohnjiles Oct 21, 2026
1c9b9b5
Credit jweg79 as a Visionary in the 2.16.5 shoutout
mohnjiles Oct 21, 2026
663f525
Merge pull request #1454 from ionite34/fix-2165-shoutout-jweg79
mohnjiles Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,44 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).

## v2.16.5
> Full technical notes for this release: [docs.lykos.ai/stability-matrix/release-notes/2.16.5](https://docs.lykos.ai/stability-matrix/release-notes/2.16.5)
### Added
- Added **Fizgig**, a LoRA training studio for Flux 2 Klein 9B, Krea 2, MiniMax H3 and Qwen Image 2.1 with dataset prep, training, LoRA repair and extraction in one app ([#1761](https://github.com/LykosAI/StabilityMatrix/pull/1761)) - thanks to @e-nord!
- Added AMD ROCm support for **Stable Diffusion WebUI Forge - Neo** on Windows and Linux. Forge Neo's developer supports NVIDIA only, so report AMD issues with it to Stability Matrix ([#1770](https://github.com/LykosAI/StabilityMatrix/pull/1770)) - thanks to @NeuralFault!
### Changed
- ComfyUI now installs and updates from its current GitHub home, [Comfy-Org/ComfyUI](https://github.com/Comfy-Org/ComfyUI)
### Fixed
- Fixed **Wan2GP** installs on Python 3.11 failing with `sageattention ... has no wheels with a matching Python version tag`. New Wan2GP installs now use Python 3.11 with PyTorch 2.10 and CUDA 13 with matching Triton, SageAttention and Flash Attention, the same setup Wan2GP's own installer uses. CUDA 13 needs NVIDIA driver 580 or newer. GTX 10XX cards stay on Python 3.10
- Existing **Wan2GP** installs on Python 3.10 keep their PyTorch 2.7 setup when updated. If a newer model such as LTX 2.5 fails with a Triton error, add Wan2GP again as a new install, which now uses Python 3.11, and move your outputs across ([#1756](https://github.com/LykosAI/StabilityMatrix/issues/1756))
- Fixed ComfyUI updates downgrading SageAttention post6 to post4 and reinstalling compatible wheels ([#1764](https://github.com/LykosAI/StabilityMatrix/issues/1764)).
- Fixed ComfyUI installation and updates failing on Python 3.13+ because the NumPy 1.x constraint has no compatible wheel ([#1754](https://github.com/LykosAI/StabilityMatrix/issues/1754))
- Fixed **AI-Toolkit** installation failing on Linux while unpacking Node.js with a "Dangerous link path" error ([#1767](https://github.com/LykosAI/StabilityMatrix/issues/1767))
- Fixed **Windows ROCm** PyTorch installs pairing mismatched `torch` and `torchvision` versions, which broke packages at `import torchvision` ([#1769](https://github.com/LykosAI/StabilityMatrix/pull/1769)) - thanks to @NeuralFault!
- ControlNet preprocessing in Inference now offers to update older tagged Inference Core Nodes installs to a version with the ControlNet import fixes and Python 3.12/3.13 support
- Fixed the package uninstall confirmation counting your whole shared model library for packages using symlink sharing. Only the package's own files are counted; shared models were never deleted
- Fixed logs ignoring `--home-dir`, and opening a missing folder or file showing an error dialog instead of a short notice ([#1766](https://github.com/LykosAI/StabilityMatrix/pull/1766)) - thanks to @e-nord!
- Fixed shared model folder links hiding **DiffusionModels** in the Checkpoint Manager, Image Lab and Inference, including when DiffusionModels is itself a link ([#1743](https://github.com/LykosAI/StabilityMatrix/pull/1743), [#1763](https://github.com/LykosAI/StabilityMatrix/pull/1763)) - thanks to @NeuralFault and @ionite34!
- Fixed the Model Browser's **Base Model** filter being lost when using **Next Model** / **Previous Model** or refreshing a model's details ([#1436](https://github.com/LykosAI/StabilityMatrix/issues/1436))
- Fixed **Open on CivArchive** opening a file URL instead of the model page on Linux ([#1765](https://github.com/LykosAI/StabilityMatrix/issues/1765))
- Fixed emoji showing as `��` in Image Lab messages and in CivitAI and CivArchive model descriptions
- Fixed release-note text being cut off beside the scrollbar in the update dialog ([#1757](https://github.com/LykosAI/StabilityMatrix/issues/1757))
- Fixed text in **Image Lab** messages and thinking blocks being cut off at the right edge. Message bubbles now fit their text, and inline code is easier to read
- Fixed **Save & Regenerate** in Image Lab deleting later images and dropping the edited message's attachments. It now shows progress and can be cancelled
- Fixed **Regenerate** in Image Lab removing the old response before the new one was ready. A failed or cancelled regenerate now keeps it
- Fixed Image Lab pulling you back to a conversation when its generation finished while you were in another, and showing that generation's error in the one you had open
- Fixed deleting one image from a multi-image message in Image Lab deleting your original file when Image Lab hadn't kept its own copy
- Fixed your message in Image Lab not being editable or deletable after a failed or cancelled generation
- Fixed cancelling a Gemini generation in Image Lab showing "Generation failed" instead of "Cancelled"
- Fixed Image Lab not scrolling to a new message when you were already at the bottom of the conversation
- Fixed a ComfyUI generation in Image Lab not stopping when ComfyUI disconnected, if you had opened a Gemini conversation in the meantime
- Fixed package version lists and update checks showing an old release as the newest, because startup update checks ran into GitHub's rate limit
### Supporters
#### 🌟 Visionaries
This patch is a good one for AMD owners: Forge Neo now runs on ROCm on both Windows and Linux, and Windows ROCm installs always get a `torch` and `torchvision` that actually match. It also adds Fizgig, a whole LoRA training studio, to the package list. Most of it arrived as community contributions, and our Visionaries are the reason we have time to review that work properly and say yes to it. Thank you **Waterclouds**, **MrMxyzptlk12836**, **bluepopsicle**, **dispenser**, **Droolguy**, **TwistedDragon**, **snotty**, **cusalapapen1481**, **moon_milky2843**, **sn3232323233350**, **CC**, **hasezou11013179**, and **Akros**. It's great to see **SnooSnooEternal** back with us and **jweg79** moving up from the Pioneers, and hello to **japublicspot78364519** and **jonny90**, our newest Visionaries; we're so happy you're here. 💛
#### 🚀 Pioneers
To our Pioneers **SinthCore**, **Jisuren**, **Hurbie53**, **Cjloha**, **Alligator1907**, **CommissarGiygas16050**, **Zombop**, **Silerae**, **joshsciascia72**, **cy_hart691842**, and **okachako**: thank you for backing a patch full of fixes that make things work on more machines. And hi **acleveranagram**, our newest Pioneer; we're really glad to have you! 💛

## v2.16.4
### Added
- Added **Comfy Kitchen Attention** (`--use-ck-attention`) as a **Cross Attention Method** launch option for ComfyUI and ComfyUI-Zluda - thanks to @e-nord!
Expand Down
54 changes: 54 additions & 0 deletions StabilityMatrix.Avalonia/Controls/MarkdownHtmlLabel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using Avalonia;
using TheArtOfDev.HtmlRenderer.Avalonia;

namespace StabilityMatrix.Avalonia.Controls;

/// <summary>
/// The HTML label behind <see cref="MarkdownViewer"/>. It fills the width it is offered, or with
/// <see cref="FitToContent"/> takes only the width its text needs, wrapping at the width offered.
/// </summary>
public class MarkdownHtmlLabel : HtmlLabel
{
public static readonly StyledProperty<bool> FitToContentProperty = AvaloniaProperty.Register<
MarkdownHtmlLabel,
bool
>(nameof(FitToContent));

static MarkdownHtmlLabel()
{
AffectsMeasure<MarkdownHtmlLabel>(FitToContentProperty);
}

/// <summary>
/// Size the width to the text, up to the width offered, instead of filling it.
/// </summary>
public bool FitToContent
{
get => GetValue(FitToContentProperty);
set => SetValue(FitToContentProperty, value);
}

protected override Size MeasureOverride(Size constraint)
{
if (!FitToContent || double.IsPositiveInfinity(constraint.Width))
return base.MeasureOverride(constraint);

var horizontal = Padding.Left + Padding.Right + BorderThickness.Left + BorderThickness.Right;
var vertical = Padding.Top + Padding.Bottom + BorderThickness.Top + BorderThickness.Bottom;
var available = Math.Max(constraint.Width - horizontal, 0);

// Lay out at the text's natural width, and wrap at the offered width only if it is wider
_htmlContainer.MaxSize = default;
_htmlContainer.PerformLayout();
if (_htmlContainer.ActualSize.Width > available)
{
_htmlContainer.MaxSize = new Size(available, 0);
_htmlContainer.PerformLayout();
}

// Rounded up so layout rounding can't hand back less than the text was laid out in
var size = _htmlContainer.ActualSize;
return new Size(Math.Ceiling(size.Width) + horizontal, Math.Ceiling(size.Height) + vertical);
}
}
8 changes: 4 additions & 4 deletions StabilityMatrix.Avalonia/Controls/MarkdownViewer.axaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonia="clr-namespace:TheArtOfDev.HtmlRenderer.Avalonia;assembly=Avalonia.HtmlRenderer"
xmlns:controls="using:StabilityMatrix.Avalonia.Controls">
<Design.PreviewWith>
<controls:MarkdownViewer
Expand All @@ -15,10 +14,11 @@
<!-- Set Defaults -->
<Setter Property="Template">
<ControlTemplate>
<avalonia:HtmlPanel
x:Name="PART_HtmlPanel"
<controls:MarkdownHtmlLabel
x:Name="PART_HtmlLabel"
AutoSizeHeightOnly="True"
Background="Transparent"
Text="{TemplateBinding Html}" />
FitToContent="{TemplateBinding FitToContent}" />
</ControlTemplate>
</Setter>
</Style>
Expand Down
75 changes: 66 additions & 9 deletions StabilityMatrix.Avalonia/Controls/MarkdownViewer.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
using System.IO;
using System.Text;
using Avalonia;
using Avalonia.Controls.Primitives;
using Markdig;
using TheArtOfDev.HtmlRenderer.Avalonia;

namespace StabilityMatrix.Avalonia.Controls;

/// <summary>
/// Renders Markdown, or HTML set through the Html property, with the app's markdown stylesheet.
/// </summary>
public class MarkdownViewer : TemplatedControlBase
{
private static readonly MarkdownPipeline Pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.UseEmojiAndSmiley()
.Build();

private HtmlLabel? htmlLabel;

public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<
MarkdownViewer,
string
>(nameof(Text));

/// <summary>
/// Markdown to render.
/// </summary>
public string Text
{
get => GetValue(TextProperty);
set
{
SetValue(TextProperty, value);
ParseText(value);
}
set => SetValue(TextProperty, value);
}

public static readonly StyledProperty<string> HtmlProperty = AvaloniaProperty.Register<
Expand All @@ -34,45 +44,88 @@ private string Html
set => SetValue(HtmlProperty, value);
}

public static readonly StyledProperty<bool> FitToContentProperty =
MarkdownHtmlLabel.FitToContentProperty.AddOwner<MarkdownViewer>();

/// <summary>
/// Size the width to the text, up to the width offered, instead of filling it. For chat
/// bubbles; leave it off where the content should span its container.
/// </summary>
public bool FitToContent
{
get => GetValue(FitToContentProperty);
set => SetValue(FitToContentProperty, value);
}

public static readonly StyledProperty<string> CustomCssProperty = AvaloniaProperty.Register<
MarkdownViewer,
string
>(nameof(CustomCss));

/// <summary>
/// CSS appended to the markdown stylesheet when the template is applied.
/// </summary>
public string CustomCss
{
get => GetValue(CustomCssProperty);
set => SetValue(CustomCssProperty, value);
}

/// <summary>
/// Encodes every character outside the Basic Multilingual Plane (emoji, mostly) as a numeric
/// character reference. HtmlRenderer splits words at each UTF-16 code unit in U+4E00..U+FA2D,
/// a range that covers the surrogates, so a raw emoji is drawn as two U+FFFD halves; a
/// reference is decoded after that split and keeps the pair together.
/// </summary>
internal static string? EncodeAstralCharacters(string? html)
{
if (string.IsNullOrEmpty(html) || html.AsSpan().IndexOfAnyInRange('\uD800', '\uDFFF') < 0)
return html;

var builder = new StringBuilder(html.Length + 16);
foreach (var rune in html.EnumerateRunes())
{
if (rune.IsBmp)
builder.Append((char)rune.Value);
else
builder.Append("&#x").Append(rune.Value.ToString("X")).Append(';');
}

return builder.ToString();
}

private void ParseText(string value)
{
if (string.IsNullOrWhiteSpace(value))
return;

var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseEmojiAndSmiley().Build();
var html =
$"""<html><body class="markdown-body">{Markdig.Markdown.ToHtml(value, pipeline)}</body></html>""";
$"""<html><body class="markdown-body">{Markdig.Markdown.ToHtml(value, Pipeline)}</body></html>""";
Html = html;
}

protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);

if (e.NameScope.Find("PART_HtmlPanel") is not HtmlPanel htmlPanel)
htmlLabel = e.NameScope.Find("PART_HtmlLabel") as HtmlLabel;
if (htmlLabel is null)
return;

using var cssFile = Assets.MarkdownCss.Open();
using var reader = new StreamReader(cssFile);
var css = reader.ReadToEnd();

htmlPanel.BaseStylesheet = $"{css}\n{CustomCss}";
htmlLabel.BaseStylesheet = $"{css}\n{CustomCss}";

if (string.IsNullOrWhiteSpace(Html) && !string.IsNullOrWhiteSpace(Text))
{
ParseText(Text);
}
else
{
htmlLabel.Text = EncodeAstralCharacters(Html);
}
}

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
Expand All @@ -81,6 +134,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
{
ParseText(change.NewValue.ToString());
}
else if (change.Property == HtmlProperty && htmlLabel is not null)
{
htmlLabel.Text = EncodeAstralCharacters(change.NewValue as string);
}

base.OnPropertyChanged(change);
}
Expand Down
24 changes: 24 additions & 0 deletions StabilityMatrix.Avalonia/Controls/ReleaseNotesMarkdownViewer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.VisualTree;

namespace StabilityMatrix.Avalonia.Controls;

/// <summary>
/// Displays release notes with space between the document and the vertical scrollbar.
/// </summary>
public class ReleaseNotesMarkdownViewer : BetterMarkdownScrollViewer
{
public ReleaseNotesMarkdownViewer()
{
if (
this.GetVisualChildren().OfType<ScrollViewer>().FirstOrDefault() is
{ Content: Control content } viewer
)
{
viewer.Content = null;
viewer.Content = new Border { Child = content, Padding = new Thickness(0, 0, 18, 0) };
}
}
}
17 changes: 3 additions & 14 deletions StabilityMatrix.Avalonia/DialogHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -696,25 +696,14 @@ private static (int FileCount, long TotalBytes) GetFileStats(DirectoryInfo direc
var fileCount = 0;
var totalBytes = 0L;

foreach (var file in directory.EnumerateFiles())
foreach (
var file in directory.EnumerateFiles("*", EnumerationOptionConstants.AllDirectoriesExceptLinks)
)
{
if (file.Attributes.HasFlag(FileAttributes.ReparsePoint))
continue;

fileCount++;
totalBytes += file.Length;
}

foreach (var subDir in directory.EnumerateDirectories())
{
if (subDir.Attributes.HasFlag(FileAttributes.ReparsePoint))
continue;

var (subCount, subBytes) = GetFileStats(subDir);
fileCount += subCount;
totalBytes += subBytes;
}

return (fileCount, totalBytes);
}
}
Expand Down
2 changes: 1 addition & 1 deletion StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ public async Task InstallNodeIfNecessary(IProgress<ProgressReport>? progress = n
);

// unzip
await ArchiveHelper.Extract7ZAuto(nodeDownloadPath, AssetsDir);
await TarGZipExtractor.ExtractAsync(nodeDownloadPath, AssetsDir);

var nodeDir = Compat.IsMacOS
? AssetsDir.JoinDir("node-v20.19.3-darwin-arm64")
Expand Down
4 changes: 3 additions & 1 deletion StabilityMatrix.Avalonia/Models/BananaVision/Messages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ public ThinkingMessage(string thinkingContent)
/// Preview of the thinking content (first ~100 chars)
/// </summary>
public string ThinkingPreview =>
ThinkingContent.Length > 100 ? ThinkingContent[..100] + "..." : ThinkingContent;
ThinkingContent.Length > 100
? ThinkingContent[..(char.IsHighSurrogate(ThinkingContent[99]) ? 99 : 100)] + "..."
: ThinkingContent;

/// <summary>
/// Whether the thinking section is expanded
Expand Down
Loading
Loading