一个跨平台的ChatGPT悬浮窗工具

2023-10-13 08:38:42 浏览数 (3)

一个跨平台的ChatGPT悬浮窗工具

使用avalonia实现的ChatGPT的工具,设计成悬浮窗,并且支持插件。

如何实现悬浮窗?

在使用avalonia实现悬浮窗也是非常的简单的。

实现我们需要将窗体设置成无边框

Window根节点添加一下属性,想要在Linux下生效请务必添加SystemDecorations属性

代码语言:javascript复制
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
SystemDecorations="None"

这样我们的窗口就设置成了无边框。

然后我们还需要将窗体的大小固定,

代码语言:javascript复制
Height="50"
MaxHeight="50"
Width="{Binding Width}"
MaxWidth="{Binding Width}"

高度固定,宽度绑定到ViewModelWidth属性中,默认270

接下来给出所有代码,

代码语言:javascript复制
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:Gotrays.Suspension.Client.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:valueConverter="clr-namespace:Gotrays.Suspension.Client.ValueConverter"
        xmlns:md="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia"
        xmlns:avedit="https://github.com/avaloniaui/avaloniaedit"
        xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="Gotrays.Suspension.Client.Views.MainWindow"
        x:DataType="vm:MainWindowViewModel"
        ExtendClientAreaToDecorationsHint="True"
        ExtendClientAreaChromeHints="NoChrome"
        ExtendClientAreaTitleBarHeightHint="-1"
        SystemDecorations="None"
        WindowStartupLocation="CenterScreen"
        Height="50"
        MaxHeight="50"
        Width="{Binding Width}"
        MaxWidth="{Binding Width}"
        Icon="/Assets/ai.png"
        Title="Gotrays.Suspension.Client">

    <Window.Resources>
        <valueConverter:ImageConverter x:Key="ImageConverter" />
        <valueConverter:ChatToStyleConverter x:Key="ChatToStyleConverter" />
    </Window.Resources>

    <Design.DataContext>
        <vm:MainWindowViewModel />
    </Design.DataContext>

    <Window.Styles>
        <Style Selector="Window">
            <Setter Property="BorderThickness" Value="0" />
            <Setter Property="Padding" Value="0" />
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="BorderBrush" Value="Transparent" />
        </Style>
        <Style Selector="TextBox.red:pointerover">
            <Setter Property="Opacity" Value="1" />
        </Style>
    </Window.Styles>

    <Border Name="MainBorder" CornerRadius="1000" Background="Black" Margin="0,0,0,0" Padding="0,0,0,0"
            HorizontalAlignment="Left" VerticalAlignment="Center" Width="100" Height="50">
        <Grid>
            <!-- 图标 -->
            <Image Source="../Assets/ai.png" Name="Logo" HorizontalAlignment="Left" VerticalAlignment="Center"
                   Width="46"
                   Tapped="Logo_OnTapped"
                   RenderOptions.BitmapInterpolationMode="HighQuality"
                   PointerPressed="OnLogoClick"
                   PointerEntered="Logo_OnPointerEntered"
                   PointerExited="Logo_OnPointerExited"
                   Height="46" Margin="0,0,0,0" />

            <!-- 模型选择 -->
            <Popup Name="ModulePopup" IsOpen="False" PlacementTarget="{Binding ElementName=MainBorder}"
                   PlacementMode="Top">
                <StackPanel Margin="5">
                    <Border Background="#1F1F1F" BorderBrush="Black" BorderThickness="1" CornerRadius="12"
                            MaxHeight="400" Width="120">
                        <ScrollViewer Name="ModuleScrollViewer" VerticalScrollBarVisibility="Auto">
                            <ItemsControl CornerRadius="12" ItemsSource="{Binding Modules}" Margin="2">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <Border Margin="5"
                                                Background="{Binding Color}"
                                                PointerExited="OnSelectStackPointerExited"
                                                PointerEntered="OnSelectStackPointerEntered"
                                                PointerPressed="OnSelectStackPointerPressed"
                                                Tag="{Binding GetThis}"
                                                CornerRadius="8">
                                            <!-- 左边显示图标,右边显示名称 -->
                                            <StackPanel Orientation="Horizontal">
                                                <Image
                                                    RenderOptions.BitmapInterpolationMode="HighQuality"
                                                    Source="{Binding Icon, Converter={StaticResource ImageConverter}}"
                                                    HorizontalAlignment="Left"
                                                    Width="20"
                                                    Height="20" />
                                                <TextBlock TextWrapping="Wrap" Width="60" Text="{Binding Title}"
                                                           Margin="5" Foreground="White" />
                                            </StackPanel>
                                        </Border>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </ScrollViewer>
                    </Border>
                </StackPanel>
            </Popup>

            <!-- 静止状态下的搜索按钮 -->
            <Border PointerPressed="SearchBorder_OnPointerPressed"
                    PointerEntered="searchBorder_PointerEnter"
                    PointerExited="OnPointerExited"
                    Name="searchBorder"
                    CornerRadius="1000" Background="#000000" BorderBrush="#FFFFFF"
                    BorderThickness="1" Margin="50,0,0,0" Padding="0,0,0,0" HorizontalAlignment="Left"
                    VerticalAlignment="Center" Width="46" Height="46" Cursor="Hand">
                <Image Source="../Assets/search.png"
                       RenderOptions.BitmapInterpolationMode="HighQuality"
                       HorizontalAlignment="Center" VerticalAlignment="Center"
                       Width="20" Height="20" Margin="0,0,0,0" />
            </Border>

            <!-- 当点击搜索按钮时,显示搜索框 -->
            <TextBox FontSize="20" Name="SearchText" Margin="50,0,0,0" IsVisible="False" Width="0" Height="40"
                     HorizontalAlignment="Left" VerticalAlignment="Center">
                <TextBox.Styles>
                    <Styles>
                        <Style Selector="TextBox">
                            <Setter Property="CornerRadius" Value="0,50,50,0"></Setter>
                        </Style>
                    </Styles>
                </TextBox.Styles>
                <TextBox.Transitions>
                    <Transitions>
                        <DoubleTransition Property="Width" Duration="0:0:0.1" />
                    </Transitions>
                </TextBox.Transitions>
            </TextBox>
            <!-- 消息显示区域 -->
            <Popup x:Name="MessagePopup"
                   IsOpen="False"
                   PlacementTarget="{Binding ElementName=MainBorder}"
                   PlacementMode="Bottom">
                <StackPanel
                    PointerEntered="MessagePopup_OnPointerEntered"
                    PointerExited="MessagePopup_OnPointerExited" Margin="5">
                    <Border Name="MessageBorder"
                            Background="#1F1F1F"
                            BorderBrush="Black"
                            BorderThickness="1"
                            CornerRadius="12"
                            MaxHeight="300">
                        <ScrollViewer Name="ScrollViewer" VerticalScrollBarVisibility="Auto">
                            <ItemsControl ItemsSource="{Binding Messages}" CornerRadius="12" Margin="2">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <StackPanel Margin="5">
                                            <StackPanel.Resources>
                                                <valueConverter:ChatToBackgroundConverter
                                                    x:Key="ChatToBackgroundConverter" />
                                            </StackPanel.Resources>
                                            <Border
                                                Background="{Binding Chat, Converter={StaticResource ChatToBackgroundConverter}}"
                                                CornerRadius="5">

                                                <md:MarkdownScrollViewer
                                                    VerticalAlignment="Stretch"
                                                    MarkdownStyleName="Standard"
                                                    SaveScrollValueWhenContentUpdated="True"
                                                    Markdown="{Binding Message}">
                                                    <md:MarkdownScrollViewer.Styles>

                                                        <Style Selector="ctxt|CCode">
                                                            <Style.Setters>
                                                                <Setter Property="BorderBrush" Value="Green" />
                                                                <Setter Property="BorderThickness" Value="2" />
                                                                <Setter Property="Padding" Value="2" />
                                                                <Setter Property="MonospaceFontFamily" Value="Meiryo" />
                                                                <Setter Property="Foreground" Value="DarkGreen" />
                                                                <Setter Property="Background" Value="LightGreen" />
                                                            </Style.Setters>
                                                        </Style>

                                                        <Style Selector="Border.CodeBlock">
                                                            <Style.Setters>
                                                                <Setter Property="BorderBrush" Value="Blue" />
                                                                <Setter Property="BorderThickness" Value="0,5,0,5" />
                                                                <Setter Property="Margin" Value="5,0,5,0" />
                                                                <Setter Property="Background" Value="LightBlue" />
                                                            </Style.Setters>
                                                        </Style>

                                                        <Style Selector="TextBlock.CodeBlock">
                                                            <Style.Setters>
                                                                <Setter Property="Foreground" Value="DarkBlue" />
                                                                <Setter Property="FontFamily" Value="Meiryo" />
                                                            </Style.Setters>
                                                        </Style>

                                                        <Style Selector="avedit|TextEditor">
                                                            <Setter Property="Background" Value="Gray" />
                                                            <Setter Property="CornerRadius" Value="10"></Setter>
                                                        </Style>

                                                    </md:MarkdownScrollViewer.Styles>
                                                </md:MarkdownScrollViewer>
                                            </Border>
                                        </StackPanel>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </ScrollViewer>
                    </Border>
                </StackPanel>
            </Popup>

        </Grid>
        <Border.Transitions>
            <Transitions>
                <DoubleTransition Property="Width" Duration="0:0:0.2" />
            </Transitions>
        </Border.Transitions>
    </Border>

