【C#之WPF+OllamaSharpe实现离线AI对话】

一、前言

C#之WPF+OllamaSharpe实现离线AI对话,调用Markdig格式化显示交互结果.

此程序默认你已经安装好了Ollama。

在运行前需要线安装好Ollama,如何安装请自行搜索

Ollama下载地址: https://ollama.org.cn

Ollama模型下载地址: https://ollama.org.cn/library

1.1 运行环境

基本运行环境: 根据自己使用的AI搜索对应模型基本配置,有需要使用GPU运行的模型。

运行效果

在这里插入图片描述

二、程序

2.1 项目结构

项目结构如下图,目前对WPF的MVVM模型只是初步初探,所有只是做了简单的模块区分。

  1. Models
    在此创建一些实现 ICommand 的类(这个目录应该只生命一些对象模型的,模型中只创建基本属性,但是我目前没有做区分。先简单实现功能,后面可能会优化。)

  2. Resources
    在此存放图像资源…

  3. Resources
    ViewModels:在此创建视图模型,视图对应的模型(前后端分离思想?),如SettingViewModel用于跟SetingView进行数据绑定(属性、命令)。

  4. Views
    在此创建视图->UI交互界面。

2.2 项目代码

MainWindow.xaml

<Window x:Class="MAIModel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MAIModel"
        xmlns:viewmodels="clr-namespace:MAIModel.ViewModels" 
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
        xmlns:behaviors="clr-namespace:MAIModel.Commands"
        mc:Ignorable="d"
        WindowStartupLocation="CenterScreen"
        Title="ChatAI" Height="600" Width="800"
        Icon="/Resources/app-logo128.ico"
        MinHeight="600" MinWidth="800">
    <!--Bind context-->
    <Window.DataContext>
        <viewmodels:MainViewModel/>
    </Window.DataContext>
    <!--Reference  style resource-->
    <Window.Resources>
        <ResourceDictionary>
            <!--resource dictionary : add control style-->
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Views/Style/ButtonStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <!-- Add close behevior event-->
    <i:Interaction.Behaviors>
        <behaviors:ClosingWindowBehavior Command="{Binding  ClosingWindowCommand}" />
    </i:Interaction.Behaviors>
    <!--Front-end display content-->
    <Grid>
        <!--defined column-->
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        <!--defined row-->
        <Grid.RowDefinitions>
            <RowDefinition Height="10"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="25"/>
        </Grid.RowDefinitions>

        <!-- Row 1 , Column 1: set the background color-->
        <Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
            <!-- Row 1 -->
            <Rectangle >
                <Rectangle.Fill>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                        <GradientStop Color="#916CE5" Offset="0.5" />
                        <GradientStop Color="#FFFFFF" Offset="1.5" />
                    </LinearGradientBrush>
                </Rectangle.Fill>
            </Rectangle>
        </Grid>
        <!-- Row 2,Column 11、Set the background color of function bar(Rectangular area).
            2、Set the function bar buttons : icon + text + other style
        -->
        <Grid Grid.Row="1"  Grid.Column="0"  >
            <!--Row 2,Column 1: Backgroud-->
            <Rectangle  Grid.Row="1" Grid.Column="0">
                <Rectangle.Fill>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                        <GradientStop Color="#9ABAFF" Offset="0.8" />
                        <GradientStop Color="#9ABFAF" Offset="0.3" />
                    </LinearGradientBrush>
                </Rectangle.Fill>
            </Rectangle>
            <!--Row 2,Column 1: Function button setting-->
            <StackPanel Margin="0 0 0 0"  Grid.Row="1" Grid.Column="0">
                <Button Command="{Binding SwitchToViewCommand}" CommandParameter="SettingView"
                Style="{StaticResource IconButtonStyle}">
                    <StackPanel Orientation="Horizontal">
                        <Image Source="/Resources/setting64.png" Margin="5" />
                        <TextBlock Text="设置" VerticalAlignment="Center"/>
                    </StackPanel>
                </Button>
                <Button 
                Command="{Binding SwitchToViewCommand}" CommandParameter="ChatMdView"
                Style="{StaticResource IconButtonStyle}">
                    <StackPanel Orientation="Horizontal">
                        <Image Source="/Resources/chat64.png" Margin="5"/>
                        <TextBlock Text="会话" VerticalAlignment="Center"/>
                    </StackPanel>
                </Button>
            </StackPanel>
        </Grid>
        <Grid Grid.Row="1" Grid.Column="1" Margin="5">
            <!-- Row 2,Column 2:Subview display area ,used to display switched subview.-->
            <ContentControl
        Content="{Binding CurrentView}"
        HorizontalAlignment="Stretch" 
        VerticalAlignment="Stretch" 
        HorizontalContentAlignment="Stretch" 
        VerticalContentAlignment="Stretch"/>
        </Grid>
        <!--  Row 2,Column 21、Background color.
            2、 Use the Lable to display current model and time.
        -->
        <Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3">
            <Rectangle>
                <Rectangle.Fill>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                        <GradientStop Color="#FAAFA9"  Offset="0.1" />
                        <GradientStop Color="#A4D3A2" Offset="0.9" />
                    </LinearGradientBrush>
                </Rectangle.Fill>
            </Rectangle >
            <WrapPanel  Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" 
    VerticalAlignment="Center" HorizontalAlignment="Right">
                <Label Content="{Binding CurrentModel}" Width="auto"  FontSize="12" Margin="5 0 5 0"/>
                <Label Content="{Binding CurrentTime}" Background="#00F0BD"
   Width="auto"  FontSize="12" Margin="5 0 5 0"/>
            </WrapPanel>
        </Grid>
    </Grid>
