using Markdig.Wpf; using OllamaSharp; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Windows.Controls; using System.Windows.Input; using YwxApp.AiChat.Commands; namespace YwxApp.AiChat.ViewModels { /// /// 0、Current class: /// public class ChatMdViewModel : INotifyPropertyChanged { #region Field | Property | Collection | Command #region Field private string? _inputText; //User input text. private Chat? chat; //Build interactive chat. private ShareOllamaObject _ollama; //share Ollama object. private CancellationTokenSource _cancellationTokenSource; //Termination chat Token private bool _useExtensions = true; //whether enable Markdown extensions function. private string _markdownContent; //Markdown context. private MarkdownViewer markdownViewer; //Markdwon viewer. private bool _isAutoScrolling = false; //whather enable scroll private double _textWidth; // MarkdownViewer width #endregion #region Property : Support property changed notify. //InputText: public string? InputText { get => _inputText; set { _inputText = value; OnPropertyChanged(); } } public string MarkdownContent { get => _markdownContent; set { _markdownContent = value; // Notify property changed if needed OnPropertyChanged(); } } public double TextWidth { get => _textWidth; set { _textWidth = value; OnPropertyChanged(); } } #endregion #region Collection: #endregion #region Command: Builde Command: generate response command public ICommand? SubmitQuestionCommand { get; } //stop current chat public ICommand? StopCurrentChatCommand { get; } //new chat public ICommand? NewSessionCommand { get; } //scroll to MarkdownViewer end command public ICommand ScrollToEndCommand { get; } #endregion #endregion #region Constructor : Initialize public ChatMdViewModel() { // initialize object markdownViewer = new MarkdownViewer(); _cancellationTokenSource = new CancellationTokenSource(); //generate command SubmitQuestionCommand = new ParameterlessCommand(async () => OnSubmitQuestion()); StopCurrentChatCommand = new ParameterlessCommand(OnStopCurrentChat); NewSessionCommand = new ParameterlessCommand(OnNewSessionCommand); //markdown reletive command ScrollToEndCommand = new ScrollViewerCommand(OnScrollToEnd); OnLoadRecord(); } #endregion #region other method #region other //setting Ollama public void SetOllama(ShareOllamaObject ollama) { _ollama = ollama; } //check chat state private bool CheckChatState() { if (_ollama.Ollama == null || _ollama.OllamaEnabled == false) { MarkdownContent += "server not open..."; return false; } if (_ollama.Ollama.SelectedModel == null) { MarkdownContent += "model not select..."; return false; } if (string.IsNullOrWhiteSpace(InputText)) { MarkdownContent += "text is null ..."; return false; } return true; } //trigger sroll to end private void OnScrollToEnd(object parameter) { var scrollViewer = parameter as ScrollViewer; if (scrollViewer != null && _isAutoScrolling) { scrollViewer.ScrollToEnd(); TextWidth = scrollViewer.Width; } } #endregion #region Mardown command binding method //loaded history record public void OnLoadRecord() { OutText(File.ReadAllText($"{Environment.CurrentDirectory}//Data//" + $"{DateTime.Today.ToString("yyyyMMdd")}//{DateTime.Today.ToString("yyyyMMdd")}_0.txt")); } #endregion #endregion #region command method /// /// Submit question: Submit problem to the AI and get the output result /// private async void OnSubmitQuestion() { try { // Checks whether the string is empty, empty, or contains only whitespace characters if (CheckChatState()) { _isAutoScrolling = true; //enable auto scroll //ToggleExtensions(); string input = InputText; InputText = string.Empty; string output = string.Empty; OutText($"{Environment.NewLine}"); OutText($"## 【User】{Environment.NewLine}"); OutText($">{input}{Environment.NewLine}"); OutText($"## 【AI】{Environment.NewLine}"); // output += ($"{Environment.NewLine}"); output += ($"## 【User】{Environment.NewLine}"); output += ($">{input}{Environment.NewLine}"); output += ($"## 【AI】{Environment.NewLine}"); if (input.Equals("/clearContext")) { chat = new Chat(_ollama.Ollama); _ollama.RecordIndex++; return; } #region Start answer :Mode two => chat mode if (chat == null) { chat = new Chat(_ollama.Ollama); _ollama.RecordIndex++; } _cancellationTokenSource = new CancellationTokenSource(); await foreach (var answerToken in chat.SendAsync(input, _cancellationTokenSource.Token)) { OutText(answerToken); output += (answerToken); await Task.Delay(20); Debug.Print(answerToken); } OutText($"{Environment.NewLine}"); _ollama.WriteDataToFileAsync(output); #endregion } } catch (Exception ex) { OutText($"Error: {ex.Message}{Environment.NewLine}"); } _isAutoScrolling = false; } /// /// New build chat. /// private void OnNewSessionCommand() { OnStopCurrentChat(); if (chat != null) { chat.SendAsync("/clearContext"); if (_ollama != null) chat = new Chat(_ollama.Ollama); } OutText($"{string.Empty}{Environment.NewLine}"); } /// /// stop chat. /// private void OnStopCurrentChat() { _cancellationTokenSource?.Cancel(); Task.Delay(100); OutText($"{string.Empty}{Environment.NewLine}"); MarkdownContent = string.Empty; } /// /// output Text to Markdown. /// /// public void OutText(string text) { MarkdownContent += text; } #endregion #region Method that trigger a property changed event. /// /// OnPropertyChanged:Trigger a property changed event. /// public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #endregion } }