1. 为什么 HTA 调 AI 接口总在 settings.json 上翻车
HTA(HTML Application,HTML 应用程序)是 Windows 桌面端一个被低估的轻量方案:一个.hta文件双击就能跑,内部用 HTML + JScript/VBScript 写界面,还能直接调WScript.Shell、ActiveXObject这些系统能力。很多人拿它做内部小工具、批量脚本面板、桌面助手。现在想给它接上 AI 能力,最省事的路径就是走一个统一的 Key/API 通道,把模型调用收敛到一份settings.json里,而不是把密钥硬编码散落在每个.hta文件里。
问题也恰恰出在这里。HTA 的运行环境是mshta.exe,它既不是标准浏览器,也不是 Node,网络请求只能靠XMLHTTP(即ActiveXObject("Msxml2.XMLHTTP")或Microsoft.XMLHTTP),JSON 解析要靠eval或JSON.parse(IE8 模式以下没有原生 JSON)。于是settings.json一旦格式不对、编码不对、字段名不对,报错信息往往只有一句「系统找不到指定的文件」或者「无效字符」,根本定位不到行号。这篇就围绕 HTA 场景,给你一份可直接复制的settings.json骨架,配上验证请求是否生效的命令行动作,以及一张错误码对照表。
适合谁看:手上有.hta小工具、想接 AI 但不想引入 Electron 或 Python 依赖的 Windows 开发者;以及已经在用统一 Key 通道、但被 HTA 的编码和请求写法卡住的人。下面所有配置都以 TaoToken 作为统一 API 通道来演示,官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api 。
2. TaoToken 前置:Key、通道与 HTA 的适配点
在写settings.json之前,先把三件事理清楚,否则后面报错会互相甩锅。
第一是 Key 的获取。登录后进控制台,在 API Keys 页面创建一个新 Key,复制出来只显示一次,务必先存到本地密码管理器。这个 Key 就是settings.json里apiKey字段的值。控制台地址:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API Keys 页面:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
第二是通道形态。TaoToken 提供的是 OpenAI 兼容的 HTTP 接口,也就是说请求体是{"model": "...", "messages": [...]}这种结构,响应里取choices[0].message.content。HTA 里用XMLHTTP发 POST,只要把Content-Type设成application/json,再带上Authorization: Bearer <key>,就能通。这一点很关键:HTA 不需要任何 SDK,纯字符串拼 JSON 即可。
第三是 HTA 的适配点,也是踩坑重灾区:
- 编码:
settings.json必须存成 UTF-8 无 BOM。带 BOM 的话,JScript 读进来第一个字符是\uFEFF,JSON.parse直接抛「无效字符」。用记事本另存为时选「UTF-8」而不是「UTF-8 带 BOM」,或者用 VS Code 右下角切编码。 - 读取方式:HTA 里读本地文件用
ActiveXObject("Scripting.FileSystemObject"),别用fetch,fetch在mshta.exe里不存在。 - 同步请求:
XMLHTTP的open第三个参数设false走同步,HTA 里同步更好调试;设true异步则要挂onreadystatechange,容易在窗口关闭时丢回调。 - 超时:
XMLHTTP没有原生 timeout,得用setTimeout配合abort()自己兜。
如果你后续要把 HTA 里的调用逻辑沉淀成长期跑的编码助手或 Agent 工作流,可以看下 Coding Plan 页面,它更适合把模型调用做成持续任务:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
3. 可复制配置:settings.json 骨架与 HTA 读取代码
先给settings.json骨架。字段名我按「一眼能懂」来设计,你可以在 HTA 里映射成请求参数。注意所有值都是字符串,HTA 里做类型转换更省心。
{ "apiBase": "https://taotoken.net/api", "apiKey": "sk-替换成你在控制台创建的Key", "model": "gpt-4o-mini", "timeoutMs": 30000, "maxTokens": 1024, "temperature": 0.7, "systemPrompt": "你是一个运行在 Windows HTA 桌面工具里的助手,回答尽量简短。", "endpoint": "/v1/chat/completions" }几个字段说明,用表格对照更清楚:
| 字段 | 作用 | 常见错误值 |
|---|---|---|
| apiBase | 接口基址,不带末尾斜杠 | 写成https://taotoken.net/api/导致双斜杠 |
| apiKey | 鉴权 Key | 复制时带了空格或换行 |
| model | 模型名 | 写了控制台里不存在的名字 |
| endpoint | 补全路径 | 漏了/v1前缀 |
| timeoutMs | 自定义超时 | 设成 0 导致立即 abort |
然后是 HTA 里读取这份配置的代码。把下面这段放进.hta的<script language="JScript">里。注意settings.json要和.hta文件放在同一目录,或者你写绝对路径。
// 读取同目录下的 settings.json function loadSettings() { var fso = new ActiveXObject("Scripting.FileSystemObject"); var htaPath = document.location.pathname.replace(/^\//, ""); var dir = fso.GetParentFolderName(htaPath); var cfgPath = fso.BuildPath(dir, "settings.json"); if (!fso.FileExists(cfgPath)) { throw new Error("配置文件不存在: " + cfgPath); } var stream = new ActiveXObject("ADODB.Stream"); stream.Type = 2; // 文本模式 stream.Charset = "utf-8"; // 关键:按 UTF-8 读 stream.Open(); stream.LoadFromFile(cfgPath); var text = stream.ReadText(); stream.Close(); // 去掉可能残留的 BOM if (text.charCodeAt(0) === 0xFEFF) { text = text.substring(1); } return JSON.parse(text); }这里用ADODB.Stream而不是fso.OpenTextFile,是因为OpenTextFile默认按 ANSI 读,中文systemPrompt会乱码。ADODB.Stream显式指定Charset = "utf-8"才稳。这一步是很多人卡半天的点:配置里写了中文提示词,结果发出去变成问号,就是读取编码没设对。
接着是发请求的核心函数:
function chat(settings, userText) { var url = settings.apiBase + settings.endpoint; var payload = { model: settings.model, max_tokens: parseInt(settings.maxTokens, 10), temperature: parseFloat(settings.temperature), messages: [ { role: "system", content: settings.systemPrompt }, { role: "user", content: userText } ] }; var xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0"); xhr.open("POST", url, false); // 同步,方便调试 xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("Authorization", "Bearer " + settings.apiKey); try { xhr.send(JSON.stringify(payload)); } catch (e) { throw new Error("请求发送失败: " + e.message); } if (xhr.status !== 200) { throw new Error("HTTP " + xhr.status + " -> " + xhr.responseText); } var resp = JSON.parse(xhr.responseText); return resp.choices[0].message.content; }Msxml2.XMLHTTP.6.0比Microsoft.XMLHTTP新,支持更好的 TLS。如果你的机器上 6.0 不存在,退回Msxml2.XMLHTTP或Microsoft.XMLHTTP。JSON.stringify在 IE8 模式下没有,如果mshta.exe报JSON 未定义,要么在<head>里加<meta http-equiv="X-UA-Compatible" content="IE=edge">,要么自己写一个简易序列化函数。
4. 验证请求是否生效:命令行动作与成功结果
配置写完别急着在 HTA 界面里点按钮,先用命令行把「Key + 通道 + 模型名」这三件事验证掉,能省掉大量在 HTA 里瞎猜的时间。
打开 CMD 或 PowerShell,用curl发一条最小请求。Windows 10 以上自带curl.exe:
curl -X POST "https://taotoken.net/api/v1/chat/completions" ^ -H "Content-Type: application/json" ^ -H "Authorization: Bearer sk-你的Key" ^ -d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"user\",\"content\":\"只回复两个字:通了\"}]}"注意 CMD 里换行用^,PowerShell 里用反引号`,或者干脆写成一行。成功的话你会看到类似这样的响应:
{ "id": "chatcmpl-xxxx", "object": "chat.completion", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "通了" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 12, "completion_tokens": 3, "total_tokens": 15 } }只要choices[0].message.content有内容,说明 Key、基址、模型名全对。这时候再回到 HTA,把settings.json里的apiKey和model对齐,基本一次就通。
如果你更想先在网页里确认模型行为、对比不同模型的输出风格,可以直接用模型对话页面试:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。在那边确认好模型名和提示词效果,再抄进settings.json,比在 HTA 里反复改配置快得多。
HTA 里验证时,建议在界面上放一个调试按钮,把原始响应打到<textarea>里,而不是只显示解析后的文本。这样一旦JSON.parse失败,你能看到原始字符串长什么样。我试过把xhr.responseText直接alert出来,发现返回的是一段 HTML 错误页,才意识到是基址写错打到了别的路径。
5. 本篇常见错排查:错误码对照与定位步骤
HTA 调 AI 接口的报错,大致分三类:读配置阶段、发请求阶段、解析响应阶段。下面这张表按现象倒查。
| 现象 / 报错 | 可能原因 | 定位动作 |
|---|---|---|
| 配置文件不存在 | 路径拼接错,document.location.pathname带盘符 | 先alert(cfgPath)看实际路径 |
| 无效字符 / JSON.parse 失败 | settings.json 带 BOM 或中文乱码 | 用 VS Code 切 UTF-8 无 BOM 重存 |
| HTTP 401 | Key 错、带空格、或已删除 | 用 curl 单独验证 Key |
| HTTP 404 | endpoint 漏了/v1或基址多了斜杠 | 打印完整 url 核对 |
| HTTP 429 | 触发限流 | 降低频率,或换用 Coding Plan 的额度 |
| HTTP 400 | model 名不存在、messages 结构错 | 对照 curl 成功请求的 body |
| 请求发送失败 / 无网络 | TLS 版本低,Microsoft.XMLHTTP不支持 | 换Msxml2.XMLHTTP.6.0 |
| JSON 未定义 | IE 模式过低 | 加X-UA-Compatible或自写序列化 |
| 中文变问号 | 读取编码非 UTF-8 | 用ADODB.Stream指定 Charset |
| 响应解析后 content 为空 | 取错字段,或模型返回了 tool_calls | 打印完整resp看结构 |
定位步骤我建议固定成三步走,别跳:
第一步,命令行 curl 通不通。不通就是 Key/基址/模型名的问题,跟 HTA 无关,先解决这个。
第二步,HTA 里把url、payload、xhr.status、xhr.responseText四个值全部alert或写进调试框。很多人只打印了 status,结果 400 的时候看不到 body 里的具体错误信息,白白多花时间。
第三步,如果 curl 通、HTA 不通,重点查编码和 TLS。编码看settings.json的 BOM,TLS 看XMLHTTP的版本号。这两个是 HTA 独有的坑,标准浏览器里根本遇不到。
还有一个隐蔽的坑:settings.json里apiKey如果是从网页复制时带了不可见字符,curl 里可能因为 shell 处理而「碰巧」能用,HTA 里却报 401。遇到这种玄学 401,把 Key 重新手打一遍,或者用JSON.stringify打印 Key 的长度,正常 Key 长度是固定的,多一个字符都能看出来。
6. 把配置沉淀成可维护的 HTA 工具
走到这里,你的 HTA 应该已经能稳定调通接口了。最后说几个让这套配置长期可维护的做法。
把settings.json和.hta放在同一目录,用相对路径读取,这样整个工具文件夹可以打包发给同事,对方只需要替换自己的 Key。不要把 Key 提交到任何代码仓库,.hta工具通常走内部分享,Key 泄露风险比想象中高。
如果你打算把 HTA 里的调用逻辑扩展成更完整的编码辅助流程,比如让模型读本地代码片段、生成补丁、再回写文件,那单靠 HTA 的同步请求会越来越吃力。这种场景更适合用 Coding Plan 把调用做成持续任务:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。HTA 负责界面和本地文件操作,模型调用走统一通道,分工清晰。
接入细节和字段说明随时可以查文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。遇到 401/404 这类鉴权和路径问题,先回 API Keys 页面确认 Key 状态:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
最后留一个实用技巧:在settings.json里加一个"debug": true字段,HTA 里判断这个值决定是否把原始请求和响应写进同目录的debug.log。排查完把debug关掉,日志文件删掉。这个开关能让你在客户现场不装任何调试工具的情况下,拿到第一手报错信息。