</Window>

SettingView.xaml

<UserControl  x:Class="MAIModel.Views.SettingView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local ="clr-namespace:MAIModel.ViewModels"
        mc:Ignorable="d" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <UserControl.Resources>
        <ResourceDictionary>
            <!--Resource dictionary : add the control style-->
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Style/ButtonStyle.xaml"/>
                <ResourceDictionary Source="Style/TextBoxStyle.xaml"/>
                <ResourceDictionary Source="Style/LabelStyle.xaml"/>
                <ResourceDictionary Source="Style/ComboBoxStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>


    <Grid Background="#FFFFFF"  HorizontalAlignment="Stretch">
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        
        <!-- The first line -->
        <WrapPanel Grid.Row="0" Margin="5"  VerticalAlignment="Center" HorizontalAlignment="Left">
            <Label Content="Ollama路径:" Margin="5" HorizontalAlignment="Left"  VerticalAlignment="Center" />
            <TextBox x:Name="Tbx_OllamaAppPath"  FontSize="12" 
                      Text="{Binding OllamaAppPath}"
                      Style="{StaticResource SearchBoxStyle}" Margin="5" />
        </WrapPanel>
        
        <!--The second line-->
        <WrapPanel Grid.Row="1" Margin="5"  VerticalAlignment="Center" HorizontalAlignment="Left">
            <Label Content="Ollama:" VerticalAlignment="Center" Margin="5" />
            <Label Name="Label_State" Style="{StaticResource RoundLabelStyle}"  />
            <Button Content="打开"   Style="{StaticResource RoundCornerButtonStyle}" 
                    Command="{Binding StartOllamaServerCommand}"/>
        </WrapPanel>
        
        <!--The third line-->
        <WrapPanel Grid.Row="2" Margin="5"  VerticalAlignment="Center" HorizontalAlignment="Left">
            <Label Content="模型:" VerticalAlignment="Center" Margin="5" />
            <ComboBox x:Name="Cbx_ModelList" Style="{StaticResource RoundComboBoxStyle}" 
              ItemsSource="{Binding ModelList}"
              SelectedItem="{Binding SelectedModel}">
            </ComboBox>
            <Button Content="刷新"    Margin="5" Grid.Row="1" 
            Style="{StaticResource RoundCornerButtonStyle}" 
            Command="{Binding ModelListUpdateCommand}"/>
        </WrapPanel>
        <TextBox  x:Name="ModelDesciption" Grid.Row="3" IsReadOnly="True" 
                  TextWrapping="WrapWithOverflow" Text="{Binding ModelInformation,Mode=OneWay}"/>
    </Grid>
</UserControl>



------------------------------------------- SettingView  ------------------------------------------- 
using MAIModel.ViewModels;
using System.Windows.Controls;

namespace MAIModel.Views
{
    public partial class SettingView : UserControl
    {
        SettingViewModel _viewModel;
        public SettingView(ShareOllamaObject ollama)
        {
            InitializeComponent();
            _viewModel = new SettingViewModel(ollama);
            this.DataContext = _viewModel;
        }
    }
}

ChatMdView.xaml

<UserControl x:Class="MAIModel.Views.ChatMdView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
             xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources >
        <ResourceDictionary>
            <!--Resource dictionary:Add control style.-->
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Style/ButtonStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid Background="#0F000F">
       
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="200" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
           
        <!--First line: Display output text to "Markdown" container-->
        <Grid Grid.Row="0">
            <ScrollViewer Background="#FFFFFF" x:Name="MarkDownScrollViewer">
                <!--Bind event command to the ScrollViewer-->
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="ScrollChanged">
                        <i:InvokeCommandAction Command = "{Binding ScrollToEndCommand}"
                            CommandParameter="{Binding ElementName=MarkDownScrollViewer}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
                <!--scrollviewer internal container-->
                <markdig:MarkdownViewer x:Name="MarkdownOutputBox" Markdown="{Binding MarkdownContent}" />
            </ScrollViewer>
        </Grid>

        <!-- the second line  -->
        <Grid Grid.Row="1">
            <TextBox x:Name="InputBox"
             Text="{Binding InputText , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
             Grid.Row="1"  Margin="5" AcceptsReturn="True" 
             VerticalScrollBarVisibility="Auto">
                <!--key binding of "Enter"-->
                <TextBox.InputBindings>
                    <KeyBinding Command="{Binding SubmitQuestionCommand}" Key="Enter"/>
                </TextBox.InputBindings>
            </TextBox>
        </Grid>
        
        <!-- The third line: submit button  -->
        <Grid Grid.Row="2">
            <WrapPanel Grid.Row="2"  HorizontalAlignment="Right">

                <Button x:Name="BtnNewChat" Content="新建会话" 
                     HorizontalAlignment="Right"
                     Style="{StaticResource RoundCornerButtonStyle}"
                     Command="{Binding NewSessionCommand}" 
                     Width="100" 
                     Height="30"/>
                <Button x:Name="BtnSubmit"  Content="提交" 
                    HorizontalAlignment="Right"
                    Style="{StaticResource RoundCornerButtonStyle}"
                    Command="{Binding SubmitQuestionCommand}" 
                    Width="100" 
                    Height="30"/>
            </WrapPanel>
        </Grid>
        
    </Grid>