</Window>

只需要设置无边框并且固定大小。悬浮窗的效果就达到了。

我们看看执行效果

image-20230702133719931

就这样简单的悬浮窗写好了,我们使用一下悬浮窗的搜索功能

image-20230702133757221

这个就是简单的使用效果,对比其他的工具,这个悬浮窗更简洁,并且跨平台和开源。

image-20230702133839454

目前的项目结构。

plugin下面的项目是默认的插件,用于搜索系统文件(未完善)

Gotrays.Suspension.Client则是实际的客户端。

Gotrays.Suspension.PlugIn则是插件定义的接口规范。

Gotrays.Update则是检查更新程序,用于更新主程序。

实现插件

plug-in

插件模块,用于扩展功能。

插件开发

1. 创建插件项目

在解决方案中创建一个类库项目,项目名称以Gotrays.Suspension.PlugIn.开头,例如Gotrays.Suspension.PlugIn.Test。 然后在项目中依赖Gotrays.Suspension.PlugIn类库。

2. 创建插件类

在项目中创建一个类,继承Gotrays.Suspension.PlugIn.PlugInBase类,例如:

代码语言:javascript复制
using Gotrays.Suspension.PlugIn;

public class SystemTools : PlugInBase
{
    public SystemTools()
    {
        Name = "系统搜索";

        // 获取system.png嵌入资源的Stream
        var stream = GetType().Assembly.GetManifestResourceStream("SystemTools.system.png");
        if (stream == null) return;

        // 读取Stream到byte数组
        var bytes = new byte[stream.Length];
        var read = stream.Read(bytes, 0, bytes.Length);
        Icon = bytes;
    }

    // 搜索触发
    public override async Task SearchAsync(string value)
    {
        // 打开系统搜索
        Process.Start("explorer.exe", "search://"   value);

        await Task.CompletedTask;
    }
    
    protected override async Task InitAsync(IServiceCollection services){
        // 插件首次加载时执行
    }
    public override async Task BuilderServiceAsync(IServiceProvider provider)
    {
        // 这里可以得到服务提供者,可以通过服务提供者获取其他服务
    }
    protected override void Selection()
    {
        //  当插件被选中时执行
    }
    
    protected override void UnSelection()
    {
        // 当插件被取消选中时执行
    }
    
    protected override async Task UnloadAsync()
    {
        // 当插件被卸载插件发生
    }
    
}

工具服务会进行自动发现,无需手动注册。 只需要将程序集放置在./plug-in目录下即可。 服务会在一个程序集中发现所有的插件类,并且进行注册。

按照上面的方式非常的简单就集成了插件。

开源地址

Gitee:https://gitee.com/gotrays/gotrays-suspension

Github:https://github.com/239573049/Suspension

0 人点赞