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

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

如何实现悬浮窗?

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

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

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

ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
SystemDecorations="None"

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

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

Height="50"
MaxHeight="50"
Width="{Binding Width}"
MaxWidth="{Binding Width}"

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

接下来给出所有代码,

<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>

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

我们看看执行效果

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

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

目前的项目结构。

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类,例如:

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

技术交流群:737776595

一个跨平台的`ChatGPT`悬浮窗工具的更多相关文章

  1. android桌面悬浮窗仿QQ手机管家加速效果

    主要还是用到了WindowManager对桌面悬浮进行管理. 需要一个火箭的悬浮窗 一个发射台悬浮窗  ,判断火箭是否放到了发射台,如果放上了,则使用AsyTask 慢慢将火箭的图片往上移.结束后., ...

  2. Android仿360手机卫士悬浮窗效果

    请看下图:                         首先是一个小的悬浮窗显示的是当前使用了百分之多少的内存,点击一下小悬浮窗,就会弹出一个大的悬浮窗,可以一键加速.好,我们现在就来模拟实现一下 ...

  3. 微信网页悬浮窗交互效果的web实现

    一.微信的悬浮窗交互效果 微信更新后,发现多了个悬浮窗功能.公众号阅读,网页浏览回退后默认会出现.再点击,可以回到刚才阅读的地方.于是,再也不会遇到回复老婆的信息,再切换回来重新找刚才阅读东西的麻烦了 ...

  4. duilib : 滑动显示的窗口实现以及 悬浮窗 (转载)

    1. vc 判断窗口是否显示  BOOL IsWindowVisible(HWND hWnd); 2.悬浮窗 http://blog.csdn.net/lincyang/article/details ...

  5. Android 桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果

    首先是一个小的悬浮窗显示的是当前使用了百分之多少的内存,点击一下小悬浮窗,就会弹出一个大的悬浮窗,可以一键加速.好,我们现在就来模拟实现一下类似的效果. 先谈一下基本的实现原理,这种桌面悬浮窗的效果很 ...

  6. Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果

    大家好,今天给大家带来一个仿360手机卫士悬浮窗效果的教程,在开始之前请允许我说几句不相干的废话. 不知不觉我发现自己接触Android已有近三个年头了,期间各种的成长少不了各位高手的帮助,总是有很多 ...

  7. Android 悬浮窗、悬浮球开发

    原文:Android 悬浮窗.悬浮球开发 1.权限管理 直接看我另外一篇博客吧,传送门: https://my.oschina.net/u/1462828/blog/1933162 2.Base类Ba ...

  8. android课程表控件、悬浮窗、Todo应用、MVP框架、Kotlin完整项目源码

    Android精选源码 Android游戏2048 MVP Kotlin项目(RxJava+Rerotfit+OkHttp+Glide) Android基于自定义Span的富文本编辑器 android ...

  9. 详解如何实现斗鱼、B站等全局悬浮窗直播小窗口

    最近业务需求需要我们直播返回或者退出直播间时,开一个小窗口在全局继续直播视频,先看效果图. 调研了一下当下主流直播平台,斗鱼.BiliBili等app,都是用WindowManger做的(这个你可以在 ...

  10. 开源一个跨平台运行的服务插件 - TaskCore.MainForm

    本次将要很大家分享的是一个跨平台运行的服务插件 - TaskCore.MainForm,此框架是使用.netcore来写的,现在netcore已经支持很多系统平台运行了,所以将以前的Task.Main ...

随机推荐

  1. [Java/IDE]IDEA运行Java类时报错:Error running 'MainTest': Command line is too long. Shorten command line for MainTest or also for Application default configuration

    报错原因 Java项目启动命令过长 解决方法 点击项目启动配置项 -> shorten command line 选项选择 classpath file 或 java manifest 选项 - ...

  2. 请求被中止: 未能创建 SSL/TLS 安全通道 解决方案

    最近项目改造https,有部分请求出现"请求被中止: 未能创建 SSL/TLS 安全通道". 原因应该是,接口方变更了安全协议,而客户端并未启用该协议. 解决办法自然就是:让客户端 ...

  3. 谈谈selenium中的clear后输入内容异常的处理

    谈谈selenium中的clear后输入内容异常的处理 案例 在线考试项目的登录:http://124.223.31.21:9097/#/ 代码 from selenium import webdri ...

  4. [操作系统] - 进程切换&进程控制

    2.1.6 进程切换 名称解析 进程的上下文(Context) 当一个进程在执行时,CPU的所有寄存器的值.进程的状态以及堆栈中的内容被称为进程的上下文Context 进程的切换(switch) 当内 ...

  5. 这是一篇记录——django-xadmin重新开发记录

    利用下面的代码把django的版本换成和xadmin2适配的版本,注意xadmin最新版本出了3.0但是就是一个纯前端的框架,和之前的版本差异较大. 因为此时距离ddl不到24小时,所以使用旧的版本. ...

  6. 【Redis】Cluster集群

    一.Redis Cluster 工作原理 在引入哨兵机制后,解决了Redis主从架构Master故障时的主从切换问题,保证了Redis服务可用性.但依旧无法解决单机节点出现的写入性能瓶颈(网卡速率.单 ...

  7. 2021-04-17:给定一个整型数组 arr,数组中的每个值都为正数,表示完成一幅画作需要的时间,再 给定 一个整数 num,表示画匠的数量,每个画匠只能画连在一起的画作。所有的画家 并行工作,请

    2021-04-17:给定一个整型数组 arr,数组中的每个值都为正数,表示完成一幅画作需要的时间,再 给定 一个整数 num,表示画匠的数量,每个画匠只能画连在一起的画作.所有的画家 并行工作,请 ...

  8. 2022-01-16:小明手中有n块积木,并且小明知道每块积木的重量。现在小明希望将这些积木堆起来, 要求是任意一块积木如果想堆在另一块积木上面,那么要求: 1.上面的积木重量不能小于下面的积木重量;

    2022-01-16:小明手中有n块积木,并且小明知道每块积木的重量.现在小明希望将这些积木堆起来, 要求是任意一块积木如果想堆在另一块积木上面,那么要求: 1.上面的积木重量不能小于下面的积木重量: ...

  9. 关于SpringBoot AutoConfiguration

    (1)如何导入的自动配置类 首先我们得从@SpringBootApplication注解入手. @SpringBootApplication public class SpringBootDemoAp ...

  10. 2014年蓝桥杯C/C++大学B组省赛真题(奇怪的分式)

    题目描述: 上小学的时候,小明经常自己发明新算法.一次,老师出的题目是:1/4 乘以 8/5 小明居然把分子拼接在一起,分母拼接在一起,答案是:18/45 (参见图1.png)老师刚想批评他,转念一想 ...