</UserControl>

----------------------------------------- ChatMdView  -----------------------------------------
using MAIModel.ViewModels;
using System.Windows.Controls;

namespace MAIModel.Views
{
    public partial class ChatMdView : UserControl
    {
        ChatMdViewModel viewModel;
        public ChatMdView(ShareOllamaObject shareOllama)
        {
            InitializeComponent();
            viewModel = new ChatMdViewModel();
            viewModel.SetOllama(shareOllama);
            this.DataContext = viewModel;
        }
    }
}

ShareOllamaObject

using OllamaSharp;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows;

namespace MAIModel.ViewModels
{
    /// <summary>
    /// 0、Current class:
    /// </summary>
    public class ShareOllamaObject
    {
        #region Field | Property | Collection | Command

        #region Field
        private bool _ollamaEnabled = false;        //ollama connected state
        private string _ollamaAppPath;              //ollama app path.
        private int recordIndex = 0;                //current record index.
        private string _currentPath;                //current record;

        private Chat chat;                          //build interactive chat model object.
        private OllamaApiClient _ollama;            //OllamaAPI object.
        #endregion

        #region Property
        public string OllamaAppPath
        {
            get { return _ollamaAppPath; }
            set { _ollamaAppPath = value; }
        }
        public bool OllamaEnabled
        {
            get { return _ollamaEnabled; }
            set { _ollamaEnabled = value; }
        }
        public OllamaApiClient Ollama
        {
            get { return _ollama; }
            set { _ollama = value; }
        }
        public Chat Chat
        {
            get { return chat; }
            set { chat = value; }
        }
        public string CurrentPath
        {
            get => _currentPath;
        }
        public int RecordIndex
        {
            get => recordIndex;
            set
            {
                recordIndex = value;
                _currentPath = $"{Environment.CurrentDirectory}//Data//{DateTime.Today.ToString("yyyyMMdd")}" +
                               $"//{DateTime.Today.ToString("yyyyMMdd")}_{recordIndex}.txt";
            }
        }
        #endregion

        #region Collection
        public ObservableCollection<string> ModelList { get; set; }
        #endregion

        #endregion

        #region Constructor
        public ShareOllamaObject()
        {
            RecordIndex = 0;
            WriteDataToFileAsync("");
            Init(OllamaAppPath, "llama3.2:9b");
        }
        #endregion

        #region other method
        /// <summary>
        /// initialite method
        /// </summary>
        private void Init(string appPath,string modelName)
        {
            OllamaAppPath =appPath;
            try
            {
                // 设置默认设备为GPU
                Environment.SetEnvironmentVariable("OLLAMA_DEFAULT_DEVICE", "gpu");
                //判断路径是否存在
                if (OllamaAppPath == string.Empty || OllamaAppPath == null) OllamaAppPath = @"ollama app.exe";
                //路径存在获取应用名
                if (File.Exists(OllamaAppPath)) OllamaAppPath = Path.GetFileName(OllamaAppPath);
                //获取环境Ollama环境变量:用于找到 :ollama app.exe
                var filePath = FindExeInPath(OllamaAppPath);
                //如果路径存在,启动Ollama
                if (File.Exists(filePath)) CheckStartProcess(OllamaAppPath);
                //连接Ollama,并设置初始模型
                _ollama = new OllamaApiClient(new Uri("http://localhost:11434"));
                //获取本地可用的模型列表
                ModelList = (ObservableCollection<string>)GetModelList();
                var tmepModelName = ModelList.FirstOrDefault(name => name.ToLower().Contains("llama3.2"));
                if (tmepModelName!=null) _ollama.SelectedModel = tmepModelName;
                else if (ModelList.Count>0) _ollama.SelectedModel = ModelList[ModelList.Count-1];

                if (ModelList.FirstOrDefault(name => name.Equals(modelName))!=null) _ollama.SelectedModel = modelName;

                //Ollama服务启用成功
                OllamaEnabled = true;
            }
            catch (Exception)
            {
                OllamaEnabled = false;
            }
        }
        /// <summary>
        /// update the model selected by  Ollama
        /// </summary>
        public void UpdataSelectedModel(string model)
        {
            Ollama.SelectedModel = model;
            OllamaEnabled = true;
        }

        /// <summary>
        /// Start Ollama app and relevant server.
        /// </summary>
        public async void StartOllama(string appPath,string modelName)
        {
            Init(appPath,modelName); await Task.Delay(1);
        }
        /// <summary>
        /// get model list
        /// </summary>
        public Collection<string> GetModelList()
        {
            var models = _ollama.ListLocalModelsAsync();
            var modelList = new ObservableCollection<string>();
            foreach (var model in models.Result)
            {
                modelList.Add(model.Name);
            }
            return modelList;
        }
        #endregion

