├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── app.ico
├── codes
├── filter.aardio
└── hotkey.txt
├── default.aproj
├── dlg
├── aiChat.aardio
├── frmIconFont.aardio
├── frmSearch.aardio
├── frmSetting.aardio
├── images
│ └── input.gif
└── template.aardio
├── lib
├── app
│ ├── filterWindows.aardio
│ ├── hotkey.aardio
│ ├── jab.aardio
│ └── util
│ │ └── _.aardio
├── config.aardio
├── fonts
│ ├── .res
│ │ ├── fontAwesomeSub.ttf
│ │ └── imtip.ttf
│ ├── fontAwesomeSub.aardio
│ └── imtip.aardio
├── style.aardio
├── tsfInput.aardio
└── ui
│ ├── appTrayMenu.aardio
│ ├── colorPicker.aardio
│ └── doublePinyinMenu.aardio
├── main.aardio
└── screenshots
├── ai.gif
├── cn-format.gif
├── cn.gif
├── color.gif
├── copy.gif
├── cpu.png
├── fim.gif
├── hotkey-ai.gif
├── hotkey-save.gif
├── iconfont.gif
├── ime.png
├── imtip-dot.gif
├── imtip.gif
├── menu.png
├── offset.gif
├── padding.gif
├── uwp.gif
└── web.gif
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 |
6 | [*.{aardio,table,aau}]
7 | end_of_line = crlf
8 | charset = utf-8
9 | indent_style = tab
10 | indent_size = 4
11 |
12 | [*.{aau}]
13 | charset = utf-8-bom
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.build/default.main.aardio
2 | /.build/default.Manifest.xml
3 | /.build/update-maker.table
4 | /.build/update-maker.aardio
5 | /.update-files/
6 | dist/
7 | .build/default.init.aardio
8 | .build/mp.lock
9 | /.imtip
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 aardio
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ImTip 智能桌面助手
2 |
3 | 点这里下载 ImTip - 免费开源,仅 832 KB。独立 EXE 无任何外部依赖,兼容 XP,Vista,Win7,Win8,Win10,Win11 ……
4 |
5 | ImTip 提供 [输入跟踪提示](#一输入跟踪提示)、[超级热键(各种桌面应用快速接入 AI)](#二超级热键)、[自定义 AI 助手](#三-ai-助手) 等功能。
6 |
7 | - [超级热键开发指南](https://www.aardio.com/zh-cn/doc/?q=library-guide%2Fstd%2Fkey%2Fhotkey.html)
8 | - [配置输入法状态跟踪提示规则与外观](https://www.aardio.com/zh-cn/doc/?q=library-guide/std/key/ime.stateBar.html)
9 | - [输入法与键盘状态检测原理与规则](https://www.aardio.com/zh-cn/doc/?q=library-guide/std/key/imeState.html)
10 |
11 | ## 一、输入跟踪提示
12 |
13 | 输入跟踪提示通过**在输入光标处显示 2 个简洁的图标** —— 提前知道中英、中英标点、全半角、大小写、多语言键盘布局等所有状态。
14 |
15 | 
16 |
17 | 可以方便地自定义外观方案,例如[单图标方案](https://imtip.aardio.com/#dot-scheme)效果如下:
18 |
19 | 
20 |
21 | **再也不怕按错了!** 保持思考与输入的连续性,避免低头看任务栏或通过其他操作检查输入状态。
22 |
23 |
24 | - 不是只能看中英状态,而是关注更少的图标,了解更多的常用输入法与键盘状态。
25 | - 不是只在切换输入法才显示一次状态,当切换到新的输入位置都会及时地提醒输入法状态,可以自定义显示时长、方式、外观。
26 |
27 |
28 | 
29 |
30 | 有了 ImTip 就可以关掉输入法自带的状态栏,屏幕更干净了,**美滋滋再也不用看右下角** !
31 |
32 | 
33 |
34 | 理论上支持所有输入法,系统自带的微软拼音,微软五笔,小小输入法,搜狗输入法,百度输入法,QQ输入法,谷歌输入法,小鹤输入法,手心输入法 …… 包括我测试的日文、韩文、西班牙语输入法都可以支持 ImTip 。
35 |
36 | ImTip 支持可视化编辑状态提示外观:
37 |
38 | 
39 |
40 | 可将外观方案直接拖入 ImTip.exe 或外观设置窗口快速导入。
41 | 支持用剪贴板直接复制粘贴配置方案代码。
42 |
43 | 
44 |
45 | ImTip **CPU 占用极低**,可以通过设置「跟踪检测速度」调整 CPU 占用:
46 |
47 | 
48 |
49 | 默认有微小延迟 —— 这是程序的主动优化( 并非被动延迟 ),您可以加快「跟踪检测速度」(更丝滑,增加的资源占用仍然是可忽略的)。
50 |
51 | ## 二、超级热键
52 |
53 | ImTip 提供可编程扩展的「超级热键」。
54 | 例如按 Ctrl+$ 打开财务大写、日期时间大写、数学运算工具:
55 |
56 | 
57 |
58 | 超级热键调用 AI 大模型自动编写 aardio 代码:
59 |
60 | 
61 |
62 | 超级热键调调用 AI 大模型在 PowerShell 中写代码
63 |
64 | 
65 |
66 | 超级热键调调用 AI 大模型在记事本中续写与补全
67 |
68 | 
69 |
70 | [超级热键大全 | 教程](https://www.aardio.com/zh-cn/doc/?q=library-guide%2Fstd%2Fkey%2Fhotkey.html)
71 |
72 | ## 三、 AI 助手
73 |
74 | ImTip 提供简洁可定制的 AI 桌面助手。
75 | 可迅速将大模型 API 转换为可用的桌面助手。AI 助手已支持渲染数学公式、代码高亮、一键分享截长屏、自动联网读取文档 …… 等功能。
76 |
77 | 可自定义多个 AI 助手配置,同一会话也可以随时切换不同的大模型。新版 ImTip 已经默认添加了翻译、词典等 AI 助手,并且支持通过超级热键调用这些 AI 助手翻译其他程序中的选区文本。使用旧版本的用户可以在关闭 ImTip 以后删除旧版配置目录 `%localappdata%/aardio/std/imtip` 并重新启用 ImTip 就可以重置为新版默认配置。
78 |
79 | 
80 |
81 | ImTip 也支持在超级热键中快助调用 AI 大模型接口,或者自动调用 AI 会话窗口。启用步骤如下:
82 | 1. 在 ImTip 主界面勾选启用超级热键。
83 | 2. 后点击『编辑超级热键』,在超级热键配置中修改 AI 接口参数。
84 | 
85 | 如果没有看到上面的 AI 示例,只要删除旧版热键配置( hotkey.aardio )再重新打开即可。
86 | 3. 点击保存按钮后热键自动生效。
87 | 
88 |
89 |
90 | ## 托盘菜单
91 |
92 | ImTip 托盘菜单提供快捷启用系统输入法、切换双拼方案等功能。
93 |
94 | 
95 |
96 | ImTip 快捷键:
97 |
98 | 按住 Shift 点击托盘图标可打开 AI 助手。
99 | 接住 Ctrl 点击托盘图标可启用/禁用输入跟踪提示。
100 |
101 | 输入法常用快捷键:
102 | Shift 切换中/英输入;
103 | Ctrl+. 换中/英标点;
104 | Shift+空格 切换全/半角;
105 | Alt+Shift 切换语言
106 |
107 | ## 常见问题
108 |
109 | ### 1. 关于英文键盘
110 |
111 | 有些第三方输入法会安装「中文美式键盘」 - 可能导致不必要的错乱。这个键盘在 Win10 其实已被废弃,建议移除或更改为「英语美式键盘」。Win7/Win10/Win11 可在 ImTip 托盘菜单中禁用启用一次「英语键盘」就可修复该问题。
112 |
113 | ### 2. 管理权限窗口
114 |
115 | ImTip 默认以普通权限启动,以管理权限启动 ImTip.exe —— 才会对其他管理权限窗口生效。以管理权限启动后重新勾选 「允许开机启动」,则开机以管理权限启动( 不会再弹出请求权限弹框 , 注意只有同样在管理权限下启动才能取消此设置 )。
116 |
117 | ### 3. 窗口兼容性
118 |
119 | ImTip 使用了多种不同的接口获取输入位置,但少数任何接口都不支持的应用窗口会退化为取鼠标输入指针位置。
120 |
121 | 在设置界面勾选『启用 java.accessBridge 扩展 』可自动支持 JetBrains 等 Java 程序窗口,一键自动启用,不需要其他手动配置与操作。
122 |
123 | 如果勾选『启用 java.accessBridge 扩展』时自动取消,并且显示 『未启用 java.accessBridge 扩展 』,请检查当前系统是否能正常联网( 此功能需要下载 aardio 扩展库 java.accessBridge )。也可以自行下载 aardio 最新版,然后在 aardio 中运行下面的代码启用 JAB( Java Access Bridge ) :
124 |
125 | ```aardio
126 | import java.accessBridge;
127 | print( java.accessBridge.switch(true) );
128 | ```
129 |
130 | 对于以上方式都不支持的窗口,请参考:[设置兼容窗口类名](https://www.aardio.com/zh-cn/doc/library-guide/std/key/ime.stateBar.html#editorClasses")
131 |
132 | 微信 4.0 已经完美支持 ImTip,不需要其他设置。
133 |
134 | > ImTip 仅在检测到输入框时显示输入状态。即使取消勾选「仅切换输入目标或状态后显示」,在检测不到输入目标的窗口仍然不会显示输入状态(除非所在窗口设置了兼容窗口类名)。
135 |
136 | ### 4. 输入法兼容性
137 |
138 | 请参考:[输入法与键备状态检测原理与规则](https://www.aardio.com/zh-cn/doc/?q=library-guide/std/key/imeState.html)
139 |
140 | - 主流输入法基本都可以支持 ImTip 。
141 |
142 | - 微软自带的所有输入法完美支持 ImTip。
143 |
144 | - 小小输入法完美支持 ImTip。如有问题可使用开源工具 [IMY](https://github.com/aardio/IMY) 卸载重装一次小小输入法就可以了。
145 |
146 | - 小狼毫输入法请安装最新 [nightly build](https://github.com/rime/weasel/releases/tag/latest) 版可支持 ImTip ,可通过 ImTip 托盘菜单启用或禁用输入法悬浮提示
147 |
148 | - 微信输入法、手心输入法、讯飞输入法需要勾选『怪异模式』。注意这三种输入法分别使用不同的『怪异模式』,最好不要同时安装这些有问题的输入法,安装变动后也请重新勾选一次『怪异模式』以更新配置。勾选『怪异模式』则不支持其他正常输入法。
149 |
150 | - 小鹤输入法在英文模式下切换全半角后状态会错乱,按 Shift 切换一次中英模式会恢复正常,可能基于多多的输入法都有类似问题。
151 |
152 | - 个别老旧的输入法会导致其他输入法的状态也变得混乱,卸载有问题的输入法,切换或重新打开窗口可恢复正常。
153 |
154 | ### 5. 启动参数
155 |
156 | - `ImTip.exe *.aardio`
157 | 加载配置方案,或者直接将配置文件拖到 ImTip.exe 上也可以。
158 |
159 | - `ImTip.exe 无参数`
160 | 如果 ImTip 已运行则打开配置窗口,或者直接双击 ImTip.exe 也可以。
161 |
162 | - `ImTip.exe /chat 配置名称 /q 需要立即发送的问题`
163 | 启动 AI 聊天助手会话窗口。配置名称可省略,q 参数也可以省略。
164 | aardio 提供 process.imTip 库可以方便地启动 ImTip 聊天助手,可参考:[超级热键 - 自动调用 AI 会话窗口](https://www.aardio.com/zh-cn/doc/library-guide/std/key/hotkey.html#imtip-ai-chat)。
165 |
166 | - `ImTip.exe /sys`
167 | 启动时不显示主界面。勾选开机启动时设置的这是这个参数。
168 |
169 |
170 | ****
171 |
172 | 本页的动画主要使用 [开源免费,下载体积仅 820 KB 的极简录屏软件 Gif123](https://gif123.aardio.com/) 录制。
173 |
--------------------------------------------------------------------------------
/app.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/app.ico
--------------------------------------------------------------------------------
/codes/filter.aardio:
--------------------------------------------------------------------------------
1 | /*
2 | 保存代码自动生效。
3 | 此代码在 ImTip 主界面线程运行,可调用 aardio 开发环境公共库。
4 | 教程: https://www.aardio.com/zh-cn/doc/library-guide/std/key/ime.stateBar.html
5 | */
6 |
7 | // ImTip 切换到新的独立窗口回调下面的函数。
8 | imeBar.onImeForegroundWindow = function(hwnd){ // imeBar 为 key.ime.stateBar 对象。
9 |
10 | //获取窗口类名
11 | var cls = win.getClass(hwnd);
12 |
13 | //检测类名,支持模式匹配 doc://guide/language/pattern-matching.html
14 | if(string.find(cls,"<禁止类名1>|<禁止类名2>")){
15 |
16 | //返回 false 禁止此窗口提示输入法状态
17 | return false;
18 | }
19 |
20 | if(string.find(cls,"<自定义窗口类名2>")){
21 |
22 | /*
23 | 自定义获取输入光标位置的函数。
24 | 返回值必须与 winex.caret.get 兼容。
25 | 细节请参考 key.ime.stateBar 源码
26 | */
27 | imeBar.getCaretEx = function(hwnd){
28 |
29 | //return caret,hFocus;
30 | }
31 |
32 | return;
33 | }
34 |
35 | //获取线程 ID,进程 ID。
36 | var tid,pid = win.getThreadProcessId(hwnd);
37 |
38 | //获取执行文件路径
39 | var path = process.getPath(pid);
40 | if(!#path) return;//普通权限无法获取管理权限进程文件名
41 |
42 | //转换为不含后缀的文件名
43 | var name = io.splitpath(path).name;
44 |
45 | //禁止提示的窗口
46 | if(IMTIP_DISABLED_APP[name]){
47 | return false;
48 | }
49 | }
50 |
51 | IMTIP_DISABLED_APP = {
52 | "禁止的执行文件名1" = true;
53 | "禁止的执行文件名2" = true;
54 | }
55 |
56 | //扩展托盘菜单
57 | /*
58 | mainForm.onTrayMenu = function(menu){
59 | menu.add("打开 AI 助手",function(){
60 | import process.imTip;
61 | process.imTip(chat="");
62 | });
63 |
64 | menu.add();//分隔线
65 | }
66 | */
67 |
68 | //显示提示窗口前触发下面的事件
69 | /*
70 | imeBar.onImeStateChange = function(hFocus,imeX,imeY,openNative,symbolMode,text,iconText){
71 | return true;//允许提示
72 | }
73 | */
--------------------------------------------------------------------------------
/codes/hotkey.txt:
--------------------------------------------------------------------------------
1 | import win.ui;
2 | /*开发环境(请勿修改){{*/
3 | if(_STUDIO_INVOKED && ...!="ImTip"){
4 | var winform = win.form(text="超级热键";right=759;bottom=469)
5 | winform.add(
6 | edit={cls="edit";left=29;top=24;right=727;bottom=420;db=1;dl=1;dr=1;dt=1;edge=1;multiline=1;z=1}
7 | )
8 | winform.show();
9 | }
10 | /*}}*/
11 |
12 | /*
13 | 保存此文件,自动更新 ImTip 热键配置。
14 | 教程: https://www.aardio.com/zh-cn/doc/?q=library-guide/std/key/hotkey.html
15 | */
16 |
17 | import fsys;
18 | import ide;
19 | import winex;
20 | import win.dlg.chineseNumber;
21 | import mouse;
22 | import key;
23 | import key.ime;
24 | import key.hotkey;
25 |
26 | //创建超级热键
27 | superHotkey = key.hotkey();//不可修改或删除这句代码
28 |
29 | superHotkey.loadTable({
30 |
31 | //大写金额与数字输入工具
32 | ["Ctrl+$"] = function(hFocus){
33 | win.dlg.chineseNumber().show();
34 | };
35 |
36 | // 按 Ctrl+Shift+F1 调用沉浸式 AI 助手,务必修改下面代码中的 API 接口密钥。
37 | ["Ctrl+Shift+F1"] = function(hFocus){
38 | /*智能续写与补全{{*/
39 |
40 | //创建多线程以执行耗时操作,以避免阻塞键盘钩子消息导致热键失效。
41 | thread.invoke(
42 | function(winform,hFocus){
43 | import web.rest.aiChat;
44 | var aiClient = web.rest.aiChat(
45 | key = '\0\1\96'; //接口密钥
46 | url = "https://ai.aardio.com/api/v1/"; //接口地址
47 | model = "imtip";//模型名称首字符为 @ 则使用 Anthropic 接口
48 | temperature = 0.1;//温度
49 | maxTokens = 2048,//最大回复长度
50 | )
51 |
52 | //import java.accessBridge;//支持 Java 程序窗口,例如 JetBrains 系列
53 | import winex.editor;
54 | import win.version;
55 | import key;
56 |
57 | //获取当前文本输入窗口光标插入点前后的文本。
58 | var leftText,rightText = winex.editor.getText2(true);
59 | if(!#leftText) return;
60 |
61 | //创建 AI 会话消息队列
62 | var msg = web.rest.aiChat.message();
63 |
64 | //让 AI 更聪明的一个方法就是清楚表达背景、需求、上下文环境,消耗更多 token 得到更多回报。
65 | msg.system(`
66 | ## 背景
67 |
68 | 当前时间为:` + tostring(..time()) + `
69 | 当前操作系统为:` + ..win.version.format() + `;
70 | 当前进程启动文件为:"` + (winex.editor.getPath() : "") + `"
71 | 当前窗口标题为:"` + (winex.editor.getTitle() : "") + `"
72 |
73 | 用户当前输入光标插入点在"""前置文本"""与"""后置文本"""中间。
74 |
75 | ## 角色
76 |
77 | 你是智能续写与补全助手。
78 |
79 | ## 任务
80 |
81 | 分析上下文("""前置文本"""与"""后置文本""")插入新的内容。
82 |
83 | ## 要求
84 |
85 | - 你需要根据当前操作系统、当前进程的启动文件名、窗口标题、上下文推测用户的输入环境与输入目的。
86 | - 如果生成的是代码则直接回复代码, 不要放进 Markdown 代码块中
87 | - 请直接返回插入的内容,回复不要包含前置文本或后置文本`)
88 |
89 | msg.prompt(`
90 |
91 | ## 前置文本:
92 |
93 | console.log("Hello,
94 |
95 | ## 后置文本:
96 |
97 | ;//在控制台输出一句话
98 |
99 | ## 任务
100 |
101 | 分析上下文("""前置文本"""与"""后置文本""")插入新的内容
102 |
103 | `)
104 |
105 | //利用小样本学习,以 AI 助手的角色教它一遍,胜过写千万句提示词
106 | msg.assistant(`world!")`);
107 |
108 | //下面是真正的问题。
109 | msg.prompt("
110 |
111 | ## 前置文本
112 |
113 | "+leftText+"
114 |
115 | ## 后置文本
116 |
117 | "+(rightText:"")+"
118 |
119 | ## 任务
120 |
121 | 请分析前置文本与后置文本,并在前置文本与后置文本之间插入新的内容。
122 |
123 | ## 要求
124 |
125 | - 请直接返回插入的内容,回复不要包含前置文本或后置文本
126 | - 如果生成的是代码则直接回复代码, 不要放进 Markdown 代码块中 。
127 | ")
128 |
129 | if(string.find(leftText,"/>|<# >||<#:>\N*[ \t]*$")){
130 | winex.editor.sendString('\n')
131 | }
132 |
133 | import winex.loading;
134 | var loading = winex.loading("Thinking",hFocus) //进度动画
135 |
136 | var ok,err = aiClient.messages(msg,function(delta,reasoning){
137 |
138 | //如果是推理模型,显示推理过程,推理过程放到 里是错误的做法不处理
139 | if(reasoning){
140 | return loading.thinking(reasoning);;
141 | }
142 |
143 | if(loading.isCanceled()){
144 | return false; //用户已取消(关闭进度动画窗口)
145 | }
146 |
147 | //以打字方式逐步输出 AI 回复的增量文本到目标输入框。
148 | //关于发送文本 doc://library-guide/std/key/sendString.html
149 | winex.editor.sendString(delta)
150 | } );
151 |
152 | if(err){
153 | winex.editor.sendString(err)
154 | }
155 | },winform,hFocus
156 | )
157 | /*}}*/
158 | };
159 |
160 | ["ctrl+Shift+#"] = function(){
161 |
162 | import winex.selection;
163 | var txt = winex.selection.get(true);
164 | if(!#txt) return true;//选区文本为空,继续发送按键
165 |
166 | txt = string.trim(txt);
167 | if(!#txt) return true;
168 |
169 | var words = string.splitEx(txt,"\s");
170 |
171 | return function(){
172 | import process.imTip;
173 |
174 | if(#words>1 || (..string.find(txt,":") && ..string.len(txt)>4 ) ){
175 | process.imTip(
176 | chat = "翻译",// AI 助手配置名称
177 | q = "请翻译:" + txt
178 | )
179 | }
180 | else{
181 | process.imTip(
182 | chat = "词典",// AI 助手配置名称
183 | q = txt
184 | )
185 |
186 | ..wmPlayer := com.CreateObject("WMPlayer.OCX");
187 | ..wmPlayer.url = "https://dict.youdao.com/dictvoice?audio="+txt+"&type=2"
188 | }
189 | }
190 | }
191 | })
192 |
193 | import winex.candidate;
194 | import key.number;
195 |
196 | //中文模式支持数字后输入小数点等分隔符
197 | superHotkey.onKeyDown = function(vk){
198 |
199 | if(key.getStateX("CTRL") || key.getStateX("ALT") ) return;
200 |
201 | if( winex.candidate.visible() ) {
202 | superHotkey.prevKeyCode = null;
203 | return;
204 | }
205 |
206 | var sep = key.number.getSeparator(vk);
207 |
208 | if(sep){
209 | if( key.number.is(superHotkey.prevKeyCode)){
210 | key.sendString( sep );
211 |
212 | return true;
213 | }
214 | }
215 |
216 | if(vk!=0xA0/*_VK_LSHIFT*/ && vk!=0xA1/*_VK_RSHIFT*/){
217 | superHotkey.prevKeyCode = vk;
218 | }
219 | }
220 |
221 | //启动消息循环
222 | win.loopMessage();//不可修改或删除这句代码
223 |
--------------------------------------------------------------------------------
/default.aproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/dlg/aiChat.aardio:
--------------------------------------------------------------------------------
1 | import fonts.imtip;
2 | import fonts.fontAwesomeDlg;
3 | import fonts.fontAwesomeSub;
4 | import config;
5 | import win.ui;
6 | /*DSG{{*/
7 | var winform = win.form(text="ImTip - AI 智能助手";right=759;bottom=620;bgcolor=0xFAF9F8;border="none")
8 | winform.add(
9 | bk={cls="bk";text="ImTip - AI 智能助手";left=22;top=5;right=213;bottom=20;align="left";dl=1;dt=1;z=14};
10 | bkPrompt={cls="plus";left=5;top=495;right=754;bottom=617;bgcolor=0xFFFFFF;border={color=0xFF800000;radius=12};clip=1;clipBk=false;db=1;disabled=1;dl=1;dr=1;z=2};
11 | bkTitle={cls="bk";left=0;top=0;right=764;bottom=27;bgcolor=0xDBDAD9;dl=1;dr=1;dt=1;forecolor=0x5C5B5B;linearGradient=0;z=3};
12 | btnClear={cls="plus";text="清除";left=589;top=588;right=655;bottom=618;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;dr=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF014';notify=1;textPadding={left=25};z=6};
13 | btnCopy={cls="plus";text="复制";left=369;top=588;right=433;bottom=618;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;disabled=1;dr=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='imtip');padding={left=8}};iconText='\uF0C5';notify=1;textPadding={left=25};z=11};
14 | btnSearch={cls="plus";text="联网";left=8;top=588;right=67;bottom=618;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;dl=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF26B';notify=1;textPadding={left=25};z=15};
15 | btnSend={cls="plus";text="问 AI";left=660;top=588;right=732;bottom=618;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;dr=1;font=LOGFONT(h=-13);iconColor=5724159;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF0AA';notify=1;textPadding={left=25};z=4};
16 | btnSetting={cls="plus";text="设置";left=73;top=588;right=140;bottom=618;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;dl=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF013';notify=1;textPadding={left=25};z=7};
17 | btnSnap={cls="plus";text="分享";left=302;top=588;right=366;bottom=618;align="left";bgcolor=0xFFFFFF;color=0x3C3C3C;db=1;disabled=1;dr=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF030';notify=1;textPadding={left=25};z=12};
18 | cmbAgent={cls="combobox";left=145;top=592;right=269;bottom=614;db=1;dl=1;edge=1;items={};mode="dropdownlist";z=16};
19 | editMaxTokens={cls="edit";left=509;top=593;right=552;bottom=616;align="right";db=1;dr=1;edge=1;z=9};
20 | editPrompt={cls="richedit";left=7;top=501;right=750;bottom=585;autohscroll=false;bgcolor=0xFFFFFF;db=1;dl=1;dr=1;link=1;multiline=1;vscroll=1;z=5};
21 | spinMaxTokens={cls="spin";left=553;top=594;right=573;bottom=616;db=1;dr=1;z=8};
22 | splitter={cls="splitter";left=-2;top=490;right=760;bottom=495;bgcolor=0xD1D1D1;db=1;dl=1;dr=1;horz=1;z=13};
23 | static={cls="static";text="回复长度:";left=434;top=591;right=500;bottom=614;align="right";bgcolor=0xFFFFFF;center=1;clipch=1;db=1;dr=1;z=10};
24 | wndBrowser={cls="custom";text="自定义控件";left=5;top=27;right=753;bottom=489;ah=1;db=1;dl=1;dr=1;dt=1;z=1}
25 | )
26 | /*}}*/
27 |
28 | var aiChatConfig = ..table.assign({},config.aiChat);
29 |
30 | if(config.aiChat.itemNames){
31 | winform.cmbAgent.items = config.aiChat.itemNames;
32 | }
33 |
34 | if(config.aiChat.selItem){
35 | winform.cmbAgent.selIndex = config.aiChat.selItem;
36 | }
37 |
38 | if(_ARGV.chat){
39 |
40 | if(#_ARGV.chat && config.aiChat.itemNames){
41 | var selIndex = table.find(config.aiChat.itemNames,_ARGV.chat);
42 | if(selIndex){
43 | winform.cmbAgent.selIndex = selIndex;
44 |
45 | var configItem = config.aiChat.itemData[selIndex];
46 | if(configItem){
47 | aiChatConfig.proxy = null;
48 | table.assign(aiChatConfig,configItem);
49 | }
50 | }
51 | }
52 | }
53 |
54 | //创建显示聊天消虑的 Web 浏览器窗口
55 | import web.form.chat;
56 | var wb = web.form.chat(winform.wndBrowser);
57 | wb.enableKatex(config.aiChat.katex);
58 |
59 | //清除上下文
60 | var resetMessages = function(){
61 |
62 | wb.clear();
63 |
64 | if(#aiChatConfig.systemPrompt){
65 | //输入系统提示词
66 | if( (aiChatConfig.model && ..string.startWith(aiChatConfig.model,"aardio")) ||
67 | string.find(aiChatConfig.systemPrompt,"<@@aardio@>|<@@imtip@>|<超级热键>") ){
68 | wb.chatMessage.aardioSystem(aiChatConfig.systemPrompt);
69 | }
70 | else {
71 | wb.system( aiChatConfig.systemPrompt)
72 | }
73 | }
74 | elseif(aiChatConfig.model && ..string.startWith(aiChatConfig.model,"aardio")){
75 | wb.chatMessage.aardioSystem();
76 | }
77 | else {
78 | wb.system("你是 ImTip 智能 AI 助手,请用中文回答问题")
79 | }
80 |
81 | wb.aiSearched = null;
82 | }
83 |
84 | resetMessages();
85 |
86 | winform.cmbAgent.onOk = function(){
87 | if(winform.cmbAgent.selIndex){
88 | var configItem = config.aiChat.itemData[winform.cmbAgent.selIndex];
89 | if(configItem){
90 | aiChatConfig.proxy = null;
91 | table.assign(aiChatConfig,configItem);
92 |
93 | if(!wb.started()){
94 | resetMessages();
95 | }
96 | }
97 | }
98 | }
99 |
100 | winform.btnClear.oncommand = function(id,event){
101 | resetMessages();//清除聊天上下文
102 | winform.editPrompt.setFocus();
103 | }
104 |
105 | winform.editPrompt.enablePopMenu(function(){
106 | return {
107 |
108 | { '问 AI(发送)\tCtrl+Enter'; function(id){
109 | winform.btnSend.oncommand();
110 | }; 0};
111 |
112 | { /*分隔线*/ };
113 |
114 | { "导出对话"; function(id){
115 | import fsys.dlg;
116 | var path = fsys.dlg.save("*.jsonl|*.jsonl|*.json|*.json||","导出对话到 *.jsonl");
117 | if(!path) return;
118 |
119 | var file,err = io.file(path,"w+b");
120 | if(!file) return winform.msgboxErr(err);
121 |
122 | var chatMessage = wb.chatMessage;
123 |
124 | if(..string.endWith(path,".jsonl",true)){
125 | for(i=1;#chatMessage;1){
126 | var msg = chatMessage[i]
127 | var line = JSON.stringify(msg,false,false);
128 | line = string.crlf(line,"")
129 | file.write(line,'\r\n');
130 | }
131 | }
132 | else {
133 | file.write((JSON.stringifyArray(chatMessage,true,false)));
134 | }
135 |
136 | file.close();
137 |
138 | }; #wb.chatMessage<2 ? 1/*_MF_GRAYED*/ : 0};
139 |
140 | { '导入对话'; function(id){
141 | import fsys.dlg;
142 | var path = fsys.dlg.open("*.jsonl|*.jsonl|*.json|*.json||","自 jsonl 或 json 文件导入对话");
143 | if(!path) return;
144 |
145 | wb.importChatMessages(path);
146 | }; winform.btnSend.text == "停止" ? 1/*_MF_GRAYED*/ : 0};
147 |
148 | { /*分隔线*/ };
149 |
150 | (win.getStyleEx(winform.hwnd, 8/*_WS_EX_TOPMOST*/) & 8)
151 | ? ( { '取消置顶'; function(id){
152 | win.setTopmost(winform.hwnd,false)
153 | } } )
154 | : ( { '置顶窗口'; function(id){
155 | win.setTopmost(winform.hwnd)
156 | } } )
157 | }
158 | } );
159 |
160 | wb.BeforeNavigate2 = function( pDisp, url, flags, targetFrame, postData, headers, cancel ) {
161 |
162 | if( string.match(url,"/dictvoice\?audio=") || ..string.endsWith(url,".mp3") ) {
163 | ..wmPlayer := com.CreateObject("WMPlayer.OCX");
164 | ..wmPlayer.url = url;
165 | }
166 | elseif(..string.match(url,"\.<@@jsonl@>|<@@json@>$")){
167 | winform.setTimeout(
168 | function(){
169 | wb.importChatMessages(url)
170 | }
171 | );
172 | }
173 | else {
174 | ..raw.execute(url);
175 | }
176 |
177 | return url, flags, targetFrame, postData, headers, true;
178 | }
179 |
180 | winform.onDropFiles = function(files){
181 | var path = files[1];
182 | if(..string.match(path,"\.<@@jsonl@>|<@@json@>$")){
183 | wb.importChatMessages(path);
184 | }
185 | }
186 |
187 | wb.importChatMessages = function(path){
188 | var json = string.load(path);
189 | if(!#json){
190 | return winform.msgboxErr("无效对话数据")
191 | }
192 |
193 | var messages;
194 |
195 | if(..string.endsWith(path,".jsonl",true)){
196 | messages = JSON.ndParse(json);
197 | }
198 | else {
199 | messages = JSON.parse(json);
200 | }
201 |
202 | if(!#messages){
203 | return winform.msgboxErr("无效对话数据")
204 | }
205 |
206 | wb.clear();
207 | wb.aiSearched = null;
208 |
209 | for(i=1;#messages;1){
210 | var msg = messages[i];
211 | if(type(msg.content)!="string"){
212 |
213 | if(msg[["role"]]!="user"){
214 | resetMessages();
215 | return winform.msgboxErr("对话数据格式错误,role 字段的值不是 user 则 content 字段必须是字符串!")
216 | }
217 | elseif(!..table.isArray(msg.content)){
218 | return winform.msgboxErr("对话数据格式错误,content字段只能是字符串或数组!")
219 | }
220 | }
221 |
222 | if(type(msg.content)!="string"){
223 |
224 | if(msg[["role"]]!="user"){
225 | resetMessages();
226 | return winform.msgboxErr("对话数据格式错误,role 字段的值不是 user 则 content 字段必须是字符串!")
227 | }
228 | elseif(!..table.isArray(msg.content)){
229 | return winform.msgboxErr("对话数据格式错误,content 字段只能是字符串或数组!")
230 | }
231 | }
232 |
233 | if(msg[["role"]]=="system"){
234 | wb.system(msg.content)
235 | }
236 | elseif(msg[["role"]]=="user"){
237 | wb.prompt(msg.content)
238 | }
239 | elseif(msg[["role"]]=="assistant"){
240 | wb.assistant(msg.content)
241 | wb.assistant(null)
242 | }
243 | else {
244 | //resetMessages();
245 | //return winform.msgboxErr("对话数据格式错误,role 字段必须是 system,user,assistant 之一。")
246 | }
247 | }
248 | }
249 |
250 |
251 | winform.splitter.origTop = winform.splitter.top;
252 |
253 | import thread.event;
254 | var eventStop = thread.event();
255 |
256 | //响应按键事件,输入用户提示词
257 | import inet.url;
258 | winform.btnSend.oncommand = function(id,event){
259 |
260 | if(winform.btnSend.text == "停止"){
261 | eventStop.set();
262 |
263 | winform.btnSend.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'}
264 | return;
265 | }
266 |
267 | var prompt = winform.editPrompt.text;
268 | if(!#prompt){
269 | wb.errorMessage("请先输入问题。");
270 | winform.editPrompt.setFocus();
271 | return;
272 | }
273 |
274 | var tApiUrl = aiChatConfig.url ? inet.url.split(aiChatConfig.url);
275 | if(!tApiUrl){
276 | wb.errorMessage(`错误的接口网址,;点这里重新设置`)
277 | winform.editPrompt.setFocus();
278 | return;
279 | }
280 |
281 | //输入 AI 提示词
282 | wb.prompt( prompt );
283 | winform.editPrompt.text = "";
284 |
285 | //按钮显示等待动画
286 | winform.btnSend.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'}
287 | winform.btnClear.disabled = true;
288 | winform.btnSnap.disabled = true;
289 |
290 | wb.limit = config.aiChat.msgLimit;
291 |
292 | if(wb.aiSearched
293 | || (aiChatConfig.model && ( ..string.endWith(aiChatConfig.model,":online") || ..string.startWith(aiChatConfig.model,"aardio") ) )
294 | ){
295 |
296 | }
297 | elseif( (config.search[["mode"]]=="exa" && config.search.exa )
298 | ||(config.search[["mode"]]=="tavily" && config.search.tavily ) ){
299 |
300 | wb.showLoading("正在联网搜索");
301 |
302 | import web.turndown;
303 | var ok = thread.invokeAndWait( function(wb,prompt,searchConfig){
304 | import web.rest.jsonClient;;
305 |
306 | if(searchConfig.mode=="exa"){
307 | //导入 Exa 索接口
308 | var exaClient = web.rest.jsonClient();
309 | exaClient.setHeaders({ "x-api-key": searchConfig.exa.key} )
310 | var exa = exaClient.api("https://api.exa.ai/");
311 |
312 | //搜索
313 | var searchData,err = exa.search({
314 | query: prompt,
315 | contents={text= true},
316 | numResults: searchConfig.exa.count || 2,
317 | includeDomains: searchConfig.exa.includeDomains,
318 | excludeDomains: !searchConfig.exa.includeDomains ? searchConfig.exa.excludeDomains : null,
319 | type:"keyword" //一般 keyword 搜索就够了(价格低一些)
320 | })
321 |
322 | var ret = searchData[["results"]]
323 | if(ret){
324 | wb.url(ret);
325 | return true;
326 | }
327 | elseif(err){
328 | wb.errorMessage(err);
329 | }
330 | }
331 | elseif(searchConfig.mode=="tavily"){
332 | var http = web.rest.jsonClient();
333 | http.setAuthToken(searchConfig.tavily.key);
334 | var tavily = http.api("https://api.tavily.com");
335 |
336 | //搜索
337 | var searchData,err = tavily.search({
338 | query = prompt,
339 | includeDomains = searchConfig.exa.includeDomains,
340 | excludeDomains: !searchConfig.exa.includeDomains ? searchConfig.exa.excludeDomains : null,
341 | max_results = searchConfig.exa.count || 2 //返回的搜索结果数量,不必要太多,前两三条就可以了
342 | })
343 |
344 | //将搜索结果添加到系统提示词
345 | var ret = searchData[["results"]]
346 | if(ret){
347 | wb.url(ret);
348 | return true;
349 | }
350 | elseif(err){
351 | wb.errorMessage(err);
352 | }
353 | }
354 | },wb,prompt,config.search)
355 |
356 | if(!ok){
357 |
358 | winform.btnSend.disabledText = null;
359 | winform.btnClear.disabled = false;
360 | winform.btnSnap.disabled = false;
361 | winform.chkFix.disabled = false;
362 |
363 | return;
364 | }
365 |
366 | wb.aiSearched = true;
367 | wb.showLoading("正在思考")
368 | }
369 |
370 | var knowledge = ""
371 | prompt = string.replace(prompt,`(https?\://[^\s()"']+)`,
372 | function(url){
373 |
374 | if(!..string.match(url,"<@@.js@>|<@@.css@>|<@@.jpg@>|<@@.png@>|<@@.gif@>|<@@.webp@>$") ){
375 |
376 | wb.showLoading("正在读取与转换网页数据:"+url)
377 |
378 | import web.turndown;
379 | var md,err = web.turndown.httpGet(url)
380 | if(!#md) return;
381 |
382 | md = '\r\n\r\n用户输入的参考网址:' + url
383 | + '\r\n\r\n下面是自该网址获取的' +(err?" Markdown ":"文本")+'格式内容:'
384 | + '\r\n\r\n' + md +'\r\n\r\n------------------------\r\n\r\n'
385 |
386 | knowledge = knowledge ++ md;
387 | }
388 | });
389 |
390 | if(#knowledge){
391 | wb.system(knowledge)
392 |
393 | if(string.match(prompt,`^\s*(https?\://[^\s()"']+)\s*$`)){
394 | prompt = "解读分析与总结要点 " + prompt;
395 | }
396 | }
397 |
398 | config.aiChat.maxTokens = winform.spinMaxTokens.pos;
399 |
400 | winform.splitter.splitAt(winform.splitter.origTop+80);
401 |
402 | eventStop.reset();
403 |
404 | var loading = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250' }
405 | winform.btnSend.disabledText = null;
406 | winform.btnSend.text = "停止"
407 | winform.btnSend.reduce(loading,function(value,index){
408 | if(value) winform.btnSend.iconText = value;
409 | return 150;
410 | } )
411 |
412 | //创建多线程向服务端发送请求
413 | thread.invoke(
414 | function(wb,config,eventStop){
415 |
416 | for(k,v in config){
417 | if(v==="")config[k] = null;
418 | }
419 |
420 | config = table.mix(config,{
421 | url = "https://ai.aardio.com/api/v1/";
422 | key = '\0\1\96';
423 | model = "imtip";
424 | temperature = 0.5;
425 | });
426 |
427 | //导入调用 HTTP 接口的 REST 客户端
428 | import web.rest.aiChat;
429 |
430 | config.userAgent = "Mozilla/5.0 (Windows NT "+ _WIN_VER_MAJOR +"."+_WIN_VER_MINOR+"; ImTip) like Gecko";
431 | var client = web.rest.aiChat(config);
432 |
433 | client.referer = "https://imtip.aardio.com";
434 | client.setHeaders({ "X-Title":"ImTip"});
435 |
436 | if(type(config.extraParameters)==="table"){
437 | client.extraParameters = config.extraParameters;
438 | }
439 |
440 | var ok,err = client.messages(wb.chatMessage,function(deltaText,deltaReasoning){
441 | if(eventStop.wait(0)){
442 | return false;
443 | }
444 |
445 | if(#deltaReasoning){
446 | wb.showThinking(deltaReasoning);
447 | return;
448 | }
449 |
450 | wb.assistant(deltaText);
451 | } );
452 |
453 | if(err){
454 | //获取错误对象(解析 JSON 格式的错误信息)
455 | var errObject = client.lastResponseError()
456 | if(errObject[["error"]][["type"]] == "authentication_error" ){
457 | wb.errorMessage(`API 密钥错误!点这里获取密钥, 点这里设置新密钥`)
458 | }
459 | else {
460 | wb.errorMessage(err)
461 | }
462 | }
463 | elseif(!ok){
464 | wb.errorMessage("错误代码:" + ( client.lastStatusCode : "未知 " + (client.lastResponseString() || "")))
465 | }
466 | elseif(config.usage){
467 | var last = client.lastResponseObject()
468 |
469 | if(last){
470 | var out = ""
471 |
472 | var usage = last.usage || last["amazon-bedrock-invocationMetrics"]
473 | if(usage){
474 | var cTokens,pTokens,cacheTokens;
475 | if(client.apiMode=="aliyun"){
476 | usage = usage[["models"]][[1]];
477 | cTokens = usage.output_tokens
478 | pTokens = usage.input_tokens;
479 | }
480 | else {
481 | cTokens = usage.completion_tokens || usage["outputTokenCount"]
482 | pTokens = usage.prompt_tokens || usage.inputTokenCount || usage.input_tokens;
483 | cacheTokens = usage.prompt_cache_hit_tokens
484 | }
485 |
486 | if(cTokens){
487 | out = out + '回复 tokens:' + ..math.size64(cTokens).format() + "
"
488 | }
489 |
490 | if(pTokens){
491 | out = out + '提示 tokens:' + ..math.size64(pTokens).format() + "
"
492 | }
493 |
494 | if(cacheTokens){
495 | out = out + '缓存 tokens:' + ..math.size64(cacheTokens).format() + "
"
496 | }
497 |
498 | if( string.match( config.url,"<@@https://api.deepseek.com/v1@>" )){
499 |
500 | if( config.model!="deepseek-reasoner"){
501 | if( time() < time("2025/02/09 00:00:00")){
502 | out = out + '本次费用:'
503 | + math.round(((pTokens-cacheTokens)*1+cacheTokens*0.1+cTokens*2)/1000000,3)
504 | + " 元
"
505 | }
506 | else {
507 | out = out + '本次费用:'
508 | + math.round(((pTokens-cacheTokens)*2+cacheTokens*0.5+cTokens*8)/1000000,3)
509 | + " 元
"
510 | }
511 | }
512 | else {
513 | out = out + '本次费用:'
514 | + math.round(((pTokens-cacheTokens)*4+cacheTokens*1+cTokens*16)/1000000,3)
515 | + " 元
"
516 | }
517 |
518 | }
519 | elseif( string.match( config.url,"<@@https://openrouter.ai/api/v1@>" )){
520 | if( config.model=="anthropic/claude-3.5-sonnet"){
521 | out = out + '本次费用:'
522 | + math.round((pTokens*3+cTokens*15)/1000000,3)
523 | + " 美元
"
524 | }
525 | elseif( ..string.endWith(config.model,":free") ){
526 | out = out + "本次费用: 0 美元
"
527 | }
528 | }
529 | elseif( string.match( config.url,"<@@https://api.siliconflow.cn/v1@>" )){
530 |
531 | if( config.model=="deepseek-ai/DeepSeek-V3"){
532 | if( time() < time("2025/02/09 00:00:00")){
533 | out = out + '本次费用:'
534 | + math.round((pTokens*1+cTokens*2)/1000000,3)
535 | + " 元
"
536 | }
537 | else {
538 | out = out + '本次费用:'
539 | + math.round((pTokens*2+cTokens*8)/1000000,3)
540 | + " 元
"
541 | }
542 | }
543 | elseif( config.model == "deepseek-ai/DeepSeek-R1") {
544 | out = out + '本次费用:'
545 | + math.round((pTokens*4+cTokens*16)/1000000,3)
546 | + " 元
"
547 | }
548 | }
549 | }
550 |
551 | if(last.error){
552 | wb.errorMessage( last.error[["message"]] || ..JSON.stringify(last.error,true,false) )
553 | }
554 | else {
555 | wb.errorMessage(#out ? out : "模型未提供 token 用量")
556 | }
557 | }
558 | else {
559 | wb.errorMessage("模型未提供 token 用量")
560 | }
561 | }
562 | },wb,aiChatConfig,eventStop//将参数传入线程
563 | )
564 |
565 | winform.btnCopy.disabled = false;
566 | winform.editPrompt.setFocus();
567 | }
568 |
569 | //在 AI 回复结束后回调此函数
570 | wb.onWriteEnd = function(){
571 | winform.btnSend.disabledText = null;
572 | winform.btnSend.reduce(false);
573 |
574 | winform.btnSend.text = "问 AI";
575 | winform.btnSend.iconText = '\uF0AA';
576 |
577 | winform.btnClear.disabled = false;
578 | winform.btnCopy.disabled = false;
579 | winform.btnSnap.disabled = false;
580 | winform.editPrompt.setFocus();
581 | }
582 |
583 | //导出 aardio 函数到网页 JavaScript 中。
584 | wb.external = {
585 | updateApiKey = function(){
586 | winform.btnSetting.oncommand();
587 | }
588 | }
589 |
590 | import key;
591 | import win.clip;
592 | winform.btnCopy.oncommand = function(id,event){
593 | var md = wb.lastMarkdown();
594 | if(!#md) return winform.msgboxErr("消息为空。");
595 |
596 | if(key.getState("CTRL")){
597 |
598 | var found;
599 | for indent,_,code in string.gmatch(md,"!\N([ \t]*)(```+)<\w+>(.+?)!\N\s*\2![^`\S]") {
600 |
601 | if(#indent){
602 | text = string.replace(text,"\n+"+indent,'\n');
603 | }
604 |
605 | win.clip.write( code );
606 | found = true;
607 | }
608 |
609 | if(!found){
610 | return winform.msgboxErr("没有找到代码块。");
611 | }
612 | }
613 | else{
614 | win.clip.write( md )
615 | }
616 |
617 | winform.btnCopy.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250';text=''}
618 | thread.delay(600);
619 | winform.btnCopy.disabledText = null;
620 | winform.editPrompt.setFocus();
621 | }
622 |
623 | //设置接口地址与 API 令牌的窗口
624 | winform.btnSetting.oncommand = function(id,event){
625 | var frmSetting = win.form(text="ImTip - 设置 AI 聊天助手";right=686;bottom=590;border="dialog frame";exmode="none";max=false;min=false;mode="popup")
626 | frmSetting.add(
627 | btnAdd={cls="plus";left=18;top=473;right=52;bottom=503;align="left";color=0x3C3C3C;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF067';notify=1;textPadding={left=25};z=6};
628 | btnEdit={cls="plus";left=93;top=473;right=127;bottom=503;align="left";color=0x3C3C3C;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF044';notify=1;textPadding={left=25};z=8};
629 | btnRemove={cls="plus";left=56;top=473;right=90;bottom=503;align="left";color=0x3C3C3C;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF1F8';notify=1;textPadding={left=25};z=7};
630 | btnSave={cls="button";text="更新配置";left=489;top=531;right=659;bottom=576;z=27};
631 | chkKatex={cls="checkbox";text=" 解析数学公式";left=55;top=526;right=182;bottom=546;z=24};
632 | chkUsage={cls="checkbox";text="显示 token 计数";left=185;top=526;right=349;bottom=546;z=25};
633 | editApiKey={cls="edit";left=281;top=68;right=662;bottom=95;edge=1;multiline=1;z=18};
634 | editApiUrl={cls="combobox";left=281;top=31;right=662;bottom=57;edge=1;hscroll=1;items={};mode="dropdown";z=17};
635 | editExtraParameters={cls="edit";left=281;top=215;right=662;bottom=242;edge=1;multiline=1;z=22};
636 | editModel={cls="combobox";left=281;top=106;right=662;bottom=132;clip=1;edge=1;items={};mode="dropdown";vscroll=1;z=19};
637 | editProxy={cls="edit";left=281;top=178;right=662;bottom=205;edge=1;z=21};
638 | editSystemPrompt={cls="edit";left=281;top=256;right=662;bottom=485;clip=1;edge=1;hscroll=1;multiline=1;vscroll=1;z=23};
639 | groupbox={cls="groupbox";text="选择当前配置:";left=9;top=6;right=675;bottom=513;edge=1;transparent=1;z=1};
640 | lbMsgLimit={cls="static";left=423;top=546;right=451;bottom=566;transparent=1;z=10};
641 | lbTemperature={cls="static";left=633;top=140;right=659;bottom=160;z=11};
642 | lstConfig={cls="listbox";left=18;top=34;right=187;bottom=464;edge=1;hscroll=1;items={};vscroll=1;z=16};
643 | static={cls="static";text="API key:";left=196;top=73;right=279;bottom=94;align="right";transparent=1;z=2};
644 | static2={cls="static";text="模型 ID:";left=196;top=109;right=279;bottom=130;align="right";transparent=1;z=3};
645 | static3={cls="static";text="不会联网读取系统提示词内的超链接,建议直接添加文档内容";left=282;top=489;right=649;bottom=508;color=0x5A5A5A;transparent=1;z=14};
646 | static4={cls="static";text="接口地址:";left=196;top=36;right=279;bottom=57;align="right";transparent=1;z=4};
647 | static5={cls="static";text="temperature:";left=196;top=146;right=279;bottom=167;align="right";transparent=1;z=5};
648 | static6={cls="static";text="上下文轮数:";left=33;top=560;right=128;bottom=592;align="right";transparent=1;z=9};
649 | static7={cls="static";text="代理服务器:";left=196;top=182;right=279;bottom=203;align="right";transparent=1;z=12};
650 | static8={cls="static";text="系统提示词:";left=196;top=253;right=279;bottom=274;align="right";transparent=1;z=13};
651 | static9={cls="static";text="自定义参数:";left=196;top=218;right=279;bottom=239;align="right";transparent=1;z=15};
652 | tbMsgLimit={cls="trackbar";left=132;top=552;right=415;bottom=582;max=100;min=0;z=26};
653 | tbTemperature={cls="trackbar";left=281;top=136;right=631;bottom=166;clip=1;max=100;min=0;z=20}
654 | )
655 |
656 | frmSetting.editExtraParameters.setCueBannerText("JSON 格式附加参数")
657 | frmSetting.editModel.setCueBannerText("请在输入 key 以后点右侧下拉按钮选择模型 ID")
658 | frmSetting.editProxy.setCueBannerText("socks=127.0.0.1:1081")
659 | frmSetting.editSystemPrompt.limit = -1;
660 |
661 | frmSetting.editApiKey.onFocusGot = function(){
662 | owner.passwordChar = null;
663 | owner.height = frmSetting.dpiScale(100);
664 | }
665 |
666 | frmSetting.editApiKey.onFocusLost = function(){
667 | owner.passwordChar = "*";
668 | owner.text = string.crlf(owner.text,"")
669 | owner.height = frmSetting.dpiScale(27);
670 | }
671 |
672 | frmSetting.editExtraParameters.onFocusGot = function(){
673 | owner.height = frmSetting.dpiScale(100);
674 | if(owner.text=""){
675 | owner.text='{\r\n\r\n}'
676 | frmSetting.setTimeout(
677 | function(){
678 | frmSetting.editExtraParameters.caretPos = 3;
679 | },100)
680 |
681 | }
682 | }
683 |
684 | frmSetting.editExtraParameters.onFocusLost = function(){
685 | owner.height = frmSetting.dpiScale(27);
686 | if(string.match(owner.text,"^\s*{\s*}\s*$")) owner.text="";
687 | }
688 |
689 | frmSetting.editApiUrl.items = {
690 | "https://ai.aardio.com/api/v1/",
691 | "https://api.deepseek.com/v1",
692 | "https://openrouter.ai/api/v1",
693 | "https://ark.cn-beijing.volces.com/api/v3/bots",
694 | "https://ark.cn-beijing.volces.com/api/v3",
695 | "https://api.siliconflow.cn/v1",
696 | "https://api.anthropic.com/v1",
697 | "https://generativelanguage.googleapis.com/v1beta/openai",
698 | "https://aiplatform.googleapis.com/v1beta1/projects/{project_id}/locations/global/endpoints/openapi/",
699 | "https://api.x.ai/v1",
700 | "https://api.openai.com/v1",
701 | "https://api.lkeap.cloud.tencent.com/v1",
702 | "https://dashscope.aliyuncs.com/api/v1/",
703 | "http://localhost:11434/v1/"
704 | }
705 |
706 | frmSetting.editApiUrl.onFocusLost = function(){
707 | var url = string.trim(frmSetting.editApiUrl.text,`"' `);
708 | if(#url && !..string.match(url,"^<@@http@>[sS]?\:")){
709 | frmSetting.editApiUrl.text = (string.startWith(url,"localhost")?"http://":"https://")+url;
710 | }
711 | }
712 |
713 | import web.rest.aiChat;
714 |
715 | var cacheModelList = {};
716 | frmSetting.editModel.onDropDown = function(){
717 | var url = string.trim(frmSetting.editApiUrl.text,`"' `);
718 | var apiKey = string.trim(frmSetting.editApiKey.text,`"' `);
719 |
720 | if(#url && #apiKey){
721 |
722 | if(!cacheModelList[url]){
723 | var aiClient = web.rest.aiChat({
724 | url = url;
725 | key = apiKey;
726 | proxy = string.trim(frmSetting.editProxy.text);
727 | model = "";
728 | });
729 | aiClient._http.setTimeouts(500,500,500);
730 | var models = aiClient.listModels();
731 |
732 | if(models[[1]][["id"]]){
733 | cacheModelList[url] = ..table.map(models,lambda(v) v["id"] )
734 | }
735 | else {
736 | cacheModelList[url] = null;
737 | }
738 |
739 | }
740 |
741 | if(#cacheModelList[url]){
742 | frmSetting.editModel.autoComplete(cacheModelList[url],1 );
743 | return;
744 | }
745 |
746 | }
747 |
748 | frmSetting.editModel.items = {};
749 | }
750 |
751 | frmSetting.editApiUrl.onListChange = function(){
752 | frmSetting.editModel.text = ""
753 | }
754 |
755 | frmSetting.tbMsgLimit.setRange(3,100);
756 | frmSetting.tbTemperature.setRange(0,10);
757 | frmSetting.tbTemperature.oncommand = function(id,event,pos){
758 |
759 | var pos = frmSetting.tbTemperature.pos;
760 | frmSetting.tbTemperature.tooltip = pos / 10;
761 | frmSetting.lbTemperature.text = pos / 10;
762 | }
763 |
764 | frmSetting.tbMsgLimit.oncommand = function(id,event,pos){
765 |
766 | frmSetting.lbMsgLimit.text = frmSetting.tbMsgLimit.pos;;
767 | }
768 |
769 | import win.ui.listEdit;
770 | var listEdit = win.ui.listEdit(frmSetting.lstConfig);
771 | listEdit.editBox.setCueBannerText("请输入配置名",true);
772 |
773 | if(!#config.aiChat.itemNames) {
774 | config.aiChat.itemNames = {"默认"}
775 | config.aiChat.itemData = {{
776 | url = config.aiChat.url || "https://api.deepseek.com/v1";
777 | key = config.aiChat.key;
778 | model = #config.aiChat.model ? config.aiChat.model : "deepseek-chat";
779 | temperature = config.aiChat.temperature;
780 | }};
781 | }
782 |
783 | frmSetting.lstConfig.onSelChange = function(){
784 | var selIndex = frmSetting.lstConfig.selIndex;
785 |
786 | if(config.aiChat.selItem && config.aiChat.selItem != selIndex){
787 |
788 | var extraParameters = frmSetting.editExtraParameters.text;
789 | if(#extraParameters && !string.match(extraParameters,"^\s*{\s*}\s*$")){
790 | extraParameters = JSON.tryParse(extraParameters);
791 | }
792 | else{
793 | extraParameters = null;
794 | }
795 |
796 | //保存上一个配置
797 | var configItem = {
798 | url = frmSetting.editApiUrl.text;
799 | key = string.trim(frmSetting.editApiKey.text,`"' `);
800 | model = ..string.match(frmSetting.editModel.text,"\s*(\S+)");
801 | temperature = frmSetting.tbTemperature.pos / 10;
802 | msgLimit = frmSetting.tbMsgLimit.pos;
803 | proxy = string.trim(frmSetting.editProxy.text);
804 | systemPrompt = frmSetting.editSystemPrompt.text;
805 | extraParameters = extraParameters;
806 | }
807 |
808 | config.aiChat.itemData[config.aiChat.selItem] = configItem;
809 | }
810 |
811 | //加载下一个配置
812 | var selIndex = frmSetting.lstConfig.selIndex;
813 | var configItem = config.aiChat.itemData[selIndex] || {};
814 |
815 | import string.escape2;
816 | configItem.key = ..string.escape2.unescape(configItem.key)
817 | frmSetting.editApiKey.text = ..string.escape2.escape(configItem.key)
818 |
819 | frmSetting.editApiUrl.text = configItem.url;
820 | frmSetting.editProxy.text = configItem.proxy;
821 | frmSetting.editSystemPrompt.text = configItem.systemPrompt || "你是 AI 助手,请用中文回答问题";
822 |
823 | if(type(configItem.extraParameters)=="table") frmSetting.editExtraParameters.text = json.stringify(configItem.extraParameters);
824 | else frmSetting.editExtraParameters.text = "";
825 |
826 | if(configItem.temperature===null) configItem.temperature = 0.1;
827 | frmSetting.tbTemperature.pos = configItem.temperature * 10;
828 | frmSetting.tbTemperature.tooltip = configItem.temperature;
829 | frmSetting.lbTemperature.text = configItem.temperature;
830 |
831 | frmSetting.tbMsgLimit.pos = configItem.msgLimit || 15;
832 | frmSetting.lbMsgLimit.text = configItem.msgLimit || 15;
833 |
834 | frmSetting.editModel.text = configItem.model;
835 |
836 | config.aiChat.proxy = null;
837 | table.assign(config.aiChat,configItem);
838 |
839 | config.aiChat.itemData[selIndex] = configItem;
840 | config.aiChat.itemNames = frmSetting.lstConfig.items;
841 |
842 | var currentSelIndex = winform.cmbAgent.selIndex;
843 | winform.cmbAgent.items = config.aiChat.itemNames;
844 | winform.cmbAgent.selIndex = currentSelIndex;
845 |
846 | config.aiChat.itemData = table.slice(config.aiChat.itemData,1,#config.aiChat.itemNames);
847 | config.aiChat.selItem = selIndex;
848 |
849 | config.aiChat.save();
850 | }
851 |
852 | frmSetting.lstConfig.items = config.aiChat.itemNames;
853 | frmSetting.lstConfig.selIndex = config.aiChat.selItem || 1;
854 | frmSetting.lstConfig.onSelChange();
855 | frmSetting.chkKatex.checked = config.aiChat.katex;
856 | frmSetting.chkUsage.checked = config.aiChat.usage;
857 |
858 | //保存并更新配置
859 | import inet.url;
860 | frmSetting.btnSave.oncommand = function(id,event){
861 |
862 | var extraParameters = ..string.trim(frmSetting.editExtraParameters.text);
863 | if(#extraParameters && !string.match(extraParameters,"^\s*{\s*}\s*$") ){
864 | var err;
865 | extraParameters,err = JSON.tryParse(extraParameters);
866 |
867 | if(!extraParameters || type(extraParameters)!="table"){
868 | return frmSetting.msgboxErr( "JSON 格式错误 " + (err||""),"ImTip - AI 智能助手");
869 | }
870 | }
871 |
872 |
873 | var configItem = {
874 | url = string.trim(frmSetting.editApiUrl.text);
875 | key = string.trim(frmSetting.editApiKey.text,`"' `);
876 | model = ..string.match(frmSetting.editModel.text,"\s*(\S+)");
877 | temperature = frmSetting.tbTemperature.pos / 10;
878 | msgLimit = frmSetting.tbMsgLimit.pos;
879 | proxy = string.trim(frmSetting.editProxy.text);
880 | systemPrompt = frmSetting.editSystemPrompt.text;
881 | extraParameters = extraParameters;
882 | }
883 |
884 | if(!#configItem.url){
885 | frmSetting.editApiUrl.editBox.showWarningTip("ImTip","请输入接口网址!")
886 | return;
887 | }
888 |
889 | if( string.match(configItem.proxy,"^[\w\-_\.]+$") && !(#configItem.model) ){
890 | frmSetting.editProxy.showWarningTip("ImTip","是否将模型 ID 输入到代理服务器了呢?!")
891 | return;
892 | }
893 |
894 | if(!(#configItem.model)){
895 | frmSetting.editModel.editBox.showWarningTip("ImTip","请输入模型 ID !")
896 | return;
897 | }
898 |
899 | import string.escape2;
900 | configItem.key = ..string.escape2.unescape(configItem.key)
901 |
902 | if(!#configItem.proxy){
903 | configItem.proxy = null;
904 | }
905 |
906 | var tUrl = inet.url.split(configItem.url);
907 | if(tUrl[["host"]]=="api.anthropic.com"){
908 | if(configItem.model[1]!='@'#){
909 | configItem.model = '@' + configItem.model;
910 | }
911 | }
912 | elseif(tUrl[["host"]]=="generativelanguage.googleapis.com"){
913 | configItem.url = "https://generativelanguage.googleapis.com/v1beta/openai"
914 | }
915 |
916 | var selIndex = frmSetting.lstConfig.selIndex;
917 | config.aiChat.selItem = selIndex;
918 | config.aiChat.itemData[selIndex] = configItem;
919 |
920 | table.assign(aiChatConfig,configItem);
921 | winform.cmbAgent.selIndex = selIndex;
922 |
923 | config.aiChat.proxy = null;
924 | table.assign(config.aiChat,configItem);
925 |
926 | config.aiChat.katex = frmSetting.chkKatex.checked;
927 | wb.enableKatex(config.aiChat.katex);
928 |
929 | config.aiChat.usage = frmSetting.chkUsage.checked;
930 | config.aiChat.save();
931 |
932 | frmSetting.endModal();
933 |
934 | if(!wb.started()){
935 | resetMessages();
936 | }
937 | else {
938 |
939 | var md = wb.getMarkdown();
940 | wb.write(md);
941 | }
942 |
943 | thread.delay(100)
944 | winform.editPrompt.setFocus();
945 | }
946 |
947 | frmSetting.btnEdit.oncommand = function(id,event){
948 | listEdit.beginEdit();
949 | }
950 |
951 | frmSetting.btnAdd.oncommand = function(id,event){
952 | listEdit.beginEdit(0);
953 | }
954 |
955 | frmSetting.btnRemove.oncommand = function(id,event){
956 |
957 | var selIndex = frmSetting.lstConfig.selIndex;
958 |
959 | ..table.remove(config.itemData,selIndex);
960 | ..table.remove(config.itemNames,selIndex);
961 | frmSetting.lstConfig.delete(selIndex)
962 |
963 | selIndex = selIndex<=frmSetting.lstConfig.count ? selIndex : selIndex -1;
964 |
965 | config.aiChat.selItem = null;
966 | frmSetting.lstConfig.selIndex = selIndex;
967 | frmSetting.lstConfig.onSelChange()
968 | }
969 |
970 | listEdit.onEditChanged = function(newText,selIndex){
971 | config.aiChat.itemNames = frmSetting.lstConfig.items;
972 |
973 | frmSetting.lstConfig.selIndex = selIndex;
974 | frmSetting.lstConfig.onSelChange();
975 |
976 | config.aiChat.save();
977 | }
978 |
979 | frmSetting.btnAdd.skin({
980 | color={
981 | active=0xFF00FF00;
982 | default=0xFF3C3C3C;
983 | disabled=0xFF6D6D6D;
984 | hover=0xFFFF0000
985 | }
986 | })
987 |
988 | frmSetting.btnRemove.skin({
989 | color={
990 | active=0xFF00FF00;
991 | default=0xFF3C3C3C;
992 | disabled=0xFF6D6D6D;
993 | hover=0xFFFF0000
994 | }
995 | })
996 |
997 | frmSetting.btnEdit.skin({
998 | color={
999 | active=0xFF00FF00;
1000 | default=0xFF3C3C3C;
1001 | disabled=0xFF6D6D6D;
1002 | hover=0xFFFF0000
1003 | }
1004 | })
1005 |
1006 | if(wb.documentMode<11){
1007 | frmSetting.chkKatex.checked = false;
1008 | frmSetting.chkKatex.disabled = true;
1009 | }
1010 |
1011 | frmSetting.doModal(winform);
1012 | winform.editPrompt.setFocus();
1013 | }
1014 |
1015 | wb.write(`
1016 | - 支持联网读取用户提示词中的网页链接,自动转换为结构化文本。
1017 | - 点这里获取接口密钥(API key)
1018 | - 设置 API key,添加 / 切换 API 助手
1019 | - 编程启动 AI 助手
1020 | - 超级热键 + 沉浸式 AI 助手
1021 | - 按住 ``Shift`` + 单击托盘直接启动 AI 助手。
1022 | - 按住 ``Ctrl+Enter`` 可直接发送问题
1023 | - 按住 ``Ctrl`` 键点 ``复制`` 可复制 AI 最后输出的代码块。
1024 | - 按住 ``Ctrl`` 键点 ``分享`` 可截长屏到剪贴板。
1025 | - 输入框右键菜单可导出对话,拖拽 json/jsonl 文件到窗口可导入对话。
1026 | `)
1027 |
1028 |
1029 | //默认设置输入框焦点
1030 | winform.editPrompt.setFocus();
1031 |
1032 | //拆分界面
1033 | winform.splitter.split(winform.wndBrowser,{winform.bkPrompt,winform.editPrompt});
1034 |
1035 | winform.splitter.ltMin = 200;
1036 | winform.splitter.rbMin = 70;
1037 |
1038 | var scrollbarHeight = ::User32.GetSystemMetrics(3/*_SM_CYHSCROLL*/)
1039 | winform.editPrompt.onOk = function(ctrl,alt,shift){
1040 | if(ctrl){
1041 | winform.btnSend.oncommand();
1042 | return true;
1043 | }
1044 |
1045 | var pt = ::POINT()
1046 | ::User32.GetCaretPos(pt)
1047 |
1048 | var lineCount = winform.editPrompt.lineCount;
1049 | var lineHeight = math.ceil(pt.x / lineCount + winform.dpiScale(5));
1050 |
1051 | if(pt.y+(lineHeight+scrollbarHeight)*3>winform.editPrompt.height){
1052 |
1053 | winform.wndBrowser.setRedraw(false)
1054 | winform.splitter.splitMove(-lineHeight)
1055 | winform.wndBrowser.setRedraw(true)
1056 | }
1057 | }
1058 |
1059 | global.onError = function( err,over ){
1060 | if(!over){
1061 | import debug;
1062 | var stack = debug.traceback(,"调用栈",3);
1063 | err = string.concat(err,stack);
1064 | }
1065 |
1066 | if( _STUDIO_INVOKED ) {
1067 | import win;
1068 | win.msgboxErr(err);
1069 | }
1070 | }
1071 |
1072 | winform.spinMaxTokens.buddy = winform.editMaxTokens;
1073 | winform.spinMaxTokens.setRange(1,1024*16);
1074 | winform.spinMaxTokens.pos = config.aiChat.maxTokens || 1024;
1075 | winform.spinMaxTokens.inc = 1024;
1076 |
1077 | winform.beforeDestroy = function(){
1078 | config.aiChat.maxTokens = winform.spinMaxTokens.pos;
1079 | }
1080 |
1081 | winform.btnSnap.oncommand = function(id,event){
1082 | import fsys.dlg;
1083 | import web.form.snap;
1084 |
1085 | if(key.getState("CTRL")){
1086 | winform.btnSnap.disabled = true;
1087 |
1088 | web.form.snap(wb,function(bmp){
1089 | var hbmp = bmp.copyHandle();
1090 | win.clip.writeBitmap(hbmp,true);
1091 | return true;
1092 | } );
1093 | }
1094 | else{
1095 | var path = fsys.dlg.save("*.jpg|*.jpg","AI 聊天助手 - 保存对话截图",,winform);
1096 | if(!path) return;
1097 |
1098 | winform.btnSnap.disabled = true;
1099 |
1100 | web.form.snap(wb,path);
1101 | winform.editPrompt.setFocus();
1102 | }
1103 |
1104 | wb.doScript(`document.documentElement.scrollTop = document.documentElement.scrollHeight + 50;`);
1105 |
1106 | thread.delay(1000);
1107 | winform.btnSnap.disabled = false;
1108 | winform.editPrompt.setFocus();
1109 | }
1110 |
1111 | winform.btnSearch.oncommand = function(id,event){
1112 |
1113 | var frmSearch = winform.loadForm("\dlg\frmSearch.aardio");
1114 | frmSearch.doModal(winform);
1115 |
1116 | winform.editPrompt.setFocus();
1117 | }
1118 |
1119 | winform.btnSearch.skin({
1120 | color={
1121 | active=0xFF00FF00;
1122 | default=0xFF3C3C3C;
1123 | disabled=0xFF999999;
1124 | hover=0xFFFF0000
1125 | }
1126 | })
1127 |
1128 | //按钮外观样式
1129 | winform.btnClear.skin({
1130 | color={
1131 | active=0xFF00FF00;
1132 | default=0xFF3C3C3C;
1133 | disabled=0xFF999999;
1134 | hover=0xFFFF0000
1135 | }
1136 | })
1137 |
1138 | //按钮外观样式
1139 | winform.btnSend.skin({
1140 | color={
1141 | active=0xFF00FF00;
1142 | default=0xFF3C3C3C;
1143 | disabled=0xFF999999;
1144 | hover=0xFFFF0000
1145 | }
1146 | })
1147 |
1148 | //按钮外观样式
1149 | winform.btnSetting.skin({
1150 | color={
1151 | active=0xFF00FF00;
1152 | default=0xFF3C3C3C;
1153 | disabled=0xFF999999;
1154 | hover=0xFFFF0000
1155 | }
1156 | })
1157 |
1158 | winform.btnCopy.skin({
1159 | color={
1160 | active=0xFF00FF00;
1161 | default=0xFF3C3C3C;
1162 | disabled=0xFF999999;
1163 | hover=0xFFFF0000
1164 | }
1165 | })
1166 |
1167 | winform.btnSnap.skin({
1168 | color={
1169 | active=0xFF00FF00;
1170 | default=0xFF3C3C3C;
1171 | disabled=0xFF999999;
1172 | hover=0xFFFF0000
1173 | }
1174 | })
1175 |
1176 | import win.ui.simpleWindow;
1177 | win.ui.simpleWindow(winform);
1178 |
1179 | winform.bkPrompt.directDrawBackgroundOnly();
1180 | winform.show();
1181 |
1182 | if(_ARGV.chat){
1183 | if(#_ARGV.q){
1184 | if(..string.endWith(_ARGV.q,".jsonl",true) && ..io.exist(_ARGV.q)){
1185 | wb.importChatMessages(_ARGV.q)
1186 | }
1187 | else{
1188 | winform.editPrompt.text = _ARGV.q;
1189 | winform.setTimeout(function(){
1190 | winform.btnSend.oncommand();
1191 | });
1192 | }
1193 |
1194 | ..win.showForeground(winform.hwnd);
1195 | }
1196 | }
1197 |
1198 | win.loopMessage();
--------------------------------------------------------------------------------
/dlg/frmIconFont.aardio:
--------------------------------------------------------------------------------
1 | //字体图标
2 | import win.ui;
3 | /*DSG{{*/
4 | var frmIconFont = win.form(text="图标字体浏览器(点选图标保存设置)";right=963;bottom=500;bgcolor=16777215;exmode="none";max=false;min=false;mode="popup")
5 | frmIconFont.add()
6 | /*}}*/
7 |
8 | import web.form;
9 | var wb = web.form(frmIconFont);
10 |
11 | wb.external={
12 | click = function(chr){
13 | var str = ustring.fromCharCode(chr);
14 | var hex = string.escape(str,true,true);
15 | frmIconFont.editCurChar.text = hex;
16 | }
17 | mouseover = function(chr){
18 | var str = ustring.fromCharCode(chr);
19 | frmIconFont.text ="图标字体浏览器(点选图标保存设置) - " + string.escape(str,true,true)
20 | }
21 | }
22 |
23 |
24 | var htmlTemplate = /**
25 |
26 |
27 |
28 |
29 |
30 |
52 |
53 |
54 |
55 | for(i=1;#owner.fontUnicodeChars/2;1){
56 | ?>
57 | =owner.fontUnicodeChars[i]?>;
64 |
65 | }
66 | ?>
67 |
68 |
69 | **/
70 |
71 | import string.fontRanges;
72 | frmIconFont.loadFont = function(fontName){
73 | if(!#fontName) return;
74 |
75 | if(string.cmp(frmIconFont.parent.embeddingFontName,fontName)==0){
76 | if(!io.exist(frmIconFont.parent.embeddingFontData)){
77 | var fontPath = io.tmpname(,".ttf");
78 | string.save(fontPath, frmIconFont.parent.embeddingFontData);
79 | if( ::Gdi32.AddFontResourceEx( fontPath,0x10/*FR_PRIVATE*/,0 ) ) {
80 | ::PostMessage( 0xFFFF/*_HWND_BROADCAST*/,0x1D/*_WM_FONTCHANGE*/,0,0);
81 | }
82 |
83 | ::Kernel32.MoveFileEx(fontPath,null,4/*_MOVEFILE_DELAY_UNTIL_REBOOT*/);
84 | }
85 | }
86 |
87 | fontUnicodeChars = string.fontRanges.getUnicodeString(fontName,0xE000,0xF8FF );
88 | if(!fontUnicodeChars) return;
89 |
90 | wb.loadcode(htmlTemplate,{fontUnicodeChars=fontUnicodeChars;fontName=fontName});
91 | return true;
92 | }
93 |
94 | frmIconFont.onInitDialog = function(hwnd,message,wParam,lParam){
95 | frmIconFont.center();
96 |
97 | var _,yParent = frmIconFont.parent.getPos();
98 | var x,y = frmIconFont.getPos();
99 |
100 | frmIconFont.setPos(x,yParent+frmIconFont.dpiScale(110));
101 | }
102 |
103 | //frmIconFont.show();
104 | //win.loopMessage();
--------------------------------------------------------------------------------
/dlg/frmSearch.aardio:
--------------------------------------------------------------------------------
1 | import fonts.fontAwesomeSub;
2 | import win.ui;
3 | import win.ui.ctrl.syslink;
4 | /*DSG{{*/
5 | var frmSearch = win.form(text="AI 联网搜索接口配置";right=852;bottom=434;bgcolor=16777215;border="dialog frame";max=false)
6 | frmSearch.add(
7 | btnSave={cls="plus";text="保存 / 测试搜索";left=657;top=389;right=811;bottom=419;align="left";color=3947580;db=1;dr=1;font=LOGFONT(h=-13);iconColor=5724159;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF0AA';notify=1;textPadding={left=25};z=15};
8 | chkExa={cls="plus";text="启用 exa.ai 联网搜索";left=23;top=15;right=242;bottom=51;align="left";dl=1;dt=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome')};iconText='\uF0C8 ';notify=1;textPadding={left=24};z=2};
9 | chkTavily={cls="plus";text="启用 Tavily 联网搜索";left=23;top=111;right=234;bottom=142;align="left";db=1;dl=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome')};iconText='\uF0C8 ';notify=1;textPadding={left=24};z=3};
10 | editExaExcludeDomains={cls="edit";left=114;top=303;right=409;bottom=335;db=1;dl=1;dt=1;edge=1;multiline=1;z=11};
11 | editExaIncludeDomains={cls="edit";left=114;top=261;right=409;bottom=293;dl=1;dt=1;edge=1;multiline=1;z=9};
12 | editExaKey={cls="edit";left=114;top=64;right=409;bottom=96;dl=1;dt=1;edge=1;multiline=1;password=1;z=1};
13 | editQuery={cls="edit";left=46;top=391;right=630;bottom=423;db=1;dl=1;dr=1;edge=1;multiline=1;z=13};
14 | editResult={cls="richedit";left=427;top=23;right=836;bottom=378;db=1;dr=1;dt=1;edge=1;hscroll=1;link=1;multiline=1;vscroll=1;z=14};
15 | editSearchCount={cls="edit";text="2";left=114;top=218;right=409;bottom=250;dl=1;dt=1;edge=1;multiline=1;num=1;z=7};
16 | editTavilyKey={cls="edit";left=114;top=152;right=409;bottom=184;db=1;dl=1;edge=1;multiline=1;password=1;z=5};
17 | lbExaKey={cls="static";text="API key:";left=14;top=71;right=101;bottom=98;align="right";dl=1;dt=1;transparent=1;z=4};
18 | lbExcludeDomains={cls="static";text="排除域名:";left=34;top=311;right=101;bottom=338;align="right";db=1;dl=1;dt=1;transparent=1;z=12};
19 | lbIncludeDomains={cls="static";text="搜索域名:";left=34;top=268;right=101;bottom=295;align="right";dl=1;dt=1;transparent=1;z=10};
20 | lbSearchCount={cls="static";text="最大网页数:";left=-11;top=226;right=101;bottom=253;align="right";dl=1;dt=1;transparent=1;z=8};
21 | lbTavilyKey={cls="static";text="API key:";left=15;top=157;right=101;bottom=184;align="right";db=1;dl=1;transparent=1;z=6};
22 | lnkExa={cls="syslink";text='exa.ai';left=269;top=25;right=412;bottom=43;bgcolor=16777215;dl=1;dt=1;z=16};
23 | lnkTavily={cls="syslink";text='tavily.com';left=261;top=117;right=404;bottom=135;bgcolor=16777215;db=1;dl=1;z=17}
24 | )
25 | /*}}*/
26 |
27 | frmSearch.editResult.text = /*
28 | - 每次清除上下文之前仅联网搜索一次。
29 | 大模型的上下文是有限的,注意首次的提示词对搜索友好。
30 |
31 | - 模型 ID 带 :online 后缀(已自带联网)不再重复联网。
32 | 模型 ID 带 aardio 前缀(已自带知识库)不再重复联网。
33 |
34 | - 用于 aardio 编程时建议使用 aardio 自带的 AI 助手。
35 | 有更多针对 aardio 的优化以及专用的 AI 接口: https://aardio.com/vip/
36 | */
37 | frmSearch.chkTavily.oncommand = function(id,event){
38 | if(frmSearch.chkTavily.checked){
39 | frmSearch.chkExa.checked = false;
40 | }
41 | }
42 |
43 | frmSearch.chkExa.oncommand = function(id,event){
44 | if(frmSearch.chkExa.checked){
45 | frmSearch.chkTavily.checked = false;
46 | }
47 | }
48 |
49 | import config;
50 | if(!config.search){
51 | config.search = {}
52 | };
53 | if(config.search.mode = "exa"){
54 | frmSearch.chkExa.checked = true;
55 | }
56 | elseif(config.search.mode = "tavily"){
57 | frmSearch.chkTavily.checked = true;
58 | }
59 |
60 | if(!config.search.exa){
61 | config.search.exa = {};
62 | }
63 |
64 | frmSearch.editSearchCount.text = config.search.exa.count;
65 | if(#config.search.exa.includeDomains){
66 | frmSearch.editExaIncludeDomains.text = string.join(config.search.exa.includeDomains,",");
67 | }
68 |
69 | if(#config.search.exa.excludeDomains){
70 | frmSearch.editExaExcludeDomains.text = string.join(config.search.exa.excludeDomains,",");
71 | }
72 |
73 | frmSearch.editExaKey.text = config.search.exa[["key"]];
74 | frmSearch.editTavilyKey.text = config.search.tavily[["key"]];
75 |
76 | frmSearch.btnSave.oncommand = function(id,event){
77 |
78 | config.search.exa = {
79 | key = frmSearch.editExaKey.text;
80 | count = tonumber(frmSearch.editSearchCount.text);
81 | includeDomains = #frmSearch.editExaIncludeDomains.text ? string.split(frmSearch.editExaIncludeDomains.text,",") : null;
82 | excludeDomains = #frmSearch.editExaExcludeDomains.text ? string.split(frmSearch.editExaExcludeDomains.text,",") : null;
83 | }
84 |
85 | if(#config.search.exa.includeDomains){
86 | if(#config.search.exa.excludeDomains){
87 | config.search.exa.excludeDomains = null;
88 | frmSearch.editExaExcludeDomains.showErrorTip("指定包含域名以后,不能再指定排除域名");
89 | return;
90 | }
91 | }
92 |
93 | config.search.tavily = {
94 | key = frmSearch.editTavilyKey.text;
95 | }
96 |
97 | if(frmSearch.chkExa.checked){
98 | config.search.mode = "exa"
99 | }
100 | elseif(frmSearch.chkTavily.checked){
101 | config.search.mode = "tavily"
102 | }
103 | else {
104 | config.search.mode = null;
105 | return;
106 | }
107 |
108 | if(!#frmSearch.editQuery.text){
109 | frmSearch.editQuery.showInfoTip("已保存设置,但测试搜索的内容为空");
110 | return ;
111 | }
112 |
113 | frmSearch.btnSave.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'}
114 | frmSearch.editResult.text = ""
115 |
116 | thread.invoke( function(frmSearch,searchConfig){
117 | import web.rest.jsonClient;
118 |
119 | if(frmSearch.chkExa.checked){
120 | //导入 Exa 索接口
121 | var exaClient = web.rest.jsonClient();
122 | exaClient.setHeaders({ "x-api-key": frmSearch.editExaKey.text} )
123 | var exa = exaClient.api("https://api.exa.ai/");
124 |
125 | //搜索
126 | var searchData,err = exa.search({
127 | query:"aardio 编程语言: " + frmSearch.editQuery.text,
128 | contents ={ text = true},
129 | includeDomains = searchConfig.exa.includeDomains,
130 | numResults: tonumber( frmSearch.editSearchCount.text ),
131 | type:"keyword" //一般 keyword 搜索就够了(价格低一些)
132 | })
133 |
134 | var ret = searchData[["results"]]
135 |
136 | if(!frmSearch.editResult.valid) return;
137 | if(ret){
138 | frmSearch.editResult.print(ret);
139 | }
140 | elseif(err){
141 | frmSearch.editResult.print(err);
142 | }
143 | }
144 | elseif(frmSearch.chkTavily.checked){
145 | var http = web.rest.jsonClient();
146 | http.setAuthToken(frmSearch.editTavilyKey.text);
147 | var tavily = http.api("https://api.tavily.com");
148 |
149 | //搜索
150 | var searchData,err = tavily.search({
151 | query = "aardio 编程语言: " + frmSearch.editQuery.text,
152 | includeDomains = searchConfig.exa.includeDomains,
153 | max_results = tonumber( frmSearch.editSearchCount.text ); //返回的搜索结果数量,不必要太多,前两三条就可以了
154 | })
155 |
156 | //将搜索结果添加到系统提示词
157 | var ret = searchData[["results"]]
158 |
159 | if(!frmSearch.editResult.valid) return;
160 | if(ret){
161 | frmSearch.editResult.print(ret);
162 | }
163 | elseif(err){
164 | frmSearch.editResult.print(err);
165 | }
166 | }
167 |
168 | frmSearch.btnSave.disabledText = null;
169 | },frmSearch,config.search)
170 | }
171 |
172 | var chkStyle = {
173 | color={
174 | active=0xFF00FF00;
175 | default=0xFF000000;
176 | disabled=0xEE666666;
177 | hover=0xFFFF0000
178 | };
179 | checked={
180 | iconText='\uF14A'
181 | };
182 | }
183 |
184 | frmSearch.btnSave.skin({
185 | color={
186 | active=0xFF00FF00;
187 | default=0xFF3C3C3C;
188 | disabled=0xFF6D6D6D;
189 | hover=0xFFFF0000
190 | }
191 | iconColor = {
192 | disabled=0xFF6D6D6D;
193 | }
194 | })
195 |
196 | frmSearch.chkTavily.skin(chkStyle);
197 | frmSearch.chkExa.skin(chkStyle);
198 |
199 | frmSearch.show();
200 | win.loopMessage()
--------------------------------------------------------------------------------
/dlg/images/input.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/dlg/images/input.gif
--------------------------------------------------------------------------------
/dlg/template.aardio:
--------------------------------------------------------------------------------
1 | ?>import win.ui;
2 | /*DSG{{*/
3 | mainForm = win.form(text="ImTip 导出方案";right=757;bottom=467)
4 | mainForm.add()
5 | /*}}*/
6 |
7 | import key.ime.stateBar;
8 | var imeBar = key.ime.stateBar(mainForm);
9 |
10 | //输入法状态提示(应先退出 ImTip ,多个提示窗口同时出现会闪烁)
11 | imeBar.imeSkin(/*ImTipConfig{{*/= owner.imTipConfig ?>/*}}*/)
12 |
13 | //托盘图标
14 | import win.util.tray;
15 | mainForm.tray = win.util.tray(mainForm)
16 | mainForm.tray.tip = "ImTip 导出方案"
17 |
18 | //响应托盘消息
19 | mainForm.onTrayMessage = {
20 | [0x205/*_WM_RBUTTONUP*/] = function(wParam){
21 | win.setForeground(mainForm.hwnd);
22 | mainForm.popmenu.popup();
23 | };
24 | }
25 |
26 | //托盘弹出菜单
27 | import win.ui.menu;
28 | mainForm.popmenu = win.ui.popmenu(mainForm);
29 | mainForm.popmenu.add('临时禁用提示',function(id){
30 | imeBar.imeWatch(!!imeBar.paused);
31 | mainForm.popmenu.check(1,!!imeBar.paused);
32 | });
33 |
34 | mainForm.popmenu.add('退出',function(id){ mainForm.close() });
35 |
36 | /*
37 | //输入法状态或目标窗口变更触发此事件
38 | var lastFocus,lastPath,lastClass;
39 | imeBar.onImeStateChange = function(hFocus,x,y,openNative,symbolMode,text,iconText){
40 | if(lastFocus!=hFocus){
41 | lastClass = win.getClass(hFocus);
42 | var tid,pid = win.getThreadProcessId(hFocus);
43 | lastPath = process.getPath(pid);
44 | }
45 |
46 | //指定文件名的程序禁止显示提示
47 | if(lastPath && io.splitpath(lastPath).file == "应用程序名称.exe"){
48 | return false;
49 | }
50 | return true;
51 | }
52 | */
53 |
54 | win.loopMessage();
--------------------------------------------------------------------------------
/lib/app/filterWindows.aardio:
--------------------------------------------------------------------------------
1 | import config;
2 |
3 | namespace app.filterWindows;
4 |
5 | update = function(){
6 |
7 | if(!..config.setting.filterWindows){
8 | ..imeBar.onImeStateChange = null;
9 | ..imeBar.onImeForegroundWindow = null;
10 | ..imeBar.getCaret = null;
11 |
12 | return;
13 | }
14 |
15 | codePath = ..config.setting.getFilterWindowsCodePath(false);
16 | if(!..io.exist(codePath)) return;
17 |
18 | import process.aardio;
19 | process.aardio.run(
20 | function(){
21 | try{
22 | loadcodex(codePath);
23 |
24 | if(!..imeBar.paused){
25 | ..imeBar.imeWatch(true);
26 | }
27 | }
28 | catch(e){
29 | import winex;
30 | winex.msgbox(e,"ImTip 过滤规则错误",0x10/*_MB_ICONHAND*/,..win.getForeground());
31 | }
32 | }
33 | )
34 | }
35 |
36 |
37 | var thrdWatcherFilterWindows;
38 | edit = function(id,event){
39 |
40 | import process.aardio;
41 | aardioPath = process.aardio.getPath();
42 |
43 | if(!aardioPath){
44 | aardioPath = process.aardio.download();
45 | if(!aardioPath) return;
46 | }
47 |
48 | codePath = ..config.setting.getFilterWindowsCodePath(true);
49 |
50 | if(!thrdWatcherFilterWindows){
51 | import fsys.dirWatcher;
52 | import win.debounce;
53 |
54 | var updateOnCodeChange = win.debounce(update,500);
55 |
56 | thrdWatcherFilterWindows = ..fsys.dirWatcher.thread(
57 | function(filename,action,actionText){
58 | updateOnCodeChange();
59 | }, ..io.splitpath(codePath).dir );
60 | }
61 |
62 | process.aardio(codePath);
63 | }
64 |
65 | watchFullScreen = function(enabled){
66 |
67 | if(enabled){
68 | if(!frmWatchFullscreen){
69 | frmWatchFullscreen = ..win.form().messageOnly();
70 | }
71 | }
72 | else {
73 | if(frmWatchFullscreen){
74 | frmWatchFullscreen.close();
75 | frmWatchFullscreen = null;
76 | }
77 |
78 | return;
79 | }
80 |
81 | import win.appBar;
82 | ..win.appBar.regist(frmWatchFullscreen,function(code,param){
83 |
84 | if(code == 2/*_ABN_FULLSCREENAPP*/){
85 | ..imeBar.imeWatch(!param)
86 | }
87 | })
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/lib/app/hotkey.aardio:
--------------------------------------------------------------------------------
1 | import config;
2 |
3 | namespace app.hotkey;
4 |
5 | var hotkeyThreadHandle;
6 | var hotkeyThreadId;
7 | update = function(enabled){
8 | if(hotkeyThreadId){
9 | ::User32.PostThreadMessage(hotkeyThreadId,0x12/*_WM_QUIT*/,0,0);
10 | if( !..thread.waitOne(hotkeyThreadHandle,1000) ){
11 | ..thread.terminate(hotkeyThreadHandle,0);
12 | }
13 |
14 | ..raw.closehandle(hotkeyThreadHandle)
15 |
16 | hotkeyThreadHandle = null;
17 | hotkeyThreadId = null;
18 | }
19 |
20 | if(!enabled) return;
21 |
22 | if(!mainPath) mainPath = ..config.setting.getHotkeyCodePath();
23 | hotkeyThreadHandle = ..thread.create(
24 | function(form,codePath){
25 | ..mainForm = form;
26 |
27 | import process.aardio;
28 | process.aardio.run(
29 | function(){
30 |
31 | import win;
32 | import win.ui;
33 |
34 | //禁止在关闭最后一个独立窗口时自动退出消息循环。
35 | win.autoQuitMessage = false;
36 |
37 | loadcodex(codePath,"ImTip");
38 | }
39 | )
40 | },..mainForm,mainPath
41 | )
42 |
43 | if(hotkeyThreadHandle){
44 | hotkeyThreadId = ::Kernel32.GetThreadId(hotkeyThreadHandle);
45 | }
46 |
47 | }
48 |
49 | var thrdWatcher;
50 | edit = function(){
51 | mainPath = ..config.setting.getHotkeyCodePath();
52 |
53 | import process.aardio;
54 | var path = process.aardio.getPath();
55 | if(!path){
56 | if(!process.aardio.download()) return;
57 | }
58 |
59 | if(!thrdWatcher){
60 | import fsys.dirWatcher;
61 | import win.debounce;
62 |
63 | var updateOnCodeChange = win.debounce(update,500);
64 |
65 | thrdWatcher = ..fsys.dirWatcher.thread(
66 | function(filename,action,actionText){
67 | updateOnCodeChange(..config.hotkey.chkEnableHotkey);
68 | }, ..io.splitpath(mainPath).dir );
69 | }
70 |
71 | process.aardio(mainPath);
72 | }
73 |
74 | ..subscribe("beforeUnload",function(){
75 | if(hotkeyThreadId){
76 | ::User32.PostThreadMessage(hotkeyThreadId,0x12/*_WM_QUIT*/,0,0);
77 | ..raw.closehandle(hotkeyThreadHandle);
78 |
79 | hotkeyThreadHandle = null;
80 | hotkeyThreadId = null;
81 | }
82 |
83 | if(thrdWatcher){
84 | thrdWatcher.close(); //停止监视文件
85 | }
86 | } );
87 |
--------------------------------------------------------------------------------
/lib/app/jab.aardio:
--------------------------------------------------------------------------------
1 |
2 | namespace app.jab;
3 |
4 | enable = function(enabled){
5 | if(!enabled) {
6 | return false;
7 | }
8 |
9 | if(..java[["accessBridge"]]){
10 | return true;
11 | }
12 |
13 | import process.aardio;
14 | var path = process.aardio.getPath();
15 | if(!path){
16 | if(!process.aardio.download()) return false;
17 | }
18 |
19 | //尝试在界面线程导入 java.accessBridge
20 | process.aardio.run(
21 | function(){
22 | if(..io.libpath("java.accessBridge")){
23 | global.import("java.accessBridge");
24 | }
25 | }
26 | )
27 |
28 | if(..java[["accessBridge"]]){
29 | return true;
30 | }
31 |
32 | //如未安装则导入 ide 库支持安装扩展库
33 | ..thread.invokeAndWait(
34 | function(){
35 | import process.aardio;
36 |
37 | //在独立线程导入 ide 扩展库,以避免改变 ImTip 主线程 _STUDIO_INVOKED 的值
38 | process.aardio.run(
39 | function(){
40 | import ide;
41 | global.import("java.accessBridge");
42 | }
43 | )
44 | }
45 | )
46 |
47 | process.aardio.run(
48 | function(){
49 | if(..io.libpath("java.accessBridge")){
50 | global.import("java.accessBridge");
51 | }
52 | }
53 | )
54 |
55 | if(..java[["accessBridge"]]){
56 | return true;
57 | }
58 | }
--------------------------------------------------------------------------------
/lib/app/util/_.aardio:
--------------------------------------------------------------------------------
1 | import string.xml;
2 | import win.ui.atom;
3 |
4 | namespace app.util;
5 |
6 | var ncr = ..string.xml.ncr;
7 | unescape = function(s){
8 | s = ncr(s);
9 | var hex = ..string.match(s,"\x\x\x\x+");
10 | if(hex) return ..string.unescape( ..string.format("\U%06x",tonumber(hex,16) ) );
11 | return s;
12 | }
13 |
14 | updateTipStyle = function(cfg){
15 | if(!cfg.embeddingFontData){
16 | var font = cfg.iconStyle[["font"]];
17 | if( font && ! ..string.cmp(font.name:"","FontAwesome") ) font.name = "imtip";
18 | }
19 |
20 | cfg.timeout = null;
21 | }
22 |
23 | findHwnd = function(){
24 | var atom,hwnd = ..win.ui.atom.find("E474890D-1DFA-4575-B456-7B46C15665DC.imtip");
25 | return hwnd;
26 | }
27 |
28 | /**intellisense(app.util)
29 | unescape(__) = 还原图标字体转义符,\n支持 NCR(Numeric Character Reference)编码
30 | updateTipStyle(__) = 升级旧版输入法提示外观配置
31 | findHwnd() = 用于 WubiLex :\n查找并返回独立运行的 ImTip.exe 主窗口句柄,\n失败返回 null
32 | end intellisense**/
33 |
--------------------------------------------------------------------------------
/lib/config.aardio:
--------------------------------------------------------------------------------
1 | //config 配置文件
2 | import fsys.config;
3 |
4 | config = fsys.config( "/.ImTip/", io.appData("/aardio/std/ImTip/") );
5 |
6 | namespace config {
7 |
8 | __defaultSchemes = {
9 | [1] = {
10 | argbColor=-1;
11 | border={color=14395508;radius=6;width=0};
12 | offsetX=31;
13 | iconStyle={align="right";font={h=-14;name="imtip";weight=700};padding={top=4;right=6;left=0;bottom=0}};
14 | background=-151026827;
15 | textRenderingHint=3;
16 | openStyle={[1]={argbColor=-1;background=-151026827;border={color=14395508;radius=6;width=0};iconColor=-1};[0]={argbColor=-1;background=-1136150017;border={color=14395508;radius=6;width=0};iconColor=-1}};
17 | timeout=2;
18 | align="left";
19 | textPadding={top=0;right=0;left=4;bottom=0};
20 | tipChars={fullShape='\uF111';close='\uF05E';hanja="漢";[1033]="En";[2052]="中";[1041]="あ";katakana="カ";halfShape='\uF186';[1042]="가";capital='\uF031';symbol="。"};
21 | width=55;
22 | font={h=-18;name="Segoe UI";weight=700};
23 | interval=50;
24 | iconColor=-1;
25 | height=34;
26 | offsetY=2;
27 | iconTextRenderingHint=3;
28 | valign="center"
29 | };
30 | [2] = {
31 | argbColor=-1836295;
32 | border={color=-1261241864;radius=18;width=2};
33 | offsetX=23;
34 | iconStyle={font={h=-12;name="imtip";weight=400};align="right";padding={top=3;right=6;left=0;bottom=0};valign="center"};
35 | background=-16562265;
36 | textRenderingHint=5;
37 | openStyle={[1]={argbColor=-1836295;background=-16562265;border={color=-1261241864;radius=18;width=2};iconColor=-4194379};[0]={argbColor=-1771533;background=-16476777;border={color=-8143936;radius=18;width=2};iconColor=-2490625}};
38 | align="left";
39 | textPadding={top=7;right=0;left=7;bottom=7};
40 | tipChars={fullShape='\uF111';close='\uE801';[1033]="英";[2052]="中";symbol='\uF82B';en="英";capital='\uF031';lang="中";halfShape='\uF042'};
41 | width=49;
42 | font={h=-14;name="Segoe UI";weight=400};
43 | iconColor=-4194379;
44 | height=29;
45 | offsetY=-5;
46 | iconTextRenderingHint=4;
47 | valign="center"
48 | };
49 | [3] = {
50 | argbColor=-1;
51 | border={color=-7810305;radius=29;width=1};
52 | offsetX=27;
53 | paddingBottom=14;
54 | iconStyle={align="right";font={h=-14;name="imtip";weight=700};padding={top=-2;right=5;left=0;bottom=-26}};
55 | background=-134163201;
56 | textRenderingHint=3;
57 | openStyle={[1]={argbColor=-1;foreground=-286406415;background=-134163201;border={color=-7810305;radius=29;width=1};iconColor=-1};[0]={argbColor=-1;foreground=-16539905;background=-12058775;border={color=14395508;radius=29;width=1};iconColor=-1}};
58 | linearGradient=265;
59 | paddingTop=3;
60 | align="left";
61 | valign="center";
62 | tipChars={fullShape='\uF111';close='\uF05E';symbol="。";[1033]="En";[2052]="中";[1041]="あ";capital='\uF031';halfShape='\uF186';[1042]="가";katakana="カ";hanja="漢"};
63 | width=36;
64 | font={h=-17;name="Segoe UI";weight=700};
65 | iconColor=-1;
66 | foreground=-286406415;
67 | height=43;
68 | offsetY=4;
69 | iconTextRenderingHint=3;
70 | textPadding={top=0;right=0;left=2;bottom=0}
71 | };
72 | [4] = {
73 | argbColor=16731983;
74 | border={color=14395508;radius=6;width=0};
75 | offsetX=13;
76 | iconStyle={align="left";font={h=-17;name="imtip";weight=700};padding={top=4;right=6;left=1;bottom=0}};
77 | background=16745333;
78 | textRenderingHint=3;
79 | openStyle={[1]={argbColor=16731983;background=16745333;border={color=14395508;radius=6;width=0};iconColor=-45233};[0]={argbColor=5291856;background=4700671;border={color=14395508;radius=6;width=0};iconColor=-11485360}};
80 | align="left";
81 | textPadding={top=0;right=0;left=-60;bottom=0};
82 | tipChars={fullShape='\uF111';close='\uE801';hanja="漢";[1033]='\uF111';[2052]='\uF111';[1041]="あ";katakana="カ";halfShape='\uF042';[1042]="가";capital='\uF031';symbol='\uF111'};
83 | width=55;
84 | font={h=-18;name="imtip";weight=400};
85 | iconColor=-45233;
86 | foreground=16745333;
87 | height=34;
88 | offsetY=3;
89 | iconTextRenderingHint=3;
90 | valign="center"
91 | };
92 | [5] = {
93 | argbColor=-1;
94 | border={color=-10454122;radius=10;width=0};
95 | offsetX=37;
96 | iconStyle={align="right";font={h=-14;name="imtip";weight=700};padding={top=-6;right=0;left=-21;bottom=-10}};
97 | background=-11639686;
98 | textRenderingHint=3;
99 | openStyle={[1]={argbColor=-1;foreground=-3373177;background=-11639686;border={color=-10454122;radius=10;width=0};iconColor=-1};[0]={argbColor=-6144;foreground=-10561920;background=-11700124;border={color=14395508;radius=10;width=0};iconColor=-1854}};
100 | paddingBottom=21;
101 | align="left";
102 | valign="center";
103 | tipChars={fullShape='\uF111';close='\uF05E';symbol="。";[1033]="En";[2052]="中";[1041]="あ";capital='\uF031';halfShape='\uF186';[1042]="가";katakana="カ";hanja="漢"};
104 | width=48;
105 | font={h=-21;name="Segoe UI";weight=700};
106 | iconColor=-1;
107 | foreground=-3373177;
108 | height=36;
109 | offsetY=2;
110 | iconTextRenderingHint=3;
111 | textPadding={top=-5;right=-41;left=3;bottom=-23}
112 | };
113 | }
114 |
115 | __defaultScheme = function(){
116 | var idx = setting.schemeIndex : 1;
117 | return ..table.clone(__defaultSchemes[idx] || __defaultSchemes[1]);
118 | }
119 | }
120 |
121 | if(!config.style.width){
122 | config.style.mix( ..config.__defaultScheme() );
123 | }
124 |
125 | if(!config.setting.schemes) {
126 | config.setting.schemes = table.clone(config.__defaultSchemes)
127 | }
128 |
129 | if(!config.setting.editorClasses){
130 | config.setting.interval = 50;
131 | config.setting.timeout = 2;
132 | config.setting.editorClasses = {
133 | ["AVL_AVView"]=1;["ConsoleWindowClass"]=1
134 | };
135 | }
136 |
137 | ..table.assign(config.setting.editorClasses,{
138 | ["@WeChatMainWndForPC"]=1;
139 | ["@ChatWnd"]=1;
140 | ["#EXCEL6"]=1;
141 | });
142 |
143 | config.style.backgroundColor = null;
144 | config.style.timeout = null;
145 |
146 | config.setting.updateQuirksMode = function(quirksMode){
147 | if(quirksMode===null){
148 | quirksMode = config.setting.quirksMode;
149 | }
150 |
151 | if(quirksMode){
152 | import ..sys.input;
153 | var tips = ..sys.input.getEnabledLayoutOrTips();
154 |
155 | if(tips["0804:{86598FB9-66A2-463E-B9C2-AEB906D477AD}{607FDF85-FCC8-4DBD-A365-41296F980C9C}"]){
156 | quirksMode = 3;//WX
157 | }
158 | elseif(tips["0804:{B722B5D7-0C4C-4933-A7B9-DF8C91F2C643}{0C7479AF-F27F-488C-A46B-5BDA6BF43E50}"] ){
159 | quirksMode = 2;//XF
160 | }
161 | elseif(tips["0804:{6573078D-A793-4CD9-A6E6-0FFB01F35262}{CE7B668C-8260-4496-AEE0-5305D3FB8E0E}"] ){
162 | quirksMode = 1;//SX
163 | }
164 | else {
165 | quirksMode = 3;
166 | }
167 | }
168 | else {
169 | quirksMode = 0;
170 | }
171 |
172 |
173 | config.setting.quirksMode = quirksMode;
174 | return quirksMode;
175 | }
176 |
177 | config.setting.getHotkeyCodePath = function(){
178 | var path = ..io.joinpath(config._cfgDir,".hotkey/hotkey.aardio");
179 | if(!..io.exist(path)){
180 | ..string.save(path, $"\codes\hotkey.txt")
181 | }
182 |
183 | return path;
184 | }
185 |
186 | config.setting.getFilterWindowsCodePath = function(init){
187 | var path = ..io.joinpath(config._cfgDir,".filter/filter.aardio");
188 | if(init && !..io.exist(path)){
189 | ..string.save(path, $"\codes\filter.aardio")
190 | }
191 |
192 | return path;
193 | }
194 |
195 | if(!config.aiChat.itemData) {
196 | config.aiChat.itemNames={[1]="默认";[2]="词典";[3]="翻译"};
197 | config.aiChat.itemData={[1]={proxy="";temperature=0.4;key='\0\1\96';model="imtip";url="https://ai.aardio.com/api/v1/";systemPrompt="";msgLimit=15};[2]={temperature=0.3;key='\0\1\96';model="imtip";url="https://ai.aardio.com/api/v1/";systemPrompt='你是一位英语教授,你的任务是根据输入的单词完成一系列英语学习相关的任务。以下是输入的单词:\r\n<单词>\r\n{{WORD}}\r\n单词>\r\n\r\n请完成以下具体任务:\r\n### 任务1:单词释义\r\n- 列出单词包含的所有词性对应的**词性**、**音标**、所有的**中文释义**和**英文释义**,一行一个。\r\n- 音标输出格式为 [/音标/🔊](https://dict.youdao.com/dictvoice?audio={WORD}&type=2)\r\n- 如果单词是动词,要展示**现在分词**、**过去式**、**过去分词**;如果是名词,要展示**第三人称单数**;如果是形容词,要展示**比较级**和**最高级**。\r\n- 讲述该单词的**词根词缀起源故事**。\r\n\r\n### 任务2:场景例句\r\n用这个单词造5个工作场景的英文例句,并附上英文翻译。\r\n\r\n### 任务3:相近词\r\n用这个单词的词根词缀,拓展5个相近单词,附带**词性**和**中文释义**。\r\n\r\n### 任务4:英文故事\r\n用任务3拓展出的单词编写一个有趣的A2难度英文故事,限7行内。\r\n\r\n### 任务5:小测验\r\n基于前4个任务生成的内容创造3个单选题,每个题目的选项一行一个,最后一起给出答案。\r\n\r\n请按照以下Markdown格式排版输出你的结果:\r\n\r\n### 单词释义\r\n\r\n### 场景例句\r\n\r\n### 相近词\r\n\r\n### 英文故事\r\n\r\n### 小测验 \r\n\r\n查看答案
这里输入小测验答案
';msgLimit=15};[3]={proxy="";temperature=0.5;key='\0\1\96';model="imtip";url="https://ai.aardio.com/api/v1/";systemPrompt='# 角色\r\n\r\n你是一位专业翻译。\r\n\r\n# 任务\r\n- 如果用户第一次输入的内容使用的语言是简体中文你就翻译为英文,如果使用的是其他语言你就翻译成简体中文。\r\n- 如果上文已经存在翻译结果,则请继续解答用户对翻译结果的疑问。\r\n\r\n# 对用户第一次输入的内容请分成以下步骤进行翻译:\r\n1. 首先根据用户第一次输入的内容使用的语言确定翻译的"""目标语言"""。\r\n如果用户输入为简体中文,则翻译"""目标语言"""为英文,否则翻译"""目标语言"""为中文。\r\n2. 首先根据原文直译为"""目标语言""",不要遗漏原文的任何信息。\r\n3. 参考第一次直译的结果,将原文重新意译为“目标语言”,在遵守原意的前提下,让翻译后的内容更地道、更通俗易懂,更符合以目标语言作为母语的表达习惯。\r\n4. 以教学为目的对翻译时涉及的重点语法与技巧进行适当的讲解,如果遇到特殊的语言现象、文化背景知识也请适当说明。\r\n\r\n# 对用户第一次输入的内容进行翻译的结果按以下 Markdown 格式排版输出(在全部上下文中这种格式只使用一次):\r\n\r\n## 直译\r\n\r\n## 意译\r\n\r\n## 讲解';msgLimit=15}};
198 | ..table.assign(config.aiChat,config.aiChat.itemData[1]);
199 | config.aiChat.selItem=1;
200 | }
201 |
202 | /**intellisense(config)
203 | setting = 通用设置
204 | saveAll() = 写入所有配置到文件
205 | ? = 获取值时指定不以下划线开始的配置表名称,\n返回一个可自动序列化到同名配置文件的表对象。\n如果此对象名以下划线开始,则可以正常读写值不会序列化为配置文件。\n否则不能对此对象直接赋值,只能对配置表对象的成员赋值。\n\n配置表可自动自文件加载,退出线程前自动序列化并存入文件。\n仅序列化以字符串、数值为键的元素,\n仅序列化值为字符串、数值、buffer 以及定义了 _serialize 元方法的成员。\n循环引用的值转换为 null,序列化时忽略成员函数\n!fsys_table.
206 | __defaultScheme() = 返回当前选择的配置方案初始默认方案
207 | end intellisense**/
--------------------------------------------------------------------------------
/lib/fonts/.res/fontAwesomeSub.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/lib/fonts/.res/fontAwesomeSub.ttf
--------------------------------------------------------------------------------
/lib/fonts/.res/imtip.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/lib/fonts/.res/imtip.ttf
--------------------------------------------------------------------------------
/lib/fonts/fontAwesomeSub.aardio:
--------------------------------------------------------------------------------
1 | import fsys;
2 | import fonts;
3 |
4 | namespace fonts.fontAwesomeSub{
5 |
6 | family = ..fonts.addFamily($"/lib/fonts/.res/fontAwesomeSub.ttf","FontAwesome")
7 | }
8 |
9 | /**intellisense()
10 | fonts.fontAwesomeSub = 导入FontAwesome 图标字体用于支持GDI/GDI+,控件/plus控件等,\n所有图标请参考aardio工具->文本文件->图标字体
11 | fonts.fontAwesomeSub.family = GDI+字体家族,可用于plus控件,gdip等库函数,\n!gdipfamily.
12 | end intellisense**/
--------------------------------------------------------------------------------
/lib/fonts/imtip.aardio:
--------------------------------------------------------------------------------
1 | import fonts;
2 |
3 | namespace fonts.imtip{
4 | family = ..fonts.addFamily(
5 | ..io.appData("/aardio/std/imtip1.0.ttf",$"~\lib\fonts\.res\imtip.ttf"),"imtip"
6 | )
7 | }
8 |
9 | /**intellisense()
10 | fonts.imtip = 导入图标字体,\n所有图标请参考 aardio 工具->文本文件->图标字体
11 | fonts.imtip.family = GDI+字样集合,可用于plus控件,gdip等库函数,\n!gdipfamily.
12 | end intellisense**/
13 |
14 | /**details(感谢)
15 | imtip 字体基于 fontello,iconfont,FontAwesome 4.7 生成。
16 | end details**/
--------------------------------------------------------------------------------
/lib/style.aardio:
--------------------------------------------------------------------------------
1 | //style 外观样式
2 |
3 | namespace style{
4 | primaryButton = {
5 | background={
6 | default=0xDD8FB2B0;
7 | hover=0xFF928BB3;
8 | disabled=0xFF999999;
9 | }
10 | };
11 | button = {
12 | background={
13 | default=0x668FB2B0;
14 | hover=0xFF928BB3;
15 | disabled=0xFF999999;
16 | }
17 | };
18 | transButton = {
19 | background={
20 | default=0;
21 | hover=0xFF928BB3;
22 | active=0x99928BB3;
23 | }
24 | color={
25 | disabled=0xFF999999;
26 | }
27 | iconColor={
28 | disabled=0xFF999999;
29 | }
30 | };
31 | checkBox = {
32 | color = {
33 | hover = 0xFFFF0000;
34 | active = 0xFF00FF00;
35 | disabled = 0xEE666666;
36 | }
37 | checked = {
38 | color = {
39 | hover = 0xFFFF0000;
40 | active = 0xFF00FF00;
41 | disabled = 0xEE666666;
42 | }
43 | iconText = '\uF14a'/*_FA_CHECK_SQUARE*/
44 | }
45 | };
46 | checkBoxGray = {
47 | color = {
48 | hover = 0xFFFF0000;
49 | active = 0xFF00FF00;
50 | disabled = 0xEE666666;
51 | }
52 | checked = {
53 | color = {
54 | hover = 0xFFFF0000;
55 | active = 0xFF00FF00;
56 | disabled = 0xEE666666;
57 | }
58 | iconText = '\uF14a'/*_FA_CHECK_SQUARE*/
59 | }
60 | };
61 | radio ={
62 | color = {
63 | hover = 0xFFFF0000;
64 | active = 0xFF00FF00;
65 | }
66 | checked = {
67 | iconText = '\uF058'/*_FA_CHECK_CIRCLE*/
68 | }
69 | };
70 | link = {
71 | color = {
72 | default = 0xFF000080;
73 | hover = 0xFFFF0000;
74 | active = 0xFF00FF00;
75 | }
76 | };
77 | plainButton = {
78 | color = {
79 | default = 0xFF3C3C3C;
80 | hover = 0xFFFF0000;
81 | active = 0xFF00FF00;
82 | }
83 | };
84 | key = {
85 | foreground={
86 | default = 0x00FFFFFF;
87 | hover= 0xFF8ADBAF;
88 | };
89 | checked = {
90 | foreground={
91 | default = 0x6600FF00;
92 | hover= 0xFF8ADBAF;
93 | };
94 | }
95 | };
96 | dropdown = {
97 | background={
98 | default=0xFF68CC95;
99 | disabled=0xFFC4CCC8;
100 | hover=0xFF4A522F;
101 | };
102 | color={
103 | default=0xFF000000;
104 | disabled=0xFF8A8A8A;
105 | hover=0xFFFFFFFF
106 | };
107 | checked = {
108 | foreground={
109 | default = 0xFFDB8A8E;
110 | hover= 0xFF8ADBAF;
111 | };
112 | }
113 | };
114 | trackbar = {
115 | background={
116 | default=0xFF23ABD9;
117 | disabled=0xFFC6C6C6;
118 | hover=0xFF4AC3F2
119 | };
120 | foreground={
121 | default=0xFFFF771C;
122 | disabled=0xFF808080;
123 | hover=0xFFFF7E24
124 | };
125 | color={
126 | active=0xFFEE630A;
127 | default=0xFFFF711E;
128 | disabled=0xFF808080;
129 | hover=0xFFF88917
130 | }
131 | };
132 | palette = {
133 | color={
134 | default=0xFFE0E3D1
135 | };
136 | border={
137 | active={color=0xFF9D9E96;width=2};hover={color=0xFF00AEFF;width=2}
138 | };
139 | checked={
140 | border={
141 | default={color=0xF0FF0000;width=1};hover={color=0xFF00AEFF;width=2}
142 | }
143 | };
144 | group="palette";
145 | };
146 | scheme = {
147 | color={
148 | default=0xFF5a5a5a;
149 | };
150 | border={
151 | active={color=0xFF9D9E96;width=2};hover={color=0xFF00AEFF;width=2}
152 | };
153 | checked={
154 | border={
155 | default={color=0xF0FF0000;width=1};hover={color=0xFF00AEFF;width=2}
156 | }
157 | };
158 | group="scheme";
159 | };
160 | }
161 |
--------------------------------------------------------------------------------
/lib/tsfInput.aardio:
--------------------------------------------------------------------------------
1 | //tsfInput 语言
2 | import sys.input;
3 | import win.rt.bcp47;
4 | import win.reg;
5 |
6 | namespace tsfInput;
7 |
8 | enablePinyin = function(enabled){
9 | ..sys.input.disable("0804:{81D4E9C9-1D3B-41BC-9E6C-4B40BF79E35E}{FA550B04-5AD7-411F-A5AC-CA038EC515D7}",!enabled );
10 | }
11 |
12 | enableWubi = function(enabled){
13 |
14 | ..sys.input.disable("0804:{6A498709-E00B-4C45-A018-8F9E4081AE40}{82590C13-F4DD-44F4-BA1D-8667246FDF8E}",!enabled);
15 | }
16 |
17 | enableUsKeyboard = function(enabled){
18 |
19 | ..sys.input.disable("0804:00000409",1);
20 | ..sys.input.disable("0804:00000804",1);
21 | ..sys.input.disable("0409:00000409",!enabled?1:0 );
22 | }
23 |
24 | getInputMethods = ..win.rt.bcp47.getInputMethods;
25 |
26 | getStatus = function(){
27 | var tips = ..sys.input.getEnabledLayoutOrTips();
28 |
29 | if(tips){
30 | return {
31 | wubi = !! tips["0804:{6A498709-E00B-4C45-A018-8F9E4081AE40}{82590C13-F4DD-44F4-BA1D-8667246FDF8E}"];
32 | pinyin = !! tips["0804:{81D4E9C9-1D3B-41BC-9E6C-4B40BF79E35E}{FA550B04-5AD7-411F-A5AC-CA038EC515D7}"];
33 | yong = !! tips["0804:{6565D455-5030-4C0F-8871-83F6AFDE514F}{4D5459DB-7543-42C0-9204-9195B91F6FB8}"];
34 | weasel = !! tips["0804:{A3F4CDED-B1E9-41EE-9CA6-7B4D0DE6CB0A}{3D02CAB6-2B8E-4781-BA20-1C9267529467}"];
35 | en = !! ( tips["0409:00000409"] || tips["0804:00000409"] || tips["0804:00000804"] );
36 | }
37 | }
38 |
39 | return {};
40 | }
41 |
42 | getLocal = function(){
43 | var set = {bool v}
44 | ::User32.SystemParametersInfo(0x104E/*_SPI_GETTHREADLOCALINPUTSETTINGS*/,0,set,0);
45 | return set.v;
46 | }
47 |
48 | setLocal = function(v){
49 | ::User32.SystemParametersInfo(0x104F/*_SPI_SETTHREADLOCALINPUTSETTINGS*/,0,!!v,3)
50 | }
51 |
52 | getInputMethodOverride = function(){
53 | var reg = ..win.reg("HKEY_CURRENT_USER\Control Panel\International\User Profile");
54 | if(reg){
55 | var ret = reg.queryValue("InputMethodOverride")
56 | reg.close();
57 |
58 | return ret;
59 | }
60 | }
61 |
62 | getDoublePinyinSchemes = function(){
63 | var result = {
64 | {name = "微软双拼",index = 0, scheme = ""};
65 | {name = "智能ABC",index = 1, scheme = ""};
66 | {name = "自然码",index = 3, scheme = ""};
67 | }
68 |
69 | var regSettings = ..win.reg("HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Settings\CHS");
70 | if(!regSettings) return result;
71 |
72 | for(name,scheme,t in regSettings.eachValue()) {
73 | if(..string.find(name,"UserDefinedDoublePinyinScheme\d+")){
74 | if(scheme && type.isString(scheme) ){
75 | var schemeName = ..string.match(scheme,"^([^*]+)");
76 | if(schemeName){
77 | var i = ..string.match(name,"(\d+)");
78 | if(i){
79 | ..table.push(result,{
80 | name = schemeName;
81 | index = 10 + (tonumber(i):0);
82 | scheme = scheme;
83 | })
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | var freeIndex = 10;
91 | for i,v in ..table.eachIndex(result){
92 | if(..string.find(v.name,"小鹤") || ..string.endWith(v.scheme,"*2*^*iuvdjhcwfg^xmlnpbksqszxkrltvyovt") ){
93 | result.xhup = i;
94 | break;
95 | }
96 |
97 | if(v.index>=freeIndex){
98 | freeIndex = v.index+1;
99 | }
100 | }
101 | result.freeIndex = freeIndex;
102 |
103 | if(regSettings.queryValue("Enable Double Pinyin")){
104 | result.default = regSettings.queryValue("DoublePinyinScheme") : 0;
105 | }
106 |
107 | regSettings.close();
108 | return result;
109 | }
110 |
111 | enableoublePinyinScheme = function(enabled,schemeId){
112 |
113 | var regSettings = ..win.reg("HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Settings\CHS");
114 | if(!regSettings) return false;
115 |
116 | if(enabled){
117 | if(schemeId!==null)regSettings.setDwValue("DoublePinyinScheme",schemeId);
118 | regSettings.setDwValue("Enable Double Pinyin",1);
119 | }
120 | else {
121 | regSettings.setDwValue("DoublePinyinScheme",0);
122 | regSettings.setDwValue("Enable Double Pinyin",0);
123 | }
124 |
125 | regSettings.close();
126 | return true;
127 | }
128 |
129 | enableXhup = function(enabled){
130 | var schemes = getDoublePinyinSchemes();
131 | if(!schemes.xhup) return false;
132 |
133 | enableoublePinyinScheme(enabled,schemes.xhup);
134 | }
135 |
136 | /**intellisense(tsfInput)
137 | getDoublePinyinSchemes() = 获取全部双拼方案,\n如果不存在小鹤双拼方案,就安装该方案
138 | enableoublePinyinScheme(.(enabled,schemeId) = 是否启用指定的双拼方案\n@schemeId 参数为双拼方案 ID
139 | enableXhup(.(enabled) = 是否安装并开启小鹤双拼
140 | enablePinyin(.(enabled) = 是否启用微软拼音
141 | enableWubi(.(enabled) = 是否启用微软五笔
142 | enableUsKeyboard(.(enabled) = 是否启用英文键盘
143 | getInputMethods() = 返回所有启用的输入法
144 | getStatus() = 返回输入法状态
145 | getLocal() = 是否允许每个应用窗口使用不同输入法
146 | setLocal(__) = 设置是否允许每个应用窗口使用不同输入法,\n参数 @1 指定布尔值
147 | getInputMethodOverride() = 返回默认输入法 ID,仅 Win10 以及之后系统可用
148 | end intellisense**/
149 |
--------------------------------------------------------------------------------
/lib/ui/appTrayMenu.aardio:
--------------------------------------------------------------------------------
1 | //appTrayMenu 托盘菜单
2 | import config;
3 | import fsys.dlg;
4 | import ui.doublePinyinMenu;
5 | import tsfInput;
6 | import win.ui.menu;
7 | import win.clip;
8 | import sys.input;
9 |
10 | namespace ui;
11 |
12 | class appTrayMenu{
13 | ctor(winform){
14 | var popmenu = ..win.ui.popmenu(winform);//创建弹出菜单
15 |
16 | popmenu.add("设置",function(){
17 | if(..mainSettingForm) ..mainSettingForm.show(false);
18 |
19 | ..mainForm.show();
20 | ..win.setForeground(..mainForm.hwnd);
21 | })
22 |
23 | popmenu.add();//分隔线
24 |
25 | var id = popmenu.add('启用超级热键',function(id){
26 |
27 | ..config.hotkey.chkEnableHotkey = !..config.hotkey.chkEnableHotkey;
28 | ..mainForm.chkHotkey.checked = ..config.hotkey.chkEnableHotkey;
29 |
30 | import app.hotkey;
31 | app.hotkey.update(..config.hotkey.chkEnableHotkey);
32 | });
33 | popmenu.check(id,!!..config.hotkey.chkEnableHotkey,0/*_MF_BYCOMMAND*/);
34 |
35 |
36 | var id = popmenu.add('启用输入跟踪提示\tCtrl+单击托盘',function(id){
37 | ..config.setting.imeBarDisabled = !..imeBar.paused;
38 | ..imeBar.imeWatch(!..config.setting.imeBarDisabled);
39 | ..mainForm.chkImeBarDisabled.checked = !..config.setting.imeBarDisabled;
40 | ..config.setting.save();
41 | });
42 | popmenu.check(id,!..imeBar.paused,0/*_MF_BYCOMMAND*/);
43 |
44 | popmenu.add();//分隔线
45 |
46 | if(_WIN10_LATER){
47 | var imStatus = ..tsfInput.getStatus();
48 | var id = popmenu.add('启用英文键盘',function(id){
49 | ..tsfInput.enableUsKeyboard(!imStatus.en);
50 | });
51 | popmenu.check(id,imStatus.en,0/*_MF_BYCOMMAND*/);
52 |
53 | var id = popmenu.add('启用微软拼音',function(id){
54 | ..tsfInput.enablePinyin(!imStatus.pinyin);
55 | });
56 | popmenu.check(id,imStatus.pinyin,0/*_MF_BYCOMMAND*/);
57 |
58 | popmenu.add('└── 双拼方案',..ui.doublePinyinMenu(winform));
59 |
60 | var id = popmenu.add('启用微软五笔',function(id){
61 | ..tsfInput.enableWubi(!imStatus.wubi);
62 | });
63 | popmenu.check(id,imStatus.wubi,0/*_MF_BYCOMMAND*/);
64 |
65 | popmenu.add('└── 管理形码词库',function(id){
66 | import process;
67 | process.openUrl("http://wubi.aardio.com/");
68 | });
69 |
70 | if(imStatus.weasel){
71 |
72 | if( weaselIsShowNotifications() ){
73 | popmenu.add('禁用小狼毫自带悬浮提示',function(id){
74 | weaselShowNotifications(false);
75 | ..mainForm.msgbox("小狼毫自带悬浮提示已关闭。按住 Shift 右键点击托盘图标可显示【启用小狼毫自带悬浮提示】菜单项。","ImTip")
76 | });
77 | }
78 | elseif(::User32.GetKeyState(0x10/*_VK_SHIFT*/) & 0x8000){
79 | popmenu.add('启用小狼毫自带悬浮提示',function(id){
80 | weaselShowNotifications(true);
81 | });
82 | }
83 |
84 | }
85 |
86 | popmenu.add();//分隔线
87 |
88 | popmenu.add('打开系统输入法设置',function(id){
89 | ..raw.execute("ms-settings:keyboard")
90 | });
91 |
92 | var menuDefTip = ..win.ui.popmenu(winform);
93 | var id = menuDefTip.add("所有应用使用同一输入法",function(){
94 | ..tsfInput.setLocal(!..tsfInput.getLocal());
95 | })
96 | menuDefTip.check(id,!..tsfInput.getLocal(),0/*_MF_BYCOMMAND*/);
97 |
98 | var inputMethodOverride = ..tsfInput.getInputMethodOverride();
99 | var tips = ..sys.input.getEnabledLayoutOrTips();
100 | if(..table.next(tips)){
101 |
102 | for(tip,tipType in tips){
103 | if(tipType==2/*_LOTP_KEYBOARDLAYOUT*/){
104 | var desc = ..sys.input.getDescription(tip);
105 | if(#desc){
106 | var id = menuDefTip.add(desc,function(){
107 | if(tip!=inputMethodOverride) ..sys.input.setDefault(tip);
108 | else ..sys.input.setDefault("");
109 | })
110 | menuDefTip.check(id,tip==inputMethodOverride,0/*_MF_BYCOMMAND*/);
111 | }
112 | }
113 | }
114 |
115 | for(tip,tipType in tips){
116 | if(tipType==1/*_LOTP_INPUTPROCESSOR*/){
117 | var desc = ..sys.input.getDescription(tip);
118 | if(#desc){
119 | var id = menuDefTip.add(desc,function(){
120 | if(tip!=inputMethodOverride) ..sys.input.setDefault(tip);
121 | else ..sys.input.setDefault("");
122 | })
123 | menuDefTip.check(id,tip==inputMethodOverride,0/*_MF_BYCOMMAND*/);
124 | }
125 | }
126 | }
127 | }
128 |
129 | popmenu.add('设置默认输入法',menuDefTip);
130 | popmenu.add();//分隔线
131 | }
132 | else{
133 |
134 | if(_WIN7_LATER){
135 | var imStatus = ..tsfInput.getStatus();
136 | var id = popmenu.add('启用英文键盘',function(id){
137 | ..tsfInput.enableUsKeyboard(!imStatus.en);
138 | });
139 | popmenu.check(id,imStatus.en,0/*_MF_BYCOMMAND*/);
140 | }
141 |
142 | popmenu.add('打开系统输入法设置',function(id){
143 | ..raw.execute("control.exe","input.dll")
144 | });
145 | }
146 |
147 | var id = popmenu.add('反转鼠标左右按键',function(id){
148 | ::User32.SwapMouseButton(!::User32.GetSystemMetrics(23));
149 | });
150 | popmenu.check(id,::User32.GetSystemMetrics(23),0/*_MF_BYCOMMAND*/);
151 |
152 | popmenu.add('打开屏幕键盘',function(id){
153 | import process.wow64;
154 | process.wow64.execute("osk")
155 | });
156 |
157 | popmenu.add();//分隔线
158 |
159 | if(..mainForm.onTrayMenu){
160 | ..mainForm.onTrayMenu(popmenu)
161 | }
162 |
163 | popmenu.add('AI 智能助手\tShift+单击托盘',function(id){
164 | import process.imTip;
165 | process.imTip(chat = "");
166 |
167 | ..mainForm.show(false);
168 | });
169 |
170 | popmenu.add();//分隔线
171 |
172 | popmenu.add('退出程序',function(id){
173 | if(..mainSettingForm) ..mainSettingForm.exportStyleToSysConfig();
174 |
175 | winform.close();
176 | ..win.quitMessage();
177 | });
178 |
179 | return popmenu;
180 | };
181 | }
182 |
183 | appTrayMenu.weaselIsShowNotifications = function(){
184 | import sys.reg;
185 | var userDir = ..sys.reg.getValue("RimeUserDir","Software\Rime\Weasel")
186 | if(!#userDir){
187 | userDir = ..io.getSpecial(0x1a/*_CSIDL_APPDATA*/,"Rime")
188 | var cfgPath = ..io.joinpath(userDir,"weasel.custom.yaml");
189 | if(..io.exist(cfgPath)){
190 | var str = ..string.load(cfgPath);
191 | if( ..string.find(str,"show_notifications\:\s*false!\W") ){
192 | return false;
193 | }
194 | }
195 | }
196 | return true;
197 | }
198 |
199 | appTrayMenu.weaselShowNotifications = function(value){
200 | value = tostring(value);
201 |
202 | import sys.reg;
203 | import process.cache;
204 | import process.popen;
205 | import fsys.path;
206 |
207 | var weaselPath = ..process.cache.find("WeaselServer.exe")
208 | var deployerPath = ..fsys.path.replaceFile(weaselPath,"WeaselDeployer.exe")
209 |
210 | if(weaselPath){
211 | var dir = ..io.splitpath(weaselPath).dir
212 | var depolyerPath = ..io.joinpath(dir,"WeaselDeployer.exe");
213 | if(..io.exist(depolyerPath)){
214 |
215 | var userDir = ..sys.reg.getValue("RimeUserDir","Software\Rime\Weasel")
216 | if(!#userDir){
217 | userDir = ..io.getSpecial(0x1a/*_CSIDL_APPDATA*/,"Rime")
218 | var cfgPath = ..io.joinpath(userDir,"weasel.custom.yaml");
219 | if(..io.exist(cfgPath)){
220 | var count;
221 | var str = ..string.load(cfgPath);
222 | str,count = ..string.replace(str,"show_notifications\:\s*[\w\_]+","show_notifications: "+value);
223 |
224 | if(!count){
225 | str,count = ..string.replace(str,"!\Npatch\:",'patch:\n show_notifications: '+value);
226 |
227 | if(!count ){
228 | if(!..string.find(str,"!\Npatch\:")){
229 | str = str + 'patch:\n show_notifications: '+value;
230 | }
231 | else {
232 | return false;
233 | }
234 |
235 | }
236 | }
237 |
238 | ..string.save(cfgPath, str);
239 | ..process.popen(deployerPath,"/deploy")
240 |
241 | return true;
242 | }
243 | else {
244 | ..string.save(cfgPath, 'patch:\n show_notifications: '+value);
245 | ..process.popen(deployerPath,"/deploy")
246 |
247 | return true;
248 | }
249 |
250 | }
251 | }
252 | }
253 |
254 | return false;
255 | }
256 |
257 | /**intellisense()
258 | ui.appTrayMenu() = 托盘菜单\n!popmenu.
259 | end intellisense**/
260 |
261 | /*
262 | import process.imTip
263 | */
--------------------------------------------------------------------------------
/lib/ui/colorPicker.aardio:
--------------------------------------------------------------------------------
1 | //colorPicker 超级调色器
2 | import win.ui.ctrl.pick;
3 |
4 | namespace ui;
5 |
6 | class colorPicker{
7 | ctor( /*输入构造函数所需要的参数*/ ){
8 | this = ..win.ui.ctrl.pick();
9 | ..win.setTopmost(this.hwnd,true);
10 | this.doModal();
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/lib/ui/doublePinyinMenu.aardio:
--------------------------------------------------------------------------------
1 | //doublePinyinMenu 菜单
2 |
3 | import tsfInput;
4 |
5 | namespace ui;
6 |
7 | class doublePinyinMenu{
8 | ctor( winform ){
9 | var dpSchemes = ..tsfInput.getDoublePinyinSchemes();
10 | var popmenuPinyinSchemes = ..win.ui.popmenu(winform);
11 |
12 | var id = popmenuPinyinSchemes.add("禁用双拼",function(id){
13 | ..tsfInput.enableoublePinyinScheme(dpSchemes.default===null);
14 | });
15 | popmenuPinyinSchemes.check(id,dpSchemes.default===null,0/*_MF_BYCOMMAND*/);
16 | popmenuPinyinSchemes.add();
17 |
18 | for i,scheme in ..table.eachIndex(dpSchemes){
19 | var id = popmenuPinyinSchemes.add(scheme.name,function(id){
20 | ..tsfInput.enableoublePinyinScheme(dpSchemes.default!=scheme.index,scheme.index);
21 | });
22 | popmenuPinyinSchemes.check(id,dpSchemes.default==scheme.index,0/*_MF_BYCOMMAND*/);
23 | }
24 |
25 | if(!dpSchemes.xhup){
26 | var id = popmenuPinyinSchemes.add("小鹤双拼",function(id){
27 | var regSettings = ..win.reg("HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Settings\CHS");
28 | if(!regSettings) return winform.msgboxErr("系统不支持小鹤双拼")
29 |
30 | regSettings.setSzValue("UserDefinedDoublePinyinScheme"+(dpSchemes.freeIndex-10),"小鹤双拼*2*^*iuvdjhcwfg^xmlnpbksqszxkrltvyovt")
31 | ..table.push(dpSchemes,{
32 | name = "小鹤双拼";
33 | index = dpSchemes.freeIndex;
34 | scheme = "小鹤双拼*2*^*iuvdjhcwfg^xmlnpbksqszxkrltvyovt";
35 | })
36 |
37 | ..tsfInput.enableoublePinyinScheme(true,dpSchemes.freeIndex);
38 | regSettings.close();
39 | });
40 | }
41 |
42 | popmenuPinyinSchemes.add();
43 | popmenuPinyinSchemes.add("设置",function(id){
44 | import process;
45 | process.execute("ms-settings:regionlanguage-chsime-pinyin");
46 | });
47 |
48 | return popmenuPinyinSchemes;
49 | };
50 | }
51 |
52 | /**intellisense()
53 | ui.doublePinyinMenu(winform) = 创建双拼弹出菜单
54 | ui.doublePinyinMenu() = !popmenu.
55 | end intellisense**/
56 |
--------------------------------------------------------------------------------
/main.aardio:
--------------------------------------------------------------------------------
1 |
2 | /*启动参数{{*/
3 | _IMTIP_APP = true;
4 | if(_ARGV.chat){
5 | loadcodex("\dlg\aiChat.aardio");
6 | return;
7 | }
8 |
9 | for(k,v in _ARGV){
10 | if(string.endWith(v,".aardio",true)){
11 | if(io.exist(v)){
12 | __argvImTipConfig = v;
13 | }
14 | }
15 | }
16 | /*}}*/
17 |
18 | import style;
19 | import fonts.imtip;
20 | import win.ui.atom;
21 | import win.ui;
22 | import win.dlg.message;
23 | import fonts.fontAwesomeSub;
24 | /*DSG{{*/
25 | mainForm = win.form(text="ImTip 7.5 - 智能桌面助手";right=645;bottom=397;bgcolor=16777215;border="dialog frame";max=false;min=false)
26 | mainForm.add(
27 | btnAiChat={cls="plus";text="AI 智能助手";left=36;top=233;right=179;bottom=263;align="left";bgcolor=11580047;db=1;dl=1;font=LOGFONT(h=-14);iconStyle={align="left";font=LOGFONT(h=-16;name='FontAwesome');padding={left=10;top=1}};iconText='\uF007';notify=1;tabstop=1;textPadding={left=29};z=9};
28 | btnFilterWindows={cls="plus";text="编辑规则";left=529;top=186;right=622;bottom=215;align="left";color=3947580;db=1;disabled=1;dr=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF044';notify=1;textPadding={left=25};z=12};
29 | btnHotkey={cls="plus";text="编辑超级热键";left=36;top=40;right=179;bottom=70;align="left";bgcolor=11580047;dl=1;dt=1;font=LOGFONT(h=-14);iconStyle={align="left";font=LOGFONT(h=-16;name='imtip');padding={left=10;top=1}};iconText='\uE8CE';notify=1;tabstop=1;textPadding={left=29};z=6};
30 | btnImTip={cls="plus";text="配置输入提示";left=36;top=186;right=179;bottom=216;align="left";bgcolor=11580047;db=1;dl=1;font=LOGFONT(h=-14);iconStyle={align="left";font=LOGFONT(h=-16;name='FontAwesome');padding={left=10;top=1}};iconText='\uF0F6';notify=1;tabstop=1;textPadding={left=29};z=8};
31 | chkFilterWindows={cls="plus";text="启用扩展规则";left=417;top=186;right=524;bottom=215;align="left";db=1;dr=1;iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={top=1}};iconText='\uF0C8 ';notify=1;tabstop=1;textPadding={left=20};z=13};
32 | chkHotkey={cls="plus";text="启用超级热键";left=193;top=41;right=389;bottom=70;align="left";dl=1;dr=1;dt=1;iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={top=1}};iconText='\uF0C8 ';notify=1;tabstop=1;textPadding={left=20};z=7};
33 | chkImeBarDisabled={cls="plus";text="启用输入提示";left=193;top=186;right=300;bottom=215;align="left";db=1;dl=1;iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={top=1}};iconText='\uF0C8 ';notify=1;tabstop=1;textPadding={left=20};z=15};
34 | chkJab={cls="plus";text="启用 aardio / java.accessBridge 扩展(支持 JetBrains 等 Java 程序窗口)";left=36;top=338;right=525;bottom=367;align="left";db=1;dl=1;dr=1;iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={top=1}};iconText='\uF0C8 ';notify=1;tabstop=1;textPadding={left=20};z=14};
35 | chkSysRun={cls="plus";text="允许开机启动";left=36;top=309;right=601;bottom=338;align="left";db=1;dl=1;dr=1;iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={top=1}};iconText='\uF0C8 ';notify=1;tabstop=1;textPadding={left=20};z=10};
36 | chkWatchFullScreen={cls="plus";text="忽略全屏窗口";left=305;top=186;right=412;bottom=215;align="left";db=1;dl=1;iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={top=1}};iconText='\uF0C8 ';notify=1;tabstop=1;textPadding={left=20};z=11};
37 | group1={cls="plus";left=19;top=24;right=629;bottom=277;align="left";border={color=-16744448;radius=8;width=1};db=1;dl=1;dr=1;dt=1;font=LOGFONT(h=-14);textPadding={left=16};valign="top";z=2};
38 | group2={cls="plus";left=19;top=296;right=629;bottom=380;align="left";border={color=-16744448;radius=8;width=1};db=1;dl=1;dr=1;font=LOGFONT(h=-14);textPadding={left=16};valign="top";z=1};
39 | groupTitle1={cls="plus";text="软件功能";left=31;top=8;right=107;bottom=34;dl=1;dt=1;z=4};
40 | groupTitle2={cls="plus";text="通用选项";left=36;top=281;right=112;bottom=307;db=1;dl=1;z=5};
41 | lpHotkey={cls="syslink";text='ImTip 强大的『超级热键』集成 aardio 开发环境,可调用 aardio 的全部标准库与扩展库。\n可直接调用大模型 API 接口,支持 将各种桌面应用快速接入 AI 智能助手。\n\nImTip 超级热键开发指南\naardio 语法速览';left=62;top=90;right=599;bottom=181;db=1;dl=1;dr=1;dt=1;notify=1;transparent=1;z=3}
42 | )
43 | /*}}*/
44 |
45 | var atom,hwnd = mainForm.atom("E474890D-1DFA-4575-B456-7B46C15665DC.imtip");
46 | if(!atom){
47 | if(__argvImTipConfig) win.sendCopyData(hwnd,__argvImTipConfig );
48 | else {
49 | win.sendCopyData(hwnd,"show");
50 | }
51 |
52 | win.quitMessage();
53 | return;
54 | }
55 |
56 | import fsys.update.simpleMain;
57 | if( fsys.update.simpleMain(
58 | "ImTip 桌面助手",
59 | "http://imtip.aardio.com/update/",
60 | io.appData("/aardio/std/ImTip/app/update"),
61 | function(version,description,status){})){
62 | return 0;
63 | }
64 |
65 | mainForm.chkSysRun.skin(style.checkBox);
66 | mainForm.chkHotkey.skin(style.checkBox);
67 | mainForm.chkJab.skin(style.checkBox);
68 | mainForm.chkFilterWindows.skin(style.checkBox);
69 | mainForm.chkWatchFullScreen.skin(style.checkBox);
70 | mainForm.chkImeBarDisabled.skin(style.checkBox);
71 | mainForm.btnHotkey.skin(style.button);
72 | mainForm.btnAiChat.skin(style.button);
73 | mainForm.btnImTip.skin(style.button);
74 |
75 | mainForm.enableDpiScaling("init");
76 |
77 | import config;
78 | if(!_ARGV.sys){
79 | if(!_ARGV.updated){
80 | mainForm.show()
81 | }
82 | else {
83 | if(!config.setting.runOnce){
84 | mainForm.show();
85 | config.setting.runOnce = true;
86 | config.setting.save();
87 | }
88 | }
89 | }
90 |
91 | //显示输入状态栏
92 | import key.ime.stateBar;
93 | imeBar = key.ime.stateBar(mainForm);
94 |
95 | imeBar.interval = config.setting.interval;
96 | imeBar.timeout = config.setting.timeout;
97 | imeBar.editorClasses = config.setting.editorClasses;
98 | imeBar.quirksMode = config.setting.updateQuirksMode();
99 |
100 | //下面创建托盘图标
101 | import win.util.tray;
102 | mainForm.tray = win.util.tray(mainForm)
103 | mainForm.tray.tip = "ImTip:桌面助手" //设置鼠标提示
104 |
105 | //响应托盘消息
106 | mainForm.onTrayMessage = {
107 | [0x205/*_WM_RBUTTONUP*/] = function(wParam){
108 | import ui.appTrayMenu;
109 | var menu = ui.appTrayMenu(mainForm); //延迟创建,避免系统启动时 DPI 缩放前创建菜单
110 |
111 | win.setForeground(mainForm.hwnd); //避免不点击菜单不会消失,父窗口隐藏也要这样做
112 | menu.popup();
113 | };
114 | [0x201/*_WM_LBUTTONDOWN*/] = function(wParam){
115 | if(..mainSettingForm) ..mainSettingForm.show(false);
116 |
117 | if(::User32.GetKeyState(0x11/*_VK_CTRL*/) & 0x8000){
118 | ..config.setting.imeBarDisabled = !..imeBar.paused;
119 | ..imeBar.imeWatch(!..config.setting.imeBarDisabled);
120 | ..mainForm.chkImeBarDisabled.checked = !..config.setting.imeBarDisabled;
121 | ..config.setting.save();
122 |
123 | win.setForeground(mainForm.hwnd);
124 | }
125 | elseif(::User32.GetKeyState(0x10/*_VK_SHIFT*/) & 0x8000){
126 | mainForm.show(false);
127 |
128 | import process.imTip;
129 | process.imTip(chat = "");
130 | }
131 | else {
132 | mainForm.show();
133 | win.setForeground(mainForm.hwnd);
134 | }
135 |
136 | };
137 | }
138 |
139 | import config;
140 | import app.util;
141 | mainForm.importImTipStyle = function(path){
142 | var buf = string.loadBuffer(path);
143 | if(!#buf) return mainForm.msgboxErr("此文件不包含有效的 ImTip 外观配置方案!");
144 |
145 | var data = string.match(buf,"\/\*ImTipConfig\{\{\*\/(.+)\/\*\}\}\*\/");
146 | if(!#data) return mainForm.msgboxErr("此文件不包含有效的 ImTip 外观配置方案!");
147 |
148 | try{
149 | var cfg = eval(data);
150 | app.util.updateTipStyle(cfg);
151 | imeBar.imeSkin(cfg);
152 |
153 | config.style.embeddingFontData = null;
154 | config.style.embeddingFontName = null;
155 | config.style.openStyle = null;
156 | config.style.border = null;
157 | config.style.embeddingFontData = null;
158 | config.style.embeddingFontName = null;
159 | config.style.openStyle = null;
160 | config.style.border = null;
161 | config.style.background = null;
162 | config.style.foreground = null;
163 | config.style.linearGradient = null;
164 | config.style.paddingLeft = null;
165 | config.style.paddingRight = null;
166 | config.style.paddingTop = null;
167 | config.style.paddingBottom = null;
168 |
169 | table.assign(config.style,cfg);
170 | config.style.save();
171 | }
172 | catch(e){
173 | mainForm.msgboxErr(e);
174 | }
175 | }
176 |
177 | mainForm.onCopyData = function(data,dataType){
178 |
179 | if(data=="start"){
180 | if(mainSettingForm){
181 | return win.setForeground(mainSettingForm.hwnd);
182 | }
183 |
184 | var frmChild = mainForm.loadForm("\dlg\frmSetting.aardio");
185 | frmChild.show();
186 | }
187 | elseif(string.endWith(data,".aardio",true) && io.exist(data) ){
188 | if(mainSettingForm) {
189 | win.setForeground(mainSettingForm.hwnd);
190 | return mainSettingForm.importStyleFromFile(data);
191 | }
192 |
193 | mainForm.importImTipStyle(data);
194 | }
195 | elseif(data=="ime-enable"){
196 | imeBar.imeWatch(true);
197 | }
198 | elseif(data=="ime-disable"){
199 | imeBar.imeWatch(false);
200 | }
201 | elseif(data=="ime-switch"){
202 | imeBar.imeWatch(!!..imeBar.paused);
203 | }
204 | else {
205 | mainForm.show();
206 | }
207 | }
208 |
209 | mainForm.onClose = function(){
210 | mainForm.show(false);
211 | return true;
212 | }
213 |
214 | if(config.style.width){
215 | app.util.updateTipStyle(config.style);
216 | imeBar.imeSkin(config.style)
217 | }
218 |
219 | if(__argvImTipConfig){
220 | win.sendCopyData(mainForm.hwnd,__argvImTipConfig);
221 | }
222 |
223 | if(config.hotkey.chkEnableHotkey){
224 | import app.hotkey;
225 | app.hotkey.update(true);
226 | }
227 |
228 | mainForm.btnImTip.oncommand = function(id,event){
229 |
230 | if(..mainHotkeyForm) ..mainHotkeyForm.hitMin();
231 | if(!..mainSettingForm) ..mainSettingForm = ..mainForm.loadForm("\dlg\frmSetting.aardio");
232 | ..mainSettingForm.show();
233 | mainForm.show(false);
234 | }
235 |
236 | mainForm.btnAiChat.oncommand = function(id,event){
237 |
238 | import process.imTip;
239 | process.imTip(chat = "");
240 |
241 | mainForm.show(false);
242 | }
243 |
244 | mainForm.btnHotkey.oncommand = function(id,event){
245 | mainForm.btnHotkey.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250';text=''}
246 | import app.hotkey;
247 | app.hotkey.edit();
248 |
249 | thread.delay(1500);
250 | mainForm.btnHotkey.disabledText = null;
251 | mainForm.show(false);
252 | }
253 |
254 | mainForm.chkSysRun.checked = config.setting.sysRun;
255 | mainForm.chkSysRun.oncommand = function(id,event){
256 | if(_STUDIO_INVOKED){
257 | error("此功能发布后可用。")
258 | }
259 |
260 | import sys.run;
261 | import process.admin;
262 |
263 | if( process.admin.isRunAs() && ! _WINXP ){
264 | import sys.runAsTask;
265 |
266 | sys.run("ImTip").delete();
267 |
268 | if(owner.checked){
269 | sys.runAsTask("ImTip","桌面助手").register("/sys");
270 | config.setting.sysRunAs = true;
271 | }
272 | else {
273 | sys.runAsTask("ImTip","桌面助手").delete();
274 | config.setting.sysRunAs = null;
275 | }
276 |
277 | mainForm.chkSysRun.text = "允许开机以管理权限启动"
278 | }
279 | else{
280 | if( config.setting.sysRunAs ){
281 | mainForm.msgboxErr('之前创建的开机启动项拥有管理权限,\n以相同的管理权限启动 ImTip 才能移除。')
282 | owner.checked = true;
283 | return ;
284 | }
285 |
286 | if(owner.checked){
287 | sys.run("ImTip").register("/sys");
288 | }
289 | else {
290 | sys.run("ImTip").delete()
291 | }
292 |
293 | mainForm.chkSysRun.text = "允许开机以普通权限启动(当前进程无管理权限)"
294 | }
295 |
296 | config.setting.sysRun = owner.checked;
297 | config.setting.save();
298 | }
299 |
300 | mainForm.chkHotkey.checked = ..config.hotkey.chkEnableHotkey;
301 | mainForm.chkHotkey.oncommand = function(id,event){
302 | ..config.hotkey.chkEnableHotkey = !..config.hotkey.chkEnableHotkey;
303 |
304 | import app.hotkey;
305 | app.hotkey.update(..config.hotkey.chkEnableHotkey);
306 | }
307 |
308 | var thrdWatcherFilterWindows;
309 | mainForm.btnFilterWindows.skin(style.transButton)
310 | mainForm.btnFilterWindows.oncommand = function(id,event){
311 | mainForm.btnFilterWindows.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250';text=''}
312 |
313 | import app.filterWindows;
314 | app.filterWindows.edit();
315 |
316 | thread.delay(1500);
317 | mainForm.btnFilterWindows.disabledText = null;
318 | }
319 |
320 | mainForm.chkFilterWindows.oncommand = function(id,event){
321 | config.setting.filterWindows = owner.checked;
322 | config.setting.save();
323 |
324 | mainForm.btnFilterWindows.disabled = !config.setting.filterWindows;
325 | import app.filterWindows;
326 | app.filterWindows.update();
327 | }
328 | mainForm.chkFilterWindows.checked = config.setting.filterWindows;
329 | if(config.setting.filterWindows){
330 | import app.filterWindows;
331 | app.filterWindows.update();
332 |
333 | mainForm.btnFilterWindows.disabled = false;
334 | }
335 |
336 | mainForm.chkWatchFullScreen.checked = config.setting.watchFullScreen;
337 | mainForm.chkWatchFullScreen.oncommand = function(id,event){
338 | config.setting.watchFullScreen = owner.checked;
339 | config.setting.save();
340 |
341 | import app.filterWindows;
342 | app.filterWindows.watchFullScreen(config.setting.watchFullScreen);
343 | }
344 | if(config.setting.watchFullScreen){
345 | import app.filterWindows;
346 | app.filterWindows.watchFullScreen(true);
347 | }
348 |
349 | mainForm.chkImeBarDisabled.oncommand = function( id,event ){
350 | ..config.setting.imeBarDisabled = !mainForm.chkImeBarDisabled.checked;
351 | ..imeBar.imeWatch(!..config.setting.imeBarDisabled);
352 | ..config.setting.save();
353 | }
354 |
355 | mainForm.chkJab.oncommand = function(id,event){
356 | if(owner.checked){
357 | import app.jab;
358 | owner.checked = app.jab.enable(true);
359 |
360 | if(owner.checked){
361 | config.setting.jab = true;
362 | mainForm.chkJab.text = "已启用 java.accessBridge 扩展,如未生效请重启目标程序或操作系统"
363 | }
364 | else {
365 | mainForm.chkJab.text = "未启用 java.accessBridge 扩展"
366 | }
367 |
368 | }
369 | else {
370 | config.setting.jab = null;
371 | mainForm.msgbox("禁用 java.accessBridge 扩展在重启 ImTip 后生效!")
372 | }
373 |
374 | config.setting.save();
375 |
376 | }
377 |
378 | if(config.setting.jab){
379 | mainForm.chkJab.checked = true;
380 | import app.jab;
381 | app.jab.enable(true);
382 | }
383 |
384 |
385 | if(..config.setting.imeBarDisabled){
386 | imeBar.imeWatch(false);
387 | }
388 | else {
389 | mainForm.chkImeBarDisabled.checked = true;
390 | }
391 |
392 | win.loopMessage();
--------------------------------------------------------------------------------
/screenshots/ai.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/ai.gif
--------------------------------------------------------------------------------
/screenshots/cn-format.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/cn-format.gif
--------------------------------------------------------------------------------
/screenshots/cn.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/cn.gif
--------------------------------------------------------------------------------
/screenshots/color.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/color.gif
--------------------------------------------------------------------------------
/screenshots/copy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/copy.gif
--------------------------------------------------------------------------------
/screenshots/cpu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/cpu.png
--------------------------------------------------------------------------------
/screenshots/fim.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/fim.gif
--------------------------------------------------------------------------------
/screenshots/hotkey-ai.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/hotkey-ai.gif
--------------------------------------------------------------------------------
/screenshots/hotkey-save.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/hotkey-save.gif
--------------------------------------------------------------------------------
/screenshots/iconfont.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/iconfont.gif
--------------------------------------------------------------------------------
/screenshots/ime.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/ime.png
--------------------------------------------------------------------------------
/screenshots/imtip-dot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/imtip-dot.gif
--------------------------------------------------------------------------------
/screenshots/imtip.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/imtip.gif
--------------------------------------------------------------------------------
/screenshots/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/menu.png
--------------------------------------------------------------------------------
/screenshots/offset.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/offset.gif
--------------------------------------------------------------------------------
/screenshots/padding.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/padding.gif
--------------------------------------------------------------------------------
/screenshots/uwp.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/uwp.gif
--------------------------------------------------------------------------------
/screenshots/web.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aardio/ImTip/11f8298dbf01a60ea82ce656af25d828519119f7/screenshots/web.gif
--------------------------------------------------------------------------------