This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
正在为您的博客“ -” 部分 {5:} 为您建立 & " @ lawyer GPT"}窗口客户端
Wednesday, 12 November 2025
一. 导言导言 导言 导言 导言
欢迎使用 {5!} we'已经建造了整个输入管道部分#4@),理解嵌入和矢量搜索@(部分#3并准备好GPU G(部分#2@._ 现在该是时候建立实际的写作助理界面@-}( Windows client) 窗口客户端, 您将用 AI-power compublications_ .}撰写博客文章
这是我自己编辑的A )Q MS K3Q 同一声音 同样的实用性 ,Q 一样
想想 GitHub 副驾驶员 或新的AI VS 代码 @- % 但不代表博客写作@. @% 您正在输入#,}这个系统从语义上搜索您过去的文章,
选择UI框架
在 C#: 中,我们有三个主要选项用于建设现代 Windows 桌面应用程序
框架比较
| @ | @ 地貌 @ | @ WPF @ MPK2 @ Avalonia @ I | @ MaUI@ | # |
|---|---|---|---|---|
| 平台平台平台 | +Windows 仅@ | +Cross @-#platform _ | +Cround_-+PlatForm # | # |
| 到期期 -=YTET -伊甸园字幕组=- 翻译: | ||||
| XAML 支持 {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}♪ | }完整的 | {\fnarial black\fs12\bord0\shad0\4aH00\shad1\3cHFF8000\b0}♪ | * 不同的口味 ♪ | * |
| 业绩表现 实绩 -=YTET -伊甸园字幕组=- 翻译: | ||||
| UI 图书馆 | 很多( mastrial deign,现代WPF}) MS K4 成长中{ | 有限公司 | ||
| 学习曲线 {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}你懂WPF吗? | ||||
| 生态系统 -=YTET -伊甸园字幕组=- 翻译: |
我的选择 : 瓦隆
为什么?
- Cross-platform (can 运行在 LinuxMS K2Mac 上,如果需要的话 @)
- 现代,积极开发
- WPPF WPF和XAML相匹配的 XAML ( 学习)
- 良好业绩
- 增长中的生态系统
但是,这些概念也适用于WPF 也“- I'll注意 WPF的替代品,因为它们不同” @.
应用程序架构
graph TB
A[MainWindow] --> B[Editor Panel]
A --> C[Suggestions Panel]
A --> D[Search Panel]
B --> E[AvalonEdit Component]
B --> F[Markdown Preview]
C --> G[Similar Posts List]
C --> H[Code Snippets]
C --> I[Suggested Links]
D --> J[Semantic Search]
D --> K[Keyword Filter]
L[Services Layer] --> M[EmbeddingService]
L --> N[VectorSearchService]
L --> O[LLMService]
B --> L
C --> L
D --> L
class A mainWindow
class B,C ui
class L services
classDef mainWindow stroke:#333,stroke-width:4px
classDef ui stroke:#333,stroke-width:2px
classDef services stroke:#333,stroke-width:2px
关键构成部分:
- 主窗口 - Shell 菜单@,状态栏
- 编辑面板 -}您寫入(}標記編輯{)}的地方
- 建议小组 以建议为动力,
- 搜索面板 -语义搜索界面
- 服务层 -后端集成
项目设置
# Create Avalonia MVVM application
dotnet new install Avalonia.Templates
dotnet new avalonia.mvvm -n Mostlylucid.BlogLLM.Client
cd Mostlylucid.BlogLLM.Client
# Add necessary packages (latest versions)
dotnet add package AvaloniaEdit
dotnet add package Markdown.Avalonia
dotnet add package CommunityToolkit.Mvvm
# Add references to our core library
dotnet add reference ../Mostlylucid.BlogLLM.Core
主窗口布局
XAML 结构
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Mostlylucid.BlogLLM.Client.ViewModels"
xmlns:views="using:Mostlylucid.BlogLLM.Client.Views"
x:Class="Mostlylucid.BlogLLM.Client.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Title="Blog Writing Assistant"
Width="1400" Height="900">
<Design.DataContext>
<vm:MainWindowViewModel />
</Design.DataContext>
<Grid RowDefinitions="Auto,*,Auto">
<!-- Menu Bar -->
<Menu Grid.Row="0">
<MenuItem Header="_File">
<MenuItem Header="_New Post" Command="{Binding NewPostCommand}" />
<MenuItem Header="_Open Post" Command="{Binding OpenPostCommand}" />
<MenuItem Header="_Save" Command="{Binding SaveCommand}" />
<Separator />
<MenuItem Header="E_xit" Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Header="_Undo" Command="{Binding UndoCommand}" />
<MenuItem Header="_Redo" Command="{Binding RedoCommand}" />
</MenuItem>
<MenuItem Header="_AI">
<MenuItem Header="_Generate Suggestions" Command="{Binding GenerateSuggestionsCommand}" />
<MenuItem Header="_Semantic Search" Command="{Binding OpenSearchCommand}" />
<MenuItem Header="_Insert Link" Command="{Binding InsertLinkCommand}" />
</MenuItem>
</Menu>
<!-- Main Content - Split View -->
<Grid Grid.Row="1" ColumnDefinitions="2*,*">
<!-- Left: Editor -->
<Border Grid.Column="0" BorderBrush="LightGray" BorderThickness="0,0,1,0">
<views:EditorView DataContext="{Binding EditorViewModel}" />
</Border>
<!-- Right: Suggestions -->
<Border Grid.Column="1">
<views:SuggestionsView DataContext="{Binding SuggestionsViewModel}" />
</Border>
</Grid>
<!-- Status Bar -->
<Border Grid.Row="2" Background="WhiteSmoke" Padding="8,4">
<Grid ColumnDefinitions="*,Auto,Auto,Auto">
<TextBlock Grid.Column="0" Text="{Binding StatusMessage}" />
<TextBlock Grid.Column="1" Text="{Binding WordCount, StringFormat='Words: {0}'}" Margin="10,0" />
<TextBlock Grid.Column="2" Text="{Binding CharCount, StringFormat='Chars: {0}'}" Margin="10,0" />
<ProgressBar Grid.Column="3"
Width="100"
Height="16"
IsVisible="{Binding IsProcessing}"
IsIndeterminate="True" />
</Grid>
</Border>
</Grid>
</Window>
主窗口视图模式
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Mostlylucid.BlogLLM.Client.Services;
using System.Threading.Tasks;
namespace Mostlylucid.BlogLLM.Client.ViewModels
{
public partial class MainWindowViewModel : ViewModelBase
{
private readonly IEditorService _editorService;
private readonly ISuggestionService _suggestionService;
[ObservableProperty]
private EditorViewModel _editorViewModel;
[ObservableProperty]
private SuggestionsViewModel _suggestionsViewModel;
[ObservableProperty]
private string _statusMessage = "Ready";
[ObservableProperty]
private int _wordCount;
[ObservableProperty]
private int _charCount;
[ObservableProperty]
private bool _isProcessing;
public MainWindowViewModel(
IEditorService editorService,
ISuggestionService suggestionService)
{
_editorService = editorService;
_suggestionService = suggestionService;
EditorViewModel = new EditorViewModel(editorService);
SuggestionsViewModel = new SuggestionsViewModel(suggestionService);
// Subscribe to editor changes
EditorViewModel.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(EditorViewModel.Text))
{
UpdateStatistics();
_ = GenerateSuggestionsAsync();
}
};
}
[RelayCommand]
private async Task NewPost()
{
EditorViewModel.Text = GenerateNewPostTemplate();
StatusMessage = "New post created";
}
[RelayCommand]
private async Task OpenPost()
{
var dialog = new OpenFileDialog
{
Filters = new List<FileDialogFilter>
{
new FileDialogFilter { Name = "Markdown", Extensions = { "md" } }
}
};
var result = await dialog.ShowAsync(GetMainWindow());
if (result != null && result.Length > 0)
{
EditorViewModel.Text = await File.ReadAllTextAsync(result[0]);
StatusMessage = $"Opened: {Path.GetFileName(result[0])}";
}
}
[RelayCommand]
private async Task Save()
{
var dialog = new SaveFileDialog
{
Filters = new List<FileDialogFilter>
{
new FileDialogFilter { Name = "Markdown", Extensions = { "md" } }
},
DefaultExtension = "md"
};
var result = await dialog.ShowAsync(GetMainWindow());
if (!string.IsNullOrEmpty(result))
{
await File.WriteAllTextAsync(result, EditorViewModel.Text);
StatusMessage = $"Saved: {Path.GetFileName(result)}";
}
}
[RelayCommand]
private async Task GenerateSuggestions()
{
IsProcessing = true;
StatusMessage = "Generating suggestions...";
try
{
var currentText = EditorViewModel.Text;
await SuggestionsViewModel.GenerateSuggestionsAsync(currentText);
StatusMessage = "Suggestions generated";
}
catch (Exception ex)
{
StatusMessage = $"Error: {ex.Message}";
}
finally
{
IsProcessing = false;
}
}
private void UpdateStatistics()
{
var text = EditorViewModel.Text ?? string.Empty;
CharCount = text.Length;
WordCount = text.Split(new[] { ' ', '\n', '\r', '\t' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
private string GenerateNewPostTemplate()
{
var today = DateTime.Now.ToString("yyyy-MM-ddTHH:mm");
return $@"# New Blog Post
<datetime class=""hidden"">{today}</datetime>
## Introduction
Write your introduction here...
[TOC]
## Section 1
Content here...
";
}
private Window GetMainWindow() =>
(Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)
?.MainWindow ?? throw new InvalidOperationException();
}
}
编辑器组件
We' 使用 Avalon 编辑器 - 功能强大的文本编辑器组件, 有语法加亮@.
编辑器 View@. @axaml
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
x:Class="Mostlylucid.BlogLLM.Client.Views.EditorView">
<Grid RowDefinitions="Auto,*,*">
<!-- Toolbar -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="5" Margin="5">
<Button Content="Bold" Command="{Binding InsertBoldCommand}" />
<Button Content="Italic" Command="{Binding InsertItalicCommand}" />
<Button Content="Code" Command="{Binding InsertCodeCommand}" />
<Separator />
<Button Content="H1" Command="{Binding InsertHeadingCommand}" CommandParameter="1" />
<Button Content="H2" Command="{Binding InsertHeadingCommand}" CommandParameter="2" />
<Button Content="H3" Command="{Binding InsertHeadingCommand}" CommandParameter="3" />
<Separator />
<Button Content="Link" Command="{Binding InsertLinkCommand}" />
<Button Content="Image" Command="{Binding InsertImageCommand}" />
</StackPanel>
<!-- Editor -->
<avalonEdit:TextEditor Grid.Row="1"
Name="Editor"
FontFamily="Consolas,Courier New"
FontSize="14"
ShowLineNumbers="True"
WordWrap="True"
Document="{Binding Document}"
SyntaxHighlighting="MarkDown" />
<!-- Live Preview -->
<Border Grid.Row="2" BorderBrush="LightGray" BorderThickness="0,1,0,0">
<ScrollViewer>
<MarkdownScrollViewer Markdown="{Binding Text}"
Margin="10"
Background="White" />
</ScrollViewer>
</Border>
</Grid>
</UserControl>
编辑器 View 模式
using AvaloniaEdit.Document;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Mostlylucid.BlogLLM.Client.ViewModels
{
public partial class EditorViewModel : ViewModelBase
{
private readonly IEditorService _editorService;
[ObservableProperty]
private TextDocument _document = new();
[ObservableProperty]
private string _text = string.Empty;
[ObservableProperty]
private int _caretOffset;
public EditorViewModel(IEditorService editorService)
{
_editorService = editorService;
// Sync Document and Text
Document.TextChanged += (s, e) =>
{
Text = Document.Text;
};
}
[RelayCommand]
private void InsertBold()
{
InsertMarkdownWrapper("**", "**", "bold text");
}
[RelayCommand]
private void InsertItalic()
{
InsertMarkdownWrapper("*", "*", "italic text");
}
[RelayCommand]
private void InsertCode()
{
InsertMarkdownWrapper("`", "`", "code");
}
[RelayCommand]
private void InsertHeading(string level)
{
var headingMarker = new string('#', int.Parse(level));
Document.Insert(CaretOffset, $"{headingMarker} Heading {level}\n");
}
[RelayCommand]
private void InsertLink()
{
InsertMarkdownWrapper("[", "](url)", "link text");
}
[RelayCommand]
private void InsertImage()
{
Document.Insert(CaretOffset, "");
}
private void InsertMarkdownWrapper(string before, string after, string placeholder)
{
var selectedText = GetSelectedText();
if (string.IsNullOrEmpty(selectedText))
{
Document.Insert(CaretOffset, $"{before}{placeholder}{after}");
}
else
{
var selectionStart = Document.GetOffset(Document.GetLocation(CaretOffset));
Document.Replace(selectionStart, selectedText.Length, $"{before}{selectedText}{after}");
}
}
private string GetSelectedText()
{
// This would get actual selection from AvalonEdit
// Simplified for example
return string.Empty;
}
}
}
建议小组
这是AI魔法发生的地方, 显示语义上相似的文章与建议@.
建议浏览@. @axaml
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Mostlylucid.BlogLLM.Client.Views.SuggestionsView">
<TabControl>
<!-- Similar Posts -->
<TabItem Header="Similar Posts">
<Grid RowDefinitions="Auto,*">
<StackPanel Grid.Row="0" Margin="5">
<TextBlock Text="Related content from your blog:" FontWeight="Bold" />
<TextBlock Text="{Binding SimilarPostsCount, StringFormat='{0} posts found'}"
FontSize="11" Foreground="Gray" />
</StackPanel>
<ListBox Grid.Row="1"
ItemsSource="{Binding SimilarPosts}"
SelectedItem="{Binding SelectedPost}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray"
BorderThickness="1"
Padding="8"
Margin="4"
CornerRadius="4">
<StackPanel>
<TextBlock Text="{Binding Title}"
FontWeight="Bold"
TextWrapping="Wrap" />
<TextBlock Text="{Binding SectionHeading}"
FontSize="11"
Foreground="DarkBlue"
Margin="0,2" />
<TextBlock Text="{Binding Preview}"
TextWrapping="Wrap"
MaxHeight="60"
FontSize="12"
Margin="0,4" />
<StackPanel Orientation="Horizontal" Spacing="10" Margin="0,4,0,0">
<TextBlock Text="{Binding Score, StringFormat='Similarity: {0:P0}'}"
FontSize="11"
Foreground="Green" />
<Button Content="Insert Link"
Command="{Binding $parent[ListBox].DataContext.InsertLinkCommand}"
CommandParameter="{Binding}"
FontSize="11" />
<Button Content="View"
Command="{Binding $parent[ListBox].DataContext.ViewPostCommand}"
CommandParameter="{Binding}"
FontSize="11" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</TabItem>
<!-- Code Snippets -->
<TabItem Header="Code Snippets">
<Grid RowDefinitions="Auto,*">
<TextBlock Grid.Row="0"
Text="Relevant code from past posts:"
FontWeight="Bold"
Margin="5" />
<ListBox Grid.Row="1" ItemsSource="{Binding CodeSnippets}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray"
BorderThickness="1"
Padding="8"
Margin="4"
Background="WhiteSmoke">
<StackPanel>
<TextBlock Text="{Binding Language}"
FontFamily="Consolas"
FontSize="11"
Foreground="DarkGray" />
<TextBlock Text="{Binding Code}"
FontFamily="Consolas"
TextWrapping="Wrap"
Margin="0,4" />
<Button Content="Insert"
Command="{Binding $parent[ListBox].DataContext.InsertCodeCommand}"
CommandParameter="{Binding}"
HorizontalAlignment="Right" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</TabItem>
<!-- AI Suggestions -->
<TabItem Header="AI Suggestions">
<Grid RowDefinitions="Auto,*,Auto">
<TextBlock Grid.Row="0"
Text="AI-generated suggestions:"
FontWeight="Bold"
Margin="5" />
<ScrollViewer Grid.Row="1">
<TextBlock Text="{Binding AiSuggestion}"
TextWrapping="Wrap"
Margin="10"
FontSize="13" />
</ScrollViewer>
<Button Grid.Row="2"
Content="Generate New Suggestion"
Command="{Binding RegenerateSuggestionCommand}"
Margin="5" />
</Grid>
</TabItem>
</TabControl>
</UserControl>
建议查看模式
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Mostlylucid.BlogLLM.Client.Models;
using Mostlylucid.BlogLLM.Client.Services;
using System.Collections.ObjectModel;
namespace Mostlylucid.BlogLLM.Client.ViewModels
{
public partial class SuggestionsViewModel : ViewModelBase
{
private readonly ISuggestionService _suggestionService;
[ObservableProperty]
private ObservableCollection<SimilarPost> _similarPosts = new();
[ObservableProperty]
private ObservableCollection<CodeSnippet> _codeSnippets = new();
[ObservableProperty]
private string _aiSuggestion = string.Empty;
[ObservableProperty]
private SimilarPost? _selectedPost;
[ObservableProperty]
private int _similarPostsCount;
public SuggestionsViewModel(ISuggestionService suggestionService)
{
_suggestionService = suggestionService;
}
public async Task GenerateSuggestionsAsync(string currentText)
{
// Extract last few sentences as context
var context = ExtractContext(currentText);
// Search for similar posts
var similarPosts = await _suggestionService.FindSimilarPostsAsync(context);
SimilarPosts.Clear();
foreach (var post in similarPosts)
{
SimilarPosts.Add(post);
}
SimilarPostsCount = SimilarPosts.Count;
// Extract code snippets from similar posts
var codeSnippets = await _suggestionService.ExtractCodeSnippetsAsync(similarPosts);
CodeSnippets.Clear();
foreach (var snippet in codeSnippets)
{
CodeSnippets.Add(snippet);
}
// Generate AI suggestion (we'll implement this in Part 6)
// AiSuggestion = await _suggestionService.GenerateAiSuggestionAsync(currentText, similarPosts);
}
[RelayCommand]
private void InsertLink(SimilarPost post)
{
var link = $"[{post.Title}](/blog/{post.Slug}#{post.SectionHeading.ToLower().Replace(" ", "-")})";
// Notify EditorViewModel to insert link
WeakReferenceMessenger.Default.Send(new InsertTextMessage(link));
}
[RelayCommand]
private void ViewPost(SimilarPost post)
{
// Open in browser
var url = $"https://www.mostlylucid.net/blog/{post.Slug}";
Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true });
}
[RelayCommand]
private void InsertCode(CodeSnippet snippet)
{
var code = $"```{snippet.Language}\n{snippet.Code}\n```";
WeakReferenceMessenger.Default.Send(new InsertTextMessage(code));
}
[RelayCommand]
private async Task RegenerateSuggestion()
{
// To be implemented in Part 6 with LLM integration
AiSuggestion = "AI suggestions will be available in Part 6...";
}
private string ExtractContext(string text, int sentences = 3)
{
// Get last N sentences as context for search
var sentenceEndings = new[] { '.', '!', '?' };
var sentences_found = 0;
var index = text.Length - 1;
while (index >= 0 && sentences_found < sentences)
{
if (sentenceEndings.Contains(text[index]))
{
sentences_found++;
}
index--;
}
return index < 0 ? text : text.Substring(index + 1).Trim();
}
}
}
服务层
推荐服务
using Mostlylucid.BlogLLM.Client.Models;
namespace Mostlylucid.BlogLLM.Client.Services
{
public interface ISuggestionService
{
Task<List<SimilarPost>> FindSimilarPostsAsync(string context);
Task<List<CodeSnippet>> ExtractCodeSnippetsAsync(List<SimilarPost> posts);
Task<string> GenerateAiSuggestionAsync(string currentText, List<SimilarPost> context);
}
}
落实建议服务
using Mostlylucid.BlogLLM.Client.Models;
using Mostlylucid.BlogLLM.Core.Services;
namespace Mostlylucid.BlogLLM.Client.Services
{
public class SuggestionService : ISuggestionService
{
private readonly BatchEmbeddingService _embeddingService;
private readonly QdrantVectorStore _vectorStore;
public SuggestionService(
BatchEmbeddingService embeddingService,
QdrantVectorStore vectorStore)
{
_embeddingService = embeddingService;
_vectorStore = vectorStore;
}
public async Task<List<SimilarPost>> FindSimilarPostsAsync(string context)
{
// Generate embedding for context
var embedding = _embeddingService.GenerateEmbedding(context);
// Search vector database
var results = await _vectorStore.SearchAsync(
queryEmbedding: embedding,
limit: 10,
languageFilter: "en"
);
// Convert to SimilarPost models
return results.Select(r => new SimilarPost
{
Slug = r.BlogPostSlug,
Title = r.BlogPostTitle,
SectionHeading = r.SectionHeading,
Preview = r.Text.Length > 200 ? r.Text.Substring(0, 200) + "..." : r.Text,
FullText = r.Text,
Score = r.Score
}).ToList();
}
public async Task<List<CodeSnippet>> ExtractCodeSnippetsAsync(List<SimilarPost> posts)
{
var snippets = new List<CodeSnippet>();
foreach (var post in posts)
{
// Extract code blocks from markdown
var codeBlocks = ExtractCodeBlocks(post.FullText);
snippets.AddRange(codeBlocks);
}
// Deduplicate and return top 5
return snippets
.GroupBy(s => s.Code)
.Select(g => g.First())
.Take(5)
.ToList();
}
public async Task<string> GenerateAiSuggestionAsync(string currentText, List<SimilarPost> context)
{
// This will be implemented in Part 6 with LLM integration
await Task.CompletedTask;
return "AI generation coming in Part 6...";
}
private List<CodeSnippet> ExtractCodeBlocks(string markdown)
{
var snippets = new List<CodeSnippet>();
var regex = new Regex(@"```(\w+)\n(.*?)\n```", RegexOptions.Singleline);
var matches = regex.Matches(markdown);
foreach (Match match in matches)
{
snippets.Add(new CodeSnippet
{
Language = match.Groups[1].Value,
Code = match.Groups[2].Value.Trim()
});
}
return snippets;
}
}
}
模型模型
namespace Mostlylucid.BlogLLM.Client.Models
{
public class SimilarPost
{
public string Slug { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string SectionHeading { get; set; } = string.Empty;
public string Preview { get; set; } = string.Empty;
public string FullText { get; set; } = string.Empty;
public float Score { get; set; }
}
public class CodeSnippet
{
public string Language { get; set; } = string.Empty;
public string Code { get; set; } = string.Empty;
}
public record InsertTextMessage(string Text);
}
依赖注射设置
App.axaml.cs
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using Mostlylucid.BlogLLM.Client.Services;
using Mostlylucid.BlogLLM.Client.ViewModels;
using Mostlylucid.BlogLLM.Client.Views;
using Mostlylucid.BlogLLM.Core.Services;
namespace Mostlylucid.BlogLLM.Client
{
public partial class App : Application
{
public IServiceProvider Services { get; private set; } = null!;
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
// Setup DI
var services = new ServiceCollection();
// Register core services
services.AddSingleton(_ => new BatchEmbeddingService(
modelPath: "C:\\models\\bge-base-en-onnx\\model.onnx",
tokenizerPath: "C:\\models\\bge-base-en-onnx\\tokenizer.json",
useGpu: true
));
services.AddSingleton(_ => new QdrantVectorStore(
host: "localhost",
port: 6334
));
// Register app services
services.AddSingleton<IEditorService, EditorService>();
services.AddSingleton<ISuggestionService, SuggestionService>();
// Register ViewModels
services.AddTransient<MainWindowViewModel>();
services.AddTransient<EditorViewModel>();
services.AddTransient<SuggestionsViewModel>();
Services = services.BuildServiceProvider();
// Create main window
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = Services.GetRequiredService<MainWindowViewModel>()
};
}
base.OnFrameworkInitializationCompleted();
}
}
}
正在运行应用程序
dotnet run
你应该看看:
- 左侧面板带有工具栏和实时预览的 :标记编辑器
- 右侧面板@: 类似文章,
- 状态栏: Word/ 字符计数和处理指标
在您输入“,” 时, 建议面板在 real@-time上更新了您的博客中的语义相似的内容。 @ !}
业绩优化
正在测试搜索
在每一个键盘上搜索@- @ 等待暂停 @ : @
public partial class EditorViewModel : ViewModelBase
{
private System.Timers.Timer _searchDebounceTimer;
private const int DebounceMs = 500;
public EditorViewModel(IEditorService editorService)
{
_editorService = editorService;
_searchDebounceTimer = new System.Timers.Timer(DebounceMs);
_searchDebounceTimer.AutoReset = false;
_searchDebounceTimer.Elapsed += async (s, e) =>
{
await GenerateSuggestionsAsync();
};
Document.TextChanged += (s, e) =>
{
Text = Document.Text;
// Restart debounce timer
_searchDebounceTimer.Stop();
_searchDebounceTimer.Start();
};
}
private async Task GenerateSuggestionsAsync()
{
var context = ExtractContext(Text);
await Dispatcher.UIThread.InvokeAsync(async () =>
{
await SuggestionsViewModel.GenerateSuggestionsAsync(context);
});
}
}
缓存的嵌套
最近打印的背景的缓存嵌入@ : @
public class EmbeddingCache
{
private readonly Dictionary<string, (float[] embedding, DateTime timestamp)> _cache = new();
private const int MaxCacheSize = 100;
private readonly TimeSpan _cacheExpiration = TimeSpan.FromMinutes(10);
public bool TryGet(string text, out float[] embedding)
{
if (_cache.TryGetValue(text, out var cached))
{
if (DateTime.Now - cached.timestamp < _cacheExpiration)
{
embedding = cached.embedding;
return true;
}
_cache.Remove(text);
}
embedding = null;
return false;
}
public void Set(string text, float[] embedding)
{
if (_cache.Count >= MaxCacheSize)
{
// Remove oldest
var oldest = _cache.OrderBy(kv => kv.Value.timestamp).First();
_cache.Remove(oldest.Key);
}
_cache[text] = (embedding, DateTime.Now);
}
}
摘要摘要
我们用 @:+# 建立了完整的 Windows 客户端
- ✅ 瓦隆 具有MVVM模式的UI框架
- ✅ 标记编辑器, 带有实时预览@(Avalon 编辑器)
- ✅ real- 键入时的语义搜索
- @✅建议小组显示类似职位。
- ✅ 代码片断提取和插入
- 与相关职位挂钩
- ✅ debspectd 搜索性能
- ✅依赖注射结构
什么是' 下一步 @?
内 **6:地方LLM整合部分**将当地LLM连结成本地 LLM推论 :
- 设置 拉马沙尔普 当地示范执行
- 正在下载和量化模型 @(_Llama #%2,#Mistral@)}*
- GGUF 格式 解释的量化
- A4000GPU 上的运行中推断
- 实际提出书面建议
- 博客写作即时工程
我们最终会看到 AI--在建议小组中提出的建议
系列导航
- 部分: 1:_ 导言 _&建筑
- 部分@2:GPU 设置 @&CUDA 在 CQ#
- “3: 理解嵌入器”部分
- 建立接收管道
- 窗口客户端 校对:Soup
- 6:地方LLM整合部分
- 内容生成 *&}快速工程
- 生产部署
资源资源资源 资源和资源资源资源
回头见 部分#6!