        #region starting or closeing method of Ollama(server).
        /// <summary>
        /// Finds whether the specified application name is configured in the system environment. 
        /// If it exists, return the full path, otherwise return null
        /// </summary>
        public static string FindExeInPath(string exeName)
        {
            // get environment variable "Path" value
            var pathVariable = Environment.GetEnvironmentVariable("PATH");

            // Split string
            string[] paths = pathVariable.Split(Path.PathSeparator);

            foreach (string path in paths)
            {
                string fullPath = Path.Combine(path, exeName);
                if (File.Exists(fullPath))
                {
                    return fullPath;
                }
            }
            return null;
        }

        /// <summary>
        ///Startup program Specifies a program, enters a program name, and determines whether the program is running.
        ///     If it is running, exit directly, otherwise run the program according to the input path.
        /// </summary>
        public static void CheckStartProcess(string processPath)
        {
            string processName = Path.GetFileName(processPath);
            CheckStartProcess(processName, processPath);
        }

        /// <summary>
        /// Startup program Specifies a program, enters a program name, and determines whether the program is running. 
        ///     If it is running, exit directly, otherwise run the program according to the input path.
        /// </summary>
        public static void CheckStartProcess(string processName, string processPath)
        {
            // Check whather  the program  is running 
            if (!IsProcessRunning(processName))
            {
                Console.WriteLine($"{processName} is not running. Starting the process...");
                StartProcess(processPath);
            }
            else Console.WriteLine($"{processName} is already running.");
        }


        /// <summary>
        /// Enter the program path to start the program
        /// </summary>
        public static void StartProcess(string processPath)
        {
            try
            {
                Process.Start(processPath);
                Console.WriteLine("Process started successfully.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error starting process: {ex.Message}");
            }
        }

        /// <summary>
        /// Check whather the process is running
        /// </summary>
        public static bool IsProcessRunning(string processName)
        {
            Process[] processes = Process.GetProcessesByName(processName);
            return processes.Length > 0;
        }

        /// <summary>
        /// close the process with the specify name.
        /// </summary>
        /// <param name="processName"></param>
        public static void CloseProcess(string processName)
        {
            try
            {
                foreach (var process in Process.GetProcessesByName(processName))
                {
                    process.Kill();
                    process.WaitForExit();
                    Application.Current.Shutdown();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"无法关闭【{processName}】进程: {ex.Message}");
            }
        }
        /// <summary>
        /// get current process name
        /// </summary>
        public static string GetProgramName()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            return assembly.GetName().Name;
        }
        #endregion

        #region File save
        /// <summary>
        /// Save record
        /// </summary>
        public void WriteDataToFileAsync(string data, int retryCount = 5, int delayMilliseconds = 500)
        {
            //Get the directory where the file located.
            string directoryPath = Path.GetDirectoryName(CurrentPath);

            // if directory exists't ,create directory(include all must directory).
            if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
            {
                Directory.CreateDirectory(directoryPath);
            }

            for (int i = 0; i < retryCount; i++)
            {
                try
                {
                    using (FileStream fs = new FileStream(CurrentPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
                    using (StreamWriter writer = new StreamWriter(fs, Encoding.UTF8))
                    {
                         writer.WriteAsync(data);
                    }
                    return;                             // successful writed exit the loop.
                }
                catch (IOException ex)
                {
                    if (i == retryCount - 1)
                    {
                        throw;                          //If the maximum number of retries is reached , a exception is thrown
                    }
                      Task.Delay(delayMilliseconds);    // Wait a while and try again
                }
                catch (Exception ex)
                {
                    throw;                              //other exception is thrown
                }
            }
        }
        #endregion
    }
}

MainViewModel

using MAIModel.Commands;
using MAIModel.Views;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
namespace MAIModel.ViewModels
{
    /// <summary>
    /// </summary>
    public class MainViewModel : INotifyPropertyChanged
    {
        #region Field | Property | Collection | Command

        #region Field
        private object _currentView;                //The current view object.
        private string _currentTime;                //The current time.
        private string _currentModel;               //The current model name.
        private DispatcherTimer _timer;             //Time label timer.
        private ShareOllamaObject _ollamaObject;    //OllamaAPI object.
        #endregion

        #region Property
        public object CurrentView
        {
            get => _currentView;
            set
            {
                _currentView = value;
                OnPropertyChanged();
            }
        }
        public string CurrentTime
        {
            get => _currentTime;
            set
            {
                _currentTime = value;
                OnPropertyChanged();
            }
        }
        public string CurrentModel
        {
            get => _currentModel;
            set
            {
                _currentModel = value;
                OnPropertyChanged();
            }
        }
        #endregion

        #region Collection
        private ObservableCollection<UserControl> _viewList;  
        private ObservableCollection<UserControl> ViewList
        {
            get => _viewList;
            set
            {
                _viewList = value;
                OnPropertyChanged();
            }
        }
        #endregion

        #region Command
        public ICommand SwitchToViewCommand { get; }
        public ICommand ClosingWindowCommand { get; }

        #endregion

        #endregion

