├── .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 | ![通用输入法状态跟踪提示](./screenshots/imtip.gif) 16 | 17 | 可以方便地自定义外观方案,例如[单图标方案](https://imtip.aardio.com/#dot-scheme)效果如下: 18 | 19 | ![ImTip 单图标配置方案](./screenshots/imtip-dot.gif) 20 | 21 | **再也不怕按错了!** 保持思考与输入的连续性,避免低头看任务栏或通过其他操作检查输入状态。 22 | 23 | 24 | - 不是只能看中英状态,而是关注更少的图标,了解更多的常用输入法与键盘状态。 25 | - 不是只在切换输入法才显示一次状态,当切换到新的输入位置都会及时地提醒输入法状态,可以自定义显示时长、方式、外观。 26 |
27 | 28 | ![获取输入框光标位置](./screenshots/web.gif) 29 | 30 | 有了 ImTip 就可以关掉输入法自带的状态栏,屏幕更干净了,**美滋滋再也不用看右下角** ! 31 | 32 | ![输入法自带状态栏](./screenshots/ime.png) 33 | 34 | 理论上支持所有输入法,系统自带的微软拼音,微软五笔,小小输入法,搜狗输入法,百度输入法,QQ输入法,谷歌输入法,小鹤输入法,手心输入法 …… 包括我测试的日文、韩文、西班牙语输入法都可以支持 ImTip 。 35 | 36 | ImTip 支持可视化编辑状态提示外观: 37 | 38 | ![调色](./screenshots/color.gif) 39 | 40 | 可将外观方案直接拖入 ImTip.exe 或外观设置窗口快速导入。 41 | 支持用剪贴板直接复制粘贴配置方案代码。 42 | 43 | ![复制配置方案](./screenshots/copy.gif) 44 | 45 | ImTip **CPU 占用极低**,可以通过设置「跟踪检测速度」调整 CPU 占用: 46 | 47 | ![跟踪检测速度](./screenshots/cpu.png) 48 | 49 | 默认有微小延迟 —— 这是程序的主动优化( 并非被动延迟 ),您可以加快「跟踪检测速度」(更丝滑,增加的资源占用仍然是可忽略的)。 50 | 51 | ## 二、超级热键 52 | 53 | ImTip 提供可编程扩展的「超级热键」。 54 | 例如按 Ctrl+$ 打开财务大写、日期时间大写、数学运算工具: 55 | 56 | ![超级热键调用中文大写工具](./screenshots/cn.gif) 57 | 58 | 超级热键调用 AI 大模型自动编写 aardio 代码: 59 | 60 | ![超级热键调用 AI 编写代码](./screenshots/fim.gif) 61 | 62 | 超级热键调调用 AI 大模型在 PowerShell 中写代码 63 | 64 | ![超级热键调调用 AI 大模型在 PowerShell 中写代码](https://www.aardio.com/zh-cn/doc/images/fim-ps.gif) 65 | 66 | 超级热键调调用 AI 大模型在记事本中续写与补全 67 | 68 | ![超级热键调调用 AI 大模型在记事本中续写与补全](https://www.aardio.com/zh-cn/doc/images/fim-notepad.gif) 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 | ![AI 助手](/screenshots/ai.gif) 80 | 81 | ImTip 也支持在超级热键中快助调用 AI 大模型接口,或者自动调用 AI 会话窗口。启用步骤如下: 82 | 1. 在 ImTip 主界面勾选启用超级热键。 83 | 2. 后点击『编辑超级热键』,在超级热键配置中修改 AI 接口参数。 84 | ![在超级热键中配置 AI 接口参数](/screenshots/hotkey-ai.gif) 85 | 如果没有看到上面的 AI 示例,只要删除旧版热键配置( hotkey.aardio )再重新打开即可。 86 | 3. 点击保存按钮后热键自动生效。 87 | ![保存超级热键配置](/screenshots/hotkey-save.gif) 88 | 89 | 90 | ## 托盘菜单 91 | 92 | ImTip 托盘菜单提供快捷启用系统输入法、切换双拼方案等功能。 93 | 94 | ![超级热键](./screenshots/menu.png) 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 | 57 | &#; 64 | 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{{*//*}}*/) 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 --------------------------------------------------------------------------------