# 2024 年 10 月 1 日
接入实际的 llamacpp 根据配置的调用使用,重构了一波异步计算信息,将简单的快捷解释界面基本弄好了
# 2024 年 10 月 2 日
今天主要是优化了各个界面排版,特别是聊天界面整体都重构了下。
还有之前感觉在 UI 涉及数据更新的时候卡顿问题,最后加了更新延迟:特别是 LLM 流式传输,打字机效果一样显示的情况,调整了 UI 更新频率感觉好些了。
# 2024 年 10 月 3 日
搞了一天加 Markdown 渲染效果,开始以为很简单,头一天晚上都想好了:
- 先用 Markdig 配合 Markdown.ColorCode.CSharpToColoredHtml 渲染成 HTML
- 然后使用 Avalonia.HtmlRenderer 渲染显示
怎么样?想着是不是很简单呢?
结果... 先是发现显示很有问题:Avalonia.HtmlRenderer 显示起来,感觉很怪,跟现在的主题完全对不上号。其次渲染感觉也有问题。其次流式更新性能也很糟糕....
然后中途就查另外一个 Avalonia.Markdown,换成这个也有不少坑,比如代码高亮都没有,最后到处查,查 issue,结果最后在它自己的 wiki... 最后一篇才写着:代码用的 TextEditor,得 Fluent 主题才支持!
于是又研究来研究去的... 最后得出结论:
- 可以给 MarkdownScrollViewer 指定 FluentTheme,然后内联指定 SimpleTheme 配合 avares://AvaloniaEdit/Themes/Simple/AvaloniaEdit.xaml 勉强正常显示
- 之所以说勉强,是因为有些显示不对:文字选择、代码高亮都没有了
- 代码高亮可以强行指定语言,比如 C#,不过其它的,纯文字看着还行,手动选择一下就不对劲了,而且有时候生成的代码是纯粹的 TextBlock 模式,选也没法选
设置代码如下:
<mdxaml:MarkdownScrollViewer | |
Name="MarkdownScrollViewer" | |
MarkdownStyleName="FluentTheme" | |
SelectionEnabled="True"> | |
<mdxaml:MarkdownScrollViewer.Plugins> | |
<mdxaml:MdAvPlugins> | |
<avalonia:ChatAISetup /> | |
</mdxaml:MdAvPlugins> | |
</mdxaml:MarkdownScrollViewer.Plugins> | |
<mdxaml:MarkdownScrollViewer.Styles> | |
<!-- <FluentTheme /> --> | |
<!-- <StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" /> --> | |
<SimpleTheme /> | |
<StyleInclude Source="avares://AvaloniaEdit/Themes/Simple/AvaloniaEdit.xaml" /> | |
<Style Selector="Button.CopyButton TextBlock"> | |
<Setter Property="Text" Value="📋" /> | |
</Style> | |
<Style Selector=".CodeBlock avedit|TextEditor"> | |
<Setter Property="ShowLineNumbers" Value="True" /> | |
<!-- 强行写死 C#,因为目前 MarkdownScrollViewer 有问题,使用 FluentTheme 代码高亮是有了,但是内容会丢失,而且闪烁 --> | |
<Setter Property="SyntaxHighlighting" Value="C#" /> | |
</Style> | |
</mdxaml:MarkdownScrollViewer.Styles> | |
</mdxaml:MarkdownScrollViewer> |
太晚了,最后无奈先用着这一版,后面再看吧。
# 2024 年 10 月 4 日
昨天晚上也是,睡不着觉,今天早上 5 点多就醒了,翻来覆去到 8 点,然后开始搞。
本来计划把突然想到的剪切板历史记录给搞出来,而且以为会很快 —— 左右估计不过几个小时,结果调各种效果啥的,愣是又花了一天,今天已经时 4 号了,国庆竟然连 B 站都基本还没打开看过什么....
选择快八点了,刚炒了菜准备吃饭,于是想先记录一下。
最后效果这样:
虽然也搞了些其它东西,比如又重构了下 SK 发送给 LLM 的方式。从下午开始搞的,但是搞这么久也真的是....
不行啊,国庆都连肝 4 天了,这可是一年才有一次的大假期!!!今天还计划把角色、工具人的设置及界面搞了,结果也没时间弄、还有优化流式显示,想着先纯文本流式打字机显示,完了再统一渲染成 Markdown,也是没时间搞了.... 我不想国庆都费再这.... 之前说的 UE5 教程也还没看...
# 2024 年 10 月 5 日
昨天晚上就发现 Avalonia.Markdown 渲染又出问题了,今天早上试了下,存在它的页面切换都要挂。
最后调到中午,发现还是绑出来的问题:
- 我创建了一个 SimpleMarkdownViewer 封装这一系列设置,SimpleMarkdownViewer 本身挂了一些绑定属性:
public static readonly StyledProperty<string> MarkdownTextProperty = | |
AvaloniaProperty.Register<SimpleMarkdownViewer, string>(nameof(MarkdownText)); | |
public static readonly StyledProperty<bool> IsPlaintextProperty = | |
AvaloniaProperty.Register<SimpleMarkdownViewer, bool>(nameof(IsPlaintext)); | |
public string MarkdownText | |
{ | |
get => GetValue(MarkdownTextProperty); | |
set | |
{ | |
// Log.Debug(_stopwatch.ElapsedMilliseconds + " Setting Markdown to: "); | |
SetValue(MarkdownTextProperty, value); | |
// _stopwatch.Restart(); | |
} | |
} | |
public string HtmlText | |
{ | |
get => MarkdownUtils.ToHtml(MarkdownText); | |
} | |
public bool IsPlaintext | |
{ | |
get => GetValue(IsPlaintextProperty); | |
set => SetValue(IsPlaintextProperty, value); | |
} |
在其它地方使用时,会直接绑定这些属性,然后在它的页面中,本身的 MarkdownScrollViewer 也会绑定它:
<!-- Avalonia.Markdown 渲染显示 --> | |
<avalonia:MarkdownScrollViewer | |
IsVisible="{Binding !IsPlaintext, RelativeSource={RelativeSource AncestorType={x:Type common:SimpleMarkdownViewer}}}" | |
SelectionEnabled="True" | |
Markdown="{Binding MarkdownText, RelativeSource={RelativeSource AncestorType={x:Type common:SimpleMarkdownViewer}}}"> | |
<avalonia:MarkdownScrollViewer.Plugins> | |
<avalonia:MdAvPlugins> | |
<avalonia:ChatAISetup /> | |
</avalonia:MdAvPlugins> | |
</avalonia:MarkdownScrollViewer.Plugins> | |
<avalonia:MarkdownScrollViewer.Styles> | |
<FluentTheme /> | |
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" /> | |
<!-- <SimpleTheme /> --> | |
<!-- <StyleInclude Source="avares://AvaloniaEdit/Themes/Simple/AvaloniaEdit.xaml" /> --> | |
<Style Selector="Button.CopyButton TextBlock"> | |
<Setter Property="Text" Value="📋" /> | |
</Style> | |
<Style Selector=".CodeBlock avedit|TextEditor"> | |
<Setter Property="ShowLineNumbers" Value="True" /> | |
<!-- 强行写死 C#,因为目前 MarkdownScrollViewer 有问题,使用 FluentTheme 代码高亮是有了,但是内容会丢失,而且闪烁 --> | |
<!-- <Setter Property="SyntaxHighlighting" Value="C#" /> --> | |
</Style> | |
</avalonia:MarkdownScrollViewer.Styles> | |
</avalonia:MarkdownScrollViewer> |
结果不知道是不是这样中转导致渲染引擎出问题了,反正就是不报错,单纯就是切换页面的时候,整个页面卸载不掉,然后崩溃。
果然还是太好高骛远了,想着用方便的高级特性,这下栽跟头了.... 白费时间,还不如用最常规、简单的方法:代码中转复制,什么事都没了。毕竟通常事情都是有代价的。
自定义控件定义的组件,其绑定属性跟随的是 DataContext 。
另外,自定义的绑定数据,Set 方法是不会被调用的!!!
public static readonly StyledProperty<bool?> IsPlaintextProperty = | |
AvaloniaProperty.Register<SimpleMarkdownViewer, bool?>(nameof(IsPlaintext), | |
defaultBindingMode: BindingMode.TwoWay); | |
public bool? IsPlaintext | |
{ | |
get => GetValue(IsPlaintextProperty); | |
set | |
{ | |
SetValue(IsPlaintextProperty, value); | |
MarkdownScrollViewer.IsVisible = !IsPlaintext ?? false; | |
BlockTextScrollViewer.IsVisible = IsPlaintext ?? true; | |
} | |
} |
如果想要在 Set 的时候做点什么,必须通过重载来额外判断:
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change) | |
{ | |
base.OnPropertyChanged(change); | |
if (change.Property == IsPlaintextProperty) | |
{ | |
// 在这里处理 IsPlaintext 属性的变化 | |
var newValue = change.NewValue.GetValueOrDefault<bool>(); | |
MarkdownScrollViewer.IsVisible = !newValue; | |
BlockTextScrollViewer.IsVisible = newValue; | |
} | |
} |
鉴于上面使用 Avalonia.Markdown 的一些问题 (最主要还是配合 Fluent 主题渲染直接报错、配合 Simple 主题也显示难看),而且 Demo 源码也跑不了。
所以折腾半晌,就又把 Avalonia.HtmlRenderer 拿来试了下,效果有,但是不大好,感觉画风都不是一套的了.... 而且真的卡,放几个对话记录就感觉有点吃不消了 —— 也许可以考虑统一封装到 SimpleMarkdownViewer,由设置项控制用哪个渲染。
搞 Avalonia.HtmlRenderer 又折腾到晚上了... 这东西默认会给文本上下加上 20 像素的空白长度..... 字体也不对劲.... 另外排版上最后觉得还是做分页吧,现在这种 Scroll 套 Scroll 进度条变来变去,体验也不好:
今天最后决定还是用 Avalonia.HtmlRenderer 了,因为跟着追源码,一边追一边调,已经发现正确修改这些问题 (包括字体、边距、自适应宽、高) 的方法了。
- 字体:可以生效,只是里面通过系统文字名字映射的,得完全一致,很不稳定对不上就花了,所以决定自定义字体,全都用一套。
- 边距:可以考虑 Markdown 渲染成 Html 的时候在就加上 markgin 上下 20px,当然,仅限于第一个
标签和最后一个。
- 自适应宽高:重载容器 (SimpleMarkdownViewer) MeasureOverride 方法,对 HtmlLabel 做 AutoSize 下的 MaxWidth 限制。
- 文字色根据主题切换:渲染 Html 时加上对于主题颜色,OnThemeChanged 事件中重新渲染 Html。
至于 Avalonia.Markdown,代码高亮强依赖 AvaloniaEdit,AvaloniaEdit 强依赖 FluntTheme,而 Avalonia.Markdown 不能使用 FluntTheme,渲染要直接报错.... 只能用 Avalonia.Markdown.FluntTheme 配合 SimpleTheme 主题才能正常渲染,但是代码高亮啥的都炸了,连文字选中表现都不对劲, 一环套一环。
而且源码 Demo 拉下来一堆报错,跑不了。目前就是是之前折腾了一天多的结论,看看维护时间好像也已经没维护了,上面说的渲染报错问题,git issue 有人 push commit 好像也没看到反应,只能先放弃。
结果一通折腾下来,又是 11 点多,国庆还是太肝了... 明天先把字体问题解决了,然后计划:
- 去掉聊天页面的动态虚拟化 (体验下来,对于存在不固定 Item 的情况下,简直是负优化)
- 把分页做了
- 加点工具设置的东西、优化下设置排版
- 优化下日志排版,现在也是虚拟化, Scroll 体验不对劲,要么改成 Item 固定高度,要么用内联文字?
# 2024 年 10 月 6 日
早上起床就开始找开源字体,准备将应用字体与 Avalonia.HtmlRenderer 保持一致。
- 注:看代码 HtmlRenderer 只支持一个字体,因此在 html style 定义多个字体的情况不行,找到第一个字体存在就直接用了,不会检测文字是否适合
字体的坑:
- Avalonia 的字体加载,是依靠字体内部的 FamilyName,一般都看不到....
- 所以文件路径是不行的,注意连空格都得有,直接打开字体看到的中文名字是不行的!
Avalonia.HtmlRenderer 好像也不行啊,中英文混排巨坑:
搞半天发现这个又坏了... 还是是因为我自己编译导致的?因为现在 nuget 上的版本很旧,自定义字体貌似都不支持,所以才拉下来自己编译了个....
测试了下还真是... 看来最新版本还没发布也是有一定原因的... 看记录今年都有不少修改,但是 nuget 已经是去年的版本了。
反编译查了下 nuget 版本,找到添加字体的入口了,在 HtmlRender 里面,直接是静态方法了,之前还以为可能得反射去查了呢 —— 这不是比改版后的方便么?AvaloniaAdapter nuget 版本是个单例,而且改版后还变成成员了。这样不是还得看啥时候添加、或者每次创建要给都得单独加一次.... 虽然是字典没关系。
反正既然老版本有设置字体的地方,那就直接换回 nuget 版本了,直接在 SimpleMarkdownViewer 静态构造函数里面初始化一次。
我就说今天又忙活了一天,之前好像也没感觉,怎么排版就挂了?差点吓惨了,因为立刻在官方 Demo 上试了下中英文混排也是乱的,都在想是不是要把 Avalonia.Markdown 也加进来,然后整个设置可选项了....
换回 nuget 版本果然没渲染问题,字体看着也 OK:
# 2024 年 10 月 7 日
添加 OpenAI 模式 BPE 分词器计算。 解决聊天 UI 抖动问题,见 使用Avalonia的踩坑记录:ItemsControl,晚上还单独写了篇这个研究记录,导致本篇日志也没时间弄了。
# 总结
记录有点乱,像流水账... 之所以没当天就写完是因为本来想整理下的,但是如今过了两周发现又不知道写啥好,就先这样吧。
毕竟考虑到确实折腾了这么久,不记录一下就没有意义了:没想到国庆肝了七天,抠细节想搞好的话,什么东西都难搞... 本来想国庆研究 UE5 的,结果全搞这个 Avalonia 和 AI 去了。后面上班了甚至感觉好像连续一个月都在上班一样,有点顶不住,接下来准备先缓一缓了。