        #region Constructor
        public MainViewModel()
        {
            //Initialize Ollama object.  
            _ollamaObject = new ShareOllamaObject(); 

            //bind command method
            SwitchToViewCommand = new ObjectPassingCommand(OnSwitchToView);
            ClosingWindowCommand = new EventsCommand<CancelEventArgs>(OnClosingWindow);

            //create view
            _viewList = new ObservableCollection<UserControl>();
            ViewList.Add(new SettingView(_ollamaObject));
            ViewList.Add(new ChatMdView(_ollamaObject));

            //Set the default display of subview 1.
            CurrentModel = _ollamaObject.Ollama.SelectedModel;
            InitializeTimer();
            CurrentView = ViewList[0];
        }

        #region The window close event 
        /// <summary>
        ///trigger close event
        /// </summary>
        private void OnClosingWindow(CancelEventArgs e)
        {
            if (MessageBox.Show("确定要关闭程序吗?", "确认关闭", MessageBoxButton.YesNo) == MessageBoxResult.No)
                e.Cancel = true;
            else  ClearingResources();
        }
        /// <summary>
        /// Clear the resource.
        /// </summary>
        private void ClearingResources()
        {
            ShareOllamaObject.CloseProcess("ollama_llama_server");
            Debug.Print($"{ShareOllamaObject.GetProgramName()}:关闭成功...");
        }
        #endregion
        #endregion

        #region Other mothod
        //Initialize time label timer //Each one second update once
        private void InitializeTimer()
        {
            _timer = new DispatcherTimer();
            _timer.Interval = TimeSpan.FromSeconds(1); 
            _timer.Tick += Timer_Tick;
            _timer.Start();
        }
        //update current time
        private void Timer_Tick(object sender, EventArgs e)
        {
            CurrentTime = DateTime.Now.ToString("HH:mm:ss");
            CurrentModel = _ollamaObject.Ollama.SelectedModel;
        }

        #endregion

        #region Command method

        #region View switch
        //set the view
        public void OnSwitchToView(object operationItem)
        {
            var viewObj = ViewList.FirstOrDefault(viewObj => viewObj.GetType().Name.Equals(operationItem));
            if (viewObj == null)
            {
                var newViewObj =new UserControl();
                switch (operationItem)
                {
                    case "ChatMdView":
                        newViewObj = new ChatMdView(_ollamaObject);
                        break;
                    case "SettingView":
                        newViewObj = new SettingView(_ollamaObject);
                        break;
                    default:
                        break;
                }
                ViewList.Add(newViewObj);
                CurrentView = newViewObj;
            }
            else
            {
                CurrentView = viewObj;
            }
        }
        #endregion

        #endregion

        #region Property changed event
        public event PropertyChangedEventHandler? PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

SettingViewModel

using MAIModel.Commands;
using MAIModel.Models;
using Microsoft.Win32;
using OllamaSharp;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace MAIModel.ViewModels
{
    /// <summary>
    /// 0、Current class:
    /// </summary>
    public class SettingViewModel:INotifyPropertyChanged
    {
        #region Field | Property | Collection | Command

        #region Field
        private string _selectedModel;                  //select model
        private string _modelInfo;                      //model info
        private SolidColorBrush _labelBackgroundColor;  //color style
        private readonly ShareOllamaObject _ollama;     //OllamaAPI object.
        #endregion

        #region Property
        public string OllamaAppPath
        {
            get { return _ollama.OllamaAppPath; }
            set { _ollama.OllamaAppPath = value; OnPropertyChanged(); }
        }
        public string SelectedModel
        {
            get => _selectedModel;
            set
            {
                if (_selectedModel != value)
                {
                    _selectedModel = value;
                    ResetModelName();
                }
                OnPropertyChanged();
            }
        }
        public string ModelInformation
        {
            get => _modelInfo;
            set
            {
                _modelInfo = value;
                OnPropertyChanged();
            }
        }
        public SolidColorBrush LabelBackgroundColor
        {
            get => _labelBackgroundColor;
            set
            {
                if (_labelBackgroundColor != value)
                {
                    _labelBackgroundColor = value;
                    OnPropertyChanged();
                }
            }
        }
        #endregion

        #region Collection
        public ObservableCollection<string> ModelList
        {
            get { return _ollama.ModelList; }
            set { _ollama.ModelList = value; OnPropertyChanged(); }
        }
        #endregion

        #region Command
        public ICommand OpenFileDialogCommand { get; }      //select Ollama application file path command.
        public ICommand GetModelListCommand { get; }        //get model list command.
        public ICommand ModelListUpdateCommand { get; }     //model list update command.
        public ICommand StartOllamaServerCommand { get; }   //start ollam server command.
        #endregion

        #endregion

        #region Constructor
        public SettingViewModel(ShareOllamaObject ollama)
        {
            _ollama = ollama;
            Task task = OnGetModelList();
            OpenFileDialogCommand = new ParameterlessCommand(() => OnSelectOllamaAppPathDialog());
            GetModelListCommand = new ParameterlessCommand(async () => await OnGetModelList());
            ModelListUpdateCommand = new ParameterlessCommand(async () => await OnModelListUpdate());
            StartOllamaServerCommand = new ParameterlessCommand(async () => OnStartOllamaServer());
            SetConnected();
        }
        #endregion

        #region other method
        ///set  ollama model server application object.
        public void SetOllamaApiClient(OllamaApiClient ollama)
        {
            _ollama.Ollama = ollama;
        }

        // set the connection states color
        public void SetConnected()
        {
            if (_ollama.OllamaEnabled)
            {
                LabelBackgroundColor = Brushes.Green;
            }
            else
            {
                LabelBackgroundColor = Brushes.Red;
            }
        }
        /// <summary>
        /// reset the model
        /// </summary>
        private void ResetModelName()
        {
            _ollama.OllamaEnabled = false;
            _ollama.Ollama.SelectedModel = SelectedModel;
            ModelInformationChanged();
            _ollama.OllamaEnabled = true;
        }
        /// <summary>
        /// model info changed
        /// </summary>
        public void ModelInformationChanged()
        {
            string modelName = SelectedModel.Split(':')[0].ToLower();
            string modelInfoPath = $"{Environment.CurrentDirectory}\\model introduction\\{modelName}.txt";
            string info = string.Empty;
            if (File.Exists(modelInfoPath))
            {
                info = File.ReadAllText(modelInfoPath);
            }
            //MessageBox.Show(modelInfoPath);
            switch (modelName)
            {
                case ModelDescription.Llama32:
                    ModelInformation = info;
                    break;
                case ModelDescription.CodeGemma:
                    ModelInformation = info;
                    break;
                default:
                    ModelInformation = "";
                    break;
            }
        }
        #endregion

        #region command trigger method
        private void OnStartOllamaServer()
        {
            if (!_ollama.OllamaEnabled)
            {
                _ollama.StartOllama(OllamaAppPath, SelectedModel);
            }
        }
        private void OnSelectOllamaAppPathDialog()
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            if (openFileDialog.ShowDialog() == true)
            {
                OllamaAppPath = openFileDialog.FileName;
            }
        }
        /// <summary>
        /// get the model list
        /// </summary>
        private async Task OnGetModelList()
        {
            try
            {
                ModelList.Clear(); 
                ModelList = (ObservableCollection<string>)_ollama.GetModelList();
                Debug.Print($"ModelList count: {ModelList.Count}");
                SelectedModel = _ollama.Ollama.SelectedModel;
                var modelName = ModelList.FirstOrDefault(name=>name.Equals(SelectedModel));
                if (ModelList.Count>0 && modelName != null)
                {
                    SelectedModel = ModelList[ModelList.Count-1];
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
        /// <summary>
        /// update the model list
        /// </summary>
        private async Task OnModelListUpdate()
        {
            MessageBox.Show($"List Update");
        }
        #endregion

        #region property changed event
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

ChatMdViewModel

using MAIModel.Commands;
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;

namespace MAIModel.ViewModels
{
    /// <summary>
    /// 0、Current class:
    /// </summary>
    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
        /// <summary>
        /// Submit question:  Submit problem to the AI and get the output result
        /// </summary>
        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;
        }

        /// <summary>
        /// New build chat.
        /// </summary>
        private void OnNewSessionCommand()
        {
            OnStopCurrentChat();
            if (chat != null)
            {
                chat.SendAsync("/clearContext");
                if (_ollama != null)
                    chat = new Chat(_ollama.Ollama);
            }
            OutText( $"{string.Empty}{Environment.NewLine}");
        }
        /// <summary>
        /// stop chat.
        /// </summary>
        private void OnStopCurrentChat()
        {
            _cancellationTokenSource?.Cancel();
            Task.Delay(100);
            OutText($"{string.Empty}{Environment.NewLine}");
            MarkdownContent = string.Empty;
        }
        /// <summary>
        /// output Text to Markdown.
        /// </summary>
        /// <param name="text"></param>
        public void OutText(string text)
        {
            MarkdownContent += text;
        }
        #endregion

        #region Method that trigger a property changed event.
        /// <summary>
        /// OnPropertyChanged:Trigger a property changed event.
        /// </summary>
        public event PropertyChangedEventHandler? PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

ModelDescription

/// <summary>
/// select switch  display model description.
/// </summary>
public class ModelDescription
{
    public const string CodeGemma = "codegemma";
    public const string Llama32 = "llama3.2";
    //model list(description)
    public const string Codellama34b = "codellama:34b";
    public const string Llava13b = "llava:13b";
    public const string CommandRLatest = "command-r:latest";
    public const string Wizardlm2Latest = "wizardlm2:latest";
    public const string Qwen25CoderLatest = "qwen2.5-coder:latest";
    public const string Qwen25_14b = "qwen2.5:14b";
    public const string SamanthaMistralLatest = "samantha-mistral:latest";
    public const string MistralSmallLatest = "mistral-small:latest";
    public const string Gemma29b = "gemma2:9b";
    public const string NemotronMiniLatest = "nemotron-mini:latest";
    public const string Phi35Latest = "phi3.5:latest";
    public const string Llama32VisionLatest = "llama3.2-vision:latest";
    public const string Llama31_8b = "llama3.1:8b";
    public const string Gemma22b = "gemma2:2b";
    public const string Qwen27b = "qwen2:7b";
    public const string Qwen20_5b = "qwen2:0.5b";
    public const string Llama31_70b = "llama3.1:70b";
    public const string Llama31Latest = "llama3.1:latest";
    public const string Llama32Latest = "llama3.2:latest";
    public const string Llama32_3b = "llama3.2:3b";
}

ClosingWindowBehavior

using System.ComponentModel;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows;

namespace MAIModel.Commands
{
    /// <summary>
    /// Close Window Behavior
    /// </summary>
    public class ClosingWindowBehavior : Behavior<Window>
    {
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(ClosingWindowBehavior), new PropertyMetadata(null));

        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object), typeof(ClosingWindowBehavior), new PropertyMetadata(null));

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        public object CommandParameter
        {
            get { return GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.Closing += OnClosing;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.Closing -= OnClosing;
        }

        private void OnClosing(object sender, CancelEventArgs e)
        {
            if (Command != null && Command.CanExecute(CommandParameter))
            {
                Command.Execute(e);
            }
        }
    }
}

EventsCommand

using System;
using System.Windows.Input;
/// <summary>
/// close window events
/// </summary>
public class EventsCommand<T> : ICommand
{
    private readonly Action<T> _execute;
    private readonly Func<T, bool> _canExecute;
    public EventsCommand(Action<T> execute, Func<T, bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }
    public bool CanExecute(object parameter)
    {
        return _canExecute?.Invoke((T)parameter) ?? true;
    }
    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
}

ObjectPassingCommand


using System.Windows.Input;

namespace MAIModel.Commands
{
    /// <summary>
    /// object parameter passing.
    /// </summary>
    public class ObjectPassingCommand : ICommand
    {
        public Action<object> execute;
        public ObjectPassingCommand(Action<object> execute)
        {
            this.execute = execute;
        }
        public event EventHandler? CanExecuteChanged;

        public bool CanExecute(object? parameter)
        {
            return CanExecuteChanged != null;
        }

        public void Execute(object? parameter)
        {
            execute?.Invoke(parameter);
        }
    }
}

ParameterlessCommand


using System.Windows.Input;

namespace MAIModel.Commands
{
    /// <summary>
    /// relay  command 
    /// </summary>
    public class ParameterlessCommand : ICommand
    {
        private Action _execute;
        public ParameterlessCommand(Action execute)
        {
            _execute = execute;
        }
        public event EventHandler? CanExecuteChanged;

        public bool CanExecute(object? parameter)
        {
            return CanExecuteChanged != null;
        }

        public void Execute(object? parameter)
        {
            _execute.Invoke();
        }
    }
}

ScrollViewerCommand


using System.Windows.Input;

namespace MAIModel.Commands
{
    /// <summary>
    /// Scroll command : The argument object passed by this constructor of this class is ScrollViewer
    /// </summary>
    class ScrollViewerCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Predicate<object> _canExecute;

        public ScrollViewerCommand(Action<object> execute, Predicate<object> canExecute = null)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    }
}

三、总结

1、通过此项目学习了WPF。
2、通过此项目了解了基本的MVVM模式,WPF的数据绑定,属性变更,以及如何通过实现ICommand接口进行命令触发。
3、简单实现了使用C#和OllamaAPI实现AI交互界面。
4、简单调用了Markdig库 将交互数据以md格式显示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/928731.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 力扣 热题 100道(十四)二叉树的中序遍历(C++)

给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 如下为代码&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullpt…

在Node.js局域网调试https的Vue项目

需求&#xff1a; 最近在测试在网页端&#xff08;HTML5&#xff09;调用移动设备的定位等权限功能&#xff0c;发现某些功能是必须保证域名在https下的否则会出现不正常现象。 解决&#xff1a; 1.在线生成和证书 访问&#xff1a;CSR文件生成工具-中国数字证书CHINASSL …

视频监控汇聚平台Liveweb视频安防监控实时视频监控系统操作方案

Liveweb国标GB28181视频平台是一种基于国标GB/T28181协议的安防视频流媒体能力平台。它支持多种视频功能&#xff0c;包括实时监控直播、录像、检索与回看、语音对讲、云存储、告警以及平台级联等功能。该平台部署简单、可扩展性强&#xff0c;支持全终端、全平台分发接入的视频…

如何利用内链策略提升网站的整体权重?

内链是谷歌SEO中常常被低估的部分&#xff0c;实际上&#xff0c;合理的内链策略不仅能帮助提升页面间的关联性&#xff0c;还可以增强网站的整体权重。通过正确的内链布局&#xff0c;用户可以更流畅地浏览你的网站&#xff0c;谷歌爬虫也能更快地抓取到更多页面&#xff0c;有…

DICOM MPPS详细介绍

文章目录 前言一、常规检查业务流程二、MPPS的作用三、MPPS的原理1、MPPS与MWL2、MPPS服务过程 四、MPPS的实现步骤1、创建实例2、传递状态 五、总结 前言 医院中现有的DICOM MWL(Modality Worklist)已开始逐渐得到应用&#xff0c;借助它可以实现病人信息的自动录入&#xff0…

44页PDF | 信息化战略规划标准框架方法论与实施方法(限免下载)

一、前言 这份报告详细介绍了企业信息化战略规划的标准框架、方法论以及实施方法&#xff0c;强调了信息化规划应以业务战略和IT战略为驱动力&#xff0c;通过构筑企业架构&#xff08;EA&#xff09;来连接长期战略和信息化建设。报告提出了信息化规划原则&#xff0c;探讨了…

Linux 权限管理:用户分类、权限解读与常见问题剖析

&#x1f31f; 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。&#x1f31f; &#x1f6a9;用通俗易懂且不失专业性的文字&#xff0c;讲解计算机领域那些看似枯燥的知识点&#x1f6a9; 目录 &#x1f4af;L…

flask内存马的真谛!!!

flask内存马 1.概念 常用的Python框架有Django、Flask, 这两者都可能存在SSTI漏洞. Python 内存马利用Flask框架中SSTI注入来实现, Flask框架中在web应用模板渲染的过程中用到render_template_string进行渲染, 但未对用户传输的代码进行过滤导致用户可以通过注入恶意代码来实…

AI技术在电商行业中的应用与发展

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

计算机网络期末复习-part1-概述

1、互联网的组成 互联网由两大块组成。 1、边沿部分&#xff1a;由所有连接在互联网上的主机组成&#xff0c;是用户直接使用的部分。 2、核心部分&#xff0c;由大量网络和路由器组成&#xff0c;为边缘部分提供服务。 2、数据传送阶段的三种交换方式的主要特点 1、电路交…

『数据结构』空间复杂度

&#x1f6a9; WRITE IN FRONT &#x1f6a9; &#x1f50e; 介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四" &#x1f50e;&#x1f3c5; 荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…

使用GDI对象绘制UI时需要注意的若干细节问题总结

目录 1、一个bitmap不能同时被选进两个dc中 2、CreateCompatibleDC和CreateCompatibleBitmap要使用同一个dc作为参数 3、不能删除已经被选入DC中的GDI对象 4、使用完的GDI对象&#xff0c;要将之释放掉&#xff0c;否则会导致GDI对象泄漏 5、CreateCompatibleBitmap返回错…

NineData云原生智能数据管理平台新功能发布|2024年11月版

本月发布 8 项更新&#xff0c;其中重点发布 2 项、功能优化 6 项。 重点发布 数据库 Devops - 数据生成支持多个数据源 NineData 支持在数据库中自动生成符合特定业务场景的随机数据&#xff0c;用于模拟实际生产环境中的数据情况&#xff0c;帮助用户在不使用真实数据的情况…

Github clone 的时候出现Error in the HTTP2 framing layer错误

解决方案 github鉴权认证&#xff0c;打开gitbash&#xff0c;并输入 ssh-keygen -t rsa -C "emailicjs.cc" 执行后会在 .ssh 目录生产两个文件&#xff1a;id_rsa&#xff08;私有密钥&#xff09;和id_rsa.pub&#xff08;公开密钥&#xff09; 直接默认回车执行…

html-两个div,让一个div跟随另外一个div的高度

在开发的过程中遇到有些场景事这样的&#xff0c;两个div的高度不一致&#xff0c;而且都是动态高度&#xff0c;有的时候div1高&#xff0c;有的时候div2高&#xff0c;如果设置flex的话&#xff0c;那么就会把较矮的元素撑大&#xff0c;但是我想始终都以div1的高度作为基准&…

【Java-数据结构篇】Java 中栈和队列:构建程序逻辑的关键数据结构基石

我的个人主页 我的专栏&#xff1a;Java-数据结构&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;点赞❤ 收藏❤ 一、引言 1. 栈与队列在编程中的角色定位 栈和队列作为两种基本的数据结构&#xff0c;在众多编程场景中都有着独特的地位。它们为数据的有序…

EasyAnimateV5 视频生成大模型原理详解与模型使用

在数字内容创作中&#xff0c;视频扮演的角色日益重要。然而&#xff0c;创作高质量视频通常耗时且昂贵。EasyAnimate 系列旨在利用人工智能技术简化这一过程。EasyAnimateV5 建立在其前代版本的基础之上&#xff0c;不仅在质量上有所提升&#xff0c;还在多模态数据处理和跨语…

浅谈volatile

volatile有三个特性&#xff1a; &#xff08;1&#xff09;可见性 &#xff08;2&#xff09;不保证原子性 &#xff08;3&#xff09;禁止指令重排 下面我们一一介绍 &#xff08;一&#xff09;可见性 volatile的可见性是说共享变量只要修改&#xff0c;就可以被其他线…

深入理解AVL树:结构、旋转及C++实现

1. AVL树的概念 什么是AVL树&#xff1f; AVL树是一种自平衡的二叉搜索树&#xff0c;其发明者是Adelson-Velsky和Landis&#xff0c;因此得名“AVL”。AVL树是首个自平衡二叉搜索树&#xff0c;通过对树的平衡因子进行控制&#xff0c;确保任何节点的左右子树高度差最多为1&…

电脑插入耳机和音响,只显示一个播放设备

1. 控制面板-硬件和声音-Realtek高清音频-扬声器-设备高级设置-播放设备里选择使用前部和后部输出设备同时播放两种不同的音频流 在声音设置中就可以看到耳机播放选项