webaiapi 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +184 -2
- package/bin/webapi.mjs +45 -0
- package/dist/browser/auth-capture.d.ts +110 -0
- package/dist/browser/auth-capture.d.ts.map +1 -0
- package/dist/browser/auth-capture.js +487 -0
- package/dist/browser/auth-capture.js.map +1 -0
- package/dist/browser/credentials.d.ts +102 -0
- package/dist/browser/credentials.d.ts.map +1 -0
- package/dist/browser/credentials.js +209 -0
- package/dist/browser/credentials.js.map +1 -0
- package/dist/browser/js-eval.d.ts +44 -0
- package/dist/browser/js-eval.d.ts.map +1 -0
- package/dist/browser/js-eval.js +20 -0
- package/dist/browser/js-eval.js.map +1 -0
- package/dist/browser/login-flow.d.ts +171 -0
- package/dist/browser/login-flow.d.ts.map +1 -0
- package/dist/browser/login-flow.js +606 -0
- package/dist/browser/login-flow.js.map +1 -0
- package/dist/browser/manager.d.ts +144 -0
- package/dist/browser/manager.d.ts.map +1 -0
- package/dist/browser/manager.js +586 -0
- package/dist/browser/manager.js.map +1 -0
- package/dist/browser/platforms.d.ts +96 -0
- package/dist/browser/platforms.d.ts.map +1 -0
- package/dist/browser/platforms.js +288 -0
- package/dist/browser/platforms.js.map +1 -0
- package/dist/browser/sse-capture.d.ts +23 -0
- package/dist/browser/sse-capture.d.ts.map +1 -0
- package/dist/browser/sse-capture.js +199 -0
- package/dist/browser/sse-capture.js.map +1 -0
- package/dist/browser/stealth.d.ts +12 -0
- package/dist/browser/stealth.d.ts.map +1 -0
- package/dist/browser/stealth.js +12 -0
- package/dist/browser/stealth.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +441 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +90 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +246 -0
- package/dist/config.js.map +1 -0
- package/dist/controllers/history-db.d.ts +56 -0
- package/dist/controllers/history-db.d.ts.map +1 -0
- package/dist/controllers/history-db.js +260 -0
- package/dist/controllers/history-db.js.map +1 -0
- package/dist/controllers/request-pool.d.ts +78 -0
- package/dist/controllers/request-pool.d.ts.map +1 -0
- package/dist/controllers/request-pool.js +222 -0
- package/dist/controllers/request-pool.js.map +1 -0
- package/dist/controllers/router.d.ts +172 -0
- package/dist/controllers/router.d.ts.map +1 -0
- package/dist/controllers/router.js +569 -0
- package/dist/controllers/router.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/log/api-logger.d.ts +55 -0
- package/dist/log/api-logger.d.ts.map +1 -0
- package/dist/log/api-logger.js +264 -0
- package/dist/log/api-logger.js.map +1 -0
- package/dist/log/debug-logger.d.ts +49 -0
- package/dist/log/debug-logger.d.ts.map +1 -0
- package/dist/log/debug-logger.js +285 -0
- package/dist/log/debug-logger.js.map +1 -0
- package/dist/log/logger.d.ts +16 -0
- package/dist/log/logger.d.ts.map +1 -0
- package/dist/log/logger.js +35 -0
- package/dist/log/logger.js.map +1 -0
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +90 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/providers/base.d.ts +83 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +51 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/chatglm-dom-extract.d.ts +24 -0
- package/dist/providers/chatglm-dom-extract.d.ts.map +1 -0
- package/dist/providers/chatglm-dom-extract.js +175 -0
- package/dist/providers/chatglm-dom-extract.js.map +1 -0
- package/dist/providers/chatglm-dom.d.ts +110 -0
- package/dist/providers/chatglm-dom.d.ts.map +1 -0
- package/dist/providers/chatglm-dom.js +384 -0
- package/dist/providers/chatglm-dom.js.map +1 -0
- package/dist/providers/chatglm-sse.d.ts +119 -0
- package/dist/providers/chatglm-sse.d.ts.map +1 -0
- package/dist/providers/chatglm-sse.js +241 -0
- package/dist/providers/chatglm-sse.js.map +1 -0
- package/dist/providers/chatglm.d.ts +288 -0
- package/dist/providers/chatglm.d.ts.map +1 -0
- package/dist/providers/chatglm.js +1631 -0
- package/dist/providers/chatglm.js.map +1 -0
- package/dist/routes/chat.d.ts +117 -0
- package/dist/routes/chat.d.ts.map +1 -0
- package/dist/routes/chat.js +1346 -0
- package/dist/routes/chat.js.map +1 -0
- package/dist/routes/compress.d.ts +142 -0
- package/dist/routes/compress.d.ts.map +1 -0
- package/dist/routes/compress.js +551 -0
- package/dist/routes/compress.js.map +1 -0
- package/dist/routes/failover.d.ts +34 -0
- package/dist/routes/failover.d.ts.map +1 -0
- package/dist/routes/failover.js +221 -0
- package/dist/routes/failover.js.map +1 -0
- package/dist/routes/helpers.d.ts +174 -0
- package/dist/routes/helpers.d.ts.map +1 -0
- package/dist/routes/helpers.js +525 -0
- package/dist/routes/helpers.js.map +1 -0
- package/dist/routes/meta.d.ts +31 -0
- package/dist/routes/meta.d.ts.map +1 -0
- package/dist/routes/meta.js +184 -0
- package/dist/routes/meta.js.map +1 -0
- package/dist/routes/sse.d.ts +82 -0
- package/dist/routes/sse.d.ts.map +1 -0
- package/dist/routes/sse.js +77 -0
- package/dist/routes/sse.js.map +1 -0
- package/dist/server/app.d.ts +24 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +292 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/middleware/api-key.d.ts +61 -0
- package/dist/server/middleware/api-key.d.ts.map +1 -0
- package/dist/server/middleware/api-key.js +95 -0
- package/dist/server/middleware/api-key.js.map +1 -0
- package/dist/server/middleware/rate-limit.d.ts +67 -0
- package/dist/server/middleware/rate-limit.d.ts.map +1 -0
- package/dist/server/middleware/rate-limit.js +134 -0
- package/dist/server/middleware/rate-limit.js.map +1 -0
- package/dist/server/middleware/timeout.d.ts +34 -0
- package/dist/server/middleware/timeout.d.ts.map +1 -0
- package/dist/server/middleware/timeout.js +64 -0
- package/dist/server/middleware/timeout.js.map +1 -0
- package/dist/server/types.d.ts +20 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -0
- package/dist/tools/build.d.ts +14 -0
- package/dist/tools/build.d.ts.map +1 -0
- package/dist/tools/build.js +105 -0
- package/dist/tools/build.js.map +1 -0
- package/dist/tools/clean.d.ts +32 -0
- package/dist/tools/clean.d.ts.map +1 -0
- package/dist/tools/clean.js +65 -0
- package/dist/tools/clean.js.map +1 -0
- package/dist/tools/convert.d.ts +50 -0
- package/dist/tools/convert.d.ts.map +1 -0
- package/dist/tools/convert.js +289 -0
- package/dist/tools/convert.js.map +1 -0
- package/dist/tools/names.d.ts +138 -0
- package/dist/tools/names.d.ts.map +1 -0
- package/dist/tools/names.js +578 -0
- package/dist/tools/names.js.map +1 -0
- package/dist/tools/parse.d.ts +31 -0
- package/dist/tools/parse.d.ts.map +1 -0
- package/dist/tools/parse.js +273 -0
- package/dist/tools/parse.js.map +1 -0
- package/dist/tools/placeholder.d.ts +21 -0
- package/dist/tools/placeholder.d.ts.map +1 -0
- package/dist/tools/placeholder.js +135 -0
- package/dist/tools/placeholder.js.map +1 -0
- package/dist/tools/prompts.d.ts +42 -0
- package/dist/tools/prompts.d.ts.map +1 -0
- package/dist/tools/prompts.js +69 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/protocol.d.ts +58 -0
- package/dist/tools/protocol.d.ts.map +1 -0
- package/dist/tools/protocol.js +287 -0
- package/dist/tools/protocol.js.map +1 -0
- package/dist/tools/repair.d.ts +83 -0
- package/dist/tools/repair.d.ts.map +1 -0
- package/dist/tools/repair.js +510 -0
- package/dist/tools/repair.js.map +1 -0
- package/dist/tools/workspace.d.ts +40 -0
- package/dist/tools/workspace.d.ts.map +1 -0
- package/dist/tools/workspace.js +224 -0
- package/dist/tools/workspace.js.map +1 -0
- package/dist/types/message.d.ts +87 -0
- package/dist/types/message.d.ts.map +1 -0
- package/dist/types/message.js +85 -0
- package/dist/types/message.js.map +1 -0
- package/dist/types/openai.d.ts +100 -0
- package/dist/types/openai.d.ts.map +1 -0
- package/dist/types/openai.js +66 -0
- package/dist/types/openai.js.map +1 -0
- package/dist/utils/errors.d.ts +82 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +115 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/mutex.d.ts +31 -0
- package/dist/utils/mutex.d.ts.map +1 -0
- package/dist/utils/mutex.js +61 -0
- package/dist/utils/mutex.js.map +1 -0
- package/dist/utils/prompt.d.ts +150 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +514 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/text.d.ts +30 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +70 -0
- package/dist/utils/text.js.map +1 -0
- package/package.json +67 -4
- package/postinstall.mjs +34 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatGLM 页面侧的**正文兜底提取**脚本(大脚本,单独成文件)。
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ 只在「完全没有 SSE 捕获」时才调用 —— SSE 捕获存在但 `type=text` 为空
|
|
5
|
+
* 说明模型还在 think 阶段,此时 DOM 的 `.answer` 里装的正是**思考正文**,
|
|
6
|
+
* 取它会把思考过程当成回答流给客户端(实测客户端先收到 3751 字符思考、然后停住)。
|
|
7
|
+
*
|
|
8
|
+
* 里面的过滤规则全是踩出来的:
|
|
9
|
+
* - `_isThinkingContent` / `_finalize`:思考过程与最终答案渲染在同一节点内,
|
|
10
|
+
* 必须取「思考结束 / 跳过思考」锚点**之后**的正文,否则会把用户提问回显 + 思考一起当答案。
|
|
11
|
+
* - `allowNoAnchor=true`(用于 `.answer` 这类纯答案容器):**并非每次回复都有思考块**
|
|
12
|
+
* (实测有回复无「思考结束」),旧逻辑会一直返回空串 → 表现为「等待 90s 后空回复」。
|
|
13
|
+
* - `_isBusyNotice`:并发/限流提示不是 AI 回复,必须丢弃,否则会被当成答案返回。
|
|
14
|
+
* - `_isLandingPage`:落地页强特征只有「今天,有什么新想法」与纯页脚。
|
|
15
|
+
*/
|
|
16
|
+
/** DOM 兜底正文提取(含思考剔除、噪声剔除、锚点截取)。 */
|
|
17
|
+
export declare const EXTRACT_RESPONSE_JS = "() => {\nfunction _isThinkingContent(text) {\n if (!text) return false;\n if (/^\u6784\u601D\u56DE\u590D|^\u5206\u6790\u8BF7\u6C42|^\u8DF3\u8FC7\u601D\u8003/.test(text)) return true;\n if (/\\n\u6784\u601D\u56DE\u590D[:\uFF1A]|\\n\u5206\u6790\u8BF7\u6C42[:\uFF1A]/.test(text)) return true;\n if (/^\u601D\u8003\u7ED3\u675F$/.test(text.trim())) return true;\n return false;\n}\n\nfunction _cleanResponseText(text) {\n if (!text) return text;\n let cleaned = text;\n cleaned = cleaned.replace(/^ChatGLM\\s*/, '');\n cleaned = cleaned.replace(/(^|\\n)\u601D\u8003\u7ED3\u675F(?!\\S)/g, '');\n cleaned = cleaned.replace(/(^|\\n)(\u601D\u8003\u4E2D[^\\n]*|\u8DF3\u8FC7\u601D\u8003|\u6784\u601D\u56DE\u590D[:\uFF1A][^\\n]*|\u5206\u6790\u8BF7\u6C42[:\uFF1A][^\\n]*)(?=\\n|$)/g, '\\n');\n cleaned = cleaned.trim();\n return cleaned;\n}\n\n// \u5E76\u53D1/\u9650\u6D41\u63D0\u793A\u4E0D\u662F AI \u56DE\u590D\uFF0C\u5FC5\u987B\u4E22\u5F03\u3002\n// \u26A0\uFE0F \u957F\u6587\u672C\u91CC\u51FA\u73B0\u8FD9\u4E9B\u8BCD\u901A\u5E38\u53EA\u662F\u300C\u63D0\u5230\u4E86\u5B83\u300D\uFF08\u5386\u53F2\u91CC\u5E26\u8FC7\u7684\u9650\u5236\u63D0\u793A\u4F1A\u88AB\u62CD\u5E73\u8FDB\u63D0\u793A\u8BCD\uFF1B\n// \u5BF9\u8BDD\u5217\u8868\u515C\u5E95\u62FF\u5230\u7684 raw \u91CC\u8FD8\u542B\u7528\u6237\u81EA\u5DF1\u7684\u63D0\u95EE\uFF09\u2192 \u957F\u5EA6\u8BBE\u4E0A\u9650\uFF0C\u907F\u514D\u8BEF\u6740\u771F\u5B9E\u56DE\u7B54\u3002\nfunction _isBusyNotice(text) {\n if (!text) return false;\n if (text.length > 200) return false;\n return /\u8BF7\u7B49\u5F85\u5176\u4ED6\u5BF9\u8BDD\u751F\u6210\u5B8C\u6BD5|\u672C\u6B21\u56DE\u7B54\u5DF2\u88AB\u7EC8\u6B62|\u8FD8\u6709\u5BF9\u8BDD\u6B63\u5728\u751F\u6210|\u6B63\u5728\u751F\u6210\u5176\u4ED6\u56DE\u7B54|\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41|\u8FC7\u4E8E\u9891\u7E41|\u8BF7\u7A0D\u540E\u518D\u8BD5/.test(text);\n}\n\n// \u672A\u5F00\u59CB\u5BF9\u8BDD\u7684\u6B22\u8FCE/\u843D\u5730\u9875\u5185\u5BB9\u3002\u53EA\u7528\u300C\u771F\u5B9E\u5BF9\u8BDD\u4E2D\u7EDD\u4E0D\u4F1A\u51FA\u73B0\u300D\u7684\u5F3A\u7279\u5F81\u3002\nfunction _isLandingPage(text) {\n if (!text) return false;\n if (/\u4ECA\u5929\uFF0C\u6709\u4EC0\u4E48\u65B0\u60F3\u6CD5/.test(text)) return true;\n if (/^\\s*(\u5185\u5BB9\u7531AI\u751F\u6210\uFF0C\u8BF7\u4ED4\u7EC6\u7504\u522B\\s*)+$/.test(text)) return true;\n return false;\n}\n\n// \u9010\u884C\u5254\u9664\u9875\u9762\u8FB9\u6846\u566A\u58F0\uFF08\u4FA7\u8FB9\u680F\u3001\u9875\u811A\u3001\u63A8\u8350\u95EE\u9898\u3001\u54C1\u724C\u6587\u6848\u7B49\uFF09\nconst _DROP_EXACT = new Set([\n '\u7528\u6237\u534F\u8BAE', '\u9690\u79C1\u653F\u7B56', '\u5F00\u6E90\u6A21\u578B', '\u505C\u6B62\u5BF9\u8BDD', '\u56DE\u7B54\u4E2D',\n '\u65B0\u5BF9\u8BDD', '\u5B66\u4E60\u642D\u5B50', 'AI\u753B\u56FE', 'AI\u9605\u8BFB', 'AI\u751F\u89C6\u9891',\n 'AgentMore', '\u4E91\u77E5\u8BC6\u5E93', '\u667A\u80FD\u4F53', '\u6700\u8FD1\u5BF9\u8BDD',\n '\u548C\u6211\u804A\u804A\u5929\u5427', 'GLM-Flash\u6781\u81F4', '\u5185\u5BB9\u7531AI\u751F\u6210\uFF0C\u8BF7\u4ED4\u7EC6\u7504\u522B', '|',\n]);\nfunction _isNoiseLine(line) {\n if (_DROP_EXACT.has(line)) return true;\n if (/^\u7528\u6237_/.test(line)) return true; // \u7528\u6237\u540D\u6807\u7B7E\n if (/^\u79EF\u5206\\s*[\\d.]+$/.test(line)) return true; // \u79EF\u5206\u663E\u793A\n if (/^\\d{4} ChatGLM/.test(line)) return true; // \u7248\u6743\u5E74\n if (/^\u4EAC(ICP\u5907|\u516C\u7F51\u5B89\u5907)/.test(line)) return true; // \u5907\u6848\u53F7\n if (/^GLM-?Flash(\u6A21\u578B)?(\u6781\u81F4)?$/.test(line)) return true;\n if (/^[\\d.]+-Flash\u6A21\u578B\u4E0A\u7EBF$/.test(line)) return true;\n if (/^\\d+\u4E2A\u6765\u6E90$/.test(line)) return true;\n if (/^\\d+\\+?\\d*\u7B49\u4E8E\u51E0\uFF1F$/.test(line)) return true; // \u63A8\u8350\u8FFD\u95EE\n // \u5F15\u7528\u6765\u6E90\u57DF\u540D\uFF08\u5982 thepaper.cn / khanacademy.org\uFF09\n if (/^[a-z0-9-]+(\\.[a-z0-9-]+)*\\.(com|cn|org|net|io|edu|gov|co)(\\.[a-z]{2})?$/i.test(line) && line.indexOf('.') > 0) return true;\n return false;\n}\nfunction _stripChrome(text) {\n if (!text) return '';\n const out = [];\n for (const raw of text.split('\\n')) {\n const line = raw.trim();\n if (!line) { out.push(''); continue; }\n if (_isNoiseLine(line)) continue;\n out.push(raw);\n }\n return out.join('\\n').replace(/\\n{3,}/g, '\\n\\n').trim();\n}\n\n// \u6700\u7EC8\u6574\u7406\uFF1A\u4F18\u5148\u53D6\u6700\u540E\u4E00\u4E2A\u300C\u601D\u8003\u7ED3\u675F / \u8DF3\u8FC7\u601D\u8003\u300D\u951A\u70B9\u4E4B\u540E\u7684\u6B63\u6587\u3002\n// ChatGLM \u7684\u601D\u8003\u8FC7\u7A0B\u4E0E\u6700\u7EC8\u7B54\u6848\u6E32\u67D3\u5728\u540C\u4E00\u8282\u70B9\u5185\uFF0C\u951A\u70B9\u4E4B\u524D\u7684\u5185\u5BB9\n// \uFF08\u7528\u6237\u63D0\u95EE\u56DE\u663E\u3001\u601D\u8003\u3001\u641C\u7D22\u8FC7\u7A0B\uFF09\u90FD\u4E0D\u662F\u7B54\u6848\u3002\n//\n// allowNoAnchor=true \u65F6\uFF08\u7528\u4E8E .answer \u8FD9\u7C7B**\u7EAF\u7B54\u6848\u5BB9\u5668**\uFF09\uFF1A\n// \u82E5\u6574\u6BB5\u90FD\u6CA1\u6709\u951A\u70B9\uFF0C\u4E14\u4E0D\u542B\u4EFB\u4F55\u601D\u8003/\u641C\u7D22\u75D5\u8FF9\uFF0C\u5219\u6574\u6BB5\u89C6\u4E3A\u7B54\u6848\u3002\n// \u5FC5\u8981\u6027\uFF1A\u5E76\u975E\u6BCF\u6B21\u56DE\u590D\u90FD\u6709\u601D\u8003\u5757\uFF08\u5B9E\u6D4B\u6709\u56DE\u590D\u65E0\u300C\u601D\u8003\u7ED3\u675F\u300D\uFF09\uFF0C\n// \u6B64\u65F6\u65E7\u903B\u8F91\u4F1A\u4E00\u76F4\u8FD4\u56DE\u7A7A\u4E32 \u2192 \u8868\u73B0\u4E3A\u300C\u7B49\u5F85 90s \u540E\u7A7A\u56DE\u590D\u300D\u3002\nfunction _finalize(text, allowNoAnchor) {\n if (!text) return '';\n let t = String(text);\n let idx = -1, alen = 0;\n for (const a of ['\u601D\u8003\u7ED3\u675F', '\u8DF3\u8FC7\u601D\u8003']) {\n const i = t.lastIndexOf(a);\n if (i > idx) { idx = i; alen = a.length; }\n }\n if (idx < 0) {\n if (!allowNoAnchor) return '';\n if (/\u601D\u8003\u4E2D|\u641C\u7D22\u4E2D|\u6B63\u5728\u67E5\u8BE2|\u6784\u601D\u56DE\u590D|\u5206\u6790\u8BF7\u6C42/.test(t)) return '';\n } else {\n t = t.slice(idx + alen);\n }\n t = _cleanResponseText(t);\n t = _stripChrome(t);\n return t;\n}\n\n// \u53D6\u5339\u914D\u96C6\u5408\u4E2D\u7684\u6700\u540E\u4E00\u4E2A\u5143\u7D20\uFF08\u6700\u65B0\u4E00\u6B21\u56DE\u7B54\uFF09\nfunction _lastText(sel) {\n const els = document.querySelectorAll(sel);\n if (!els.length) return '';\n return (els[els.length - 1].innerText || '').trim();\n}\n\n// \u6309\u4F18\u5148\u7EA7\u5C1D\u8BD5\u5404\u7B54\u6848\u5BB9\u5668\uFF08ChatGLM \u5B9E\u9645\u7ED3\u6784\u4E3A .answer\uFF09\nconst _SELECTORS = [\n '.answer',\n '.answer-content',\n '.answer-content-wrap',\n '.markdown-body',\n];\nfor (const sel of _SELECTORS) {\n const raw = _lastText(sel);\n if (!raw) continue;\n if (_isLandingPage(raw)) continue;\n // \u5E76\u53D1\u9650\u5236\u63D0\u793A\u4E0D\u662F\u7B54\u6848\uFF0C\u76F4\u63A5\u4E22\u5F03\uFF08\u5426\u5219\u4F1A\u88AB\u5F53\u6210\u56DE\u590D\u8FD4\u56DE\uFF09\n if (_isBusyNotice(raw)) continue;\n // \u8FD9\u4E9B\u662F\u7EAF\u7B54\u6848\u5BB9\u5668\uFF0C\u5141\u8BB8\u300C\u65E0\u601D\u8003\u5757\u300D\u65F6\u6574\u6BB5\u4F5C\u4E3A\u7B54\u6848\n const fin = _finalize(raw, true);\n if (fin && !_isBusyNotice(fin)) return fin;\n}\n\n// \u515C\u5E95\uFF1A\u6574\u4E2A\u5BF9\u8BDD\u5217\u8868\uFF08\u542B\u7528\u6237\u63D0\u95EE\u4E0E\u63A8\u8350\u8FFD\u95EE\uFF0C\u5FC5\u987B\u4F9D\u8D56\u951A\u70B9\uFF09\nconst convEls = document.querySelectorAll('.session-block .conversation-container');\nif (convEls.length) {\n const el = convEls[convEls.length - 1];\n const listEl = el.querySelector('.conversation-list-outer');\n const raw = ((listEl || el).innerText || '');\n if (!_isLandingPage(raw) && !_isBusyNotice(raw)) {\n const fin = _finalize(raw, false);\n if (fin && !_isBusyNotice(fin)) return fin;\n }\n}\n\nreturn '';\n}";
|
|
18
|
+
/**
|
|
19
|
+
* 整轮零产出时的**强制兜底**提取(再取一次 `.answer` 正文)。
|
|
20
|
+
*
|
|
21
|
+
* 仅当其中不含思考/搜索痕迹、也不是平台限制提示时才返回内容。
|
|
22
|
+
*/
|
|
23
|
+
export declare const FORCED_EXTRACT_JS = "() => {\n const els = document.querySelectorAll('.answer');\n if (!els.length) return '';\n let t = (els[els.length - 1].innerText || '');\n if (/\u601D\u8003\u4E2D|\u641C\u7D22\u4E2D|\u6B63\u5728\u67E5\u8BE2|\u6784\u601D\u56DE\u590D|\u5206\u6790\u8BF7\u6C42/.test(t)) return '';\n // \u5E76\u53D1\u9650\u5236\u63D0\u793A\u4E0D\u662F\u7B54\u6848\n // \u53EA\u6709\u300C\u77ED\u6587\u672C + \u547D\u4E2D\u5173\u952E\u8BCD\u300D\u624D\u7B97\u5E73\u53F0\u9650\u5236\u63D0\u793A\uFF08\u957F\u6587\u672C\u662F\u771F\u5B9E\u56DE\u7B54\uFF09\n if (t.length < 200 && /\u8BF7\u7B49\u5F85\u5176\u4ED6\u5BF9\u8BDD\u751F\u6210\u5B8C\u6BD5|\u672C\u6B21\u56DE\u7B54\u5DF2\u88AB\u7EC8\u6B62|\u8FD8\u6709\u5BF9\u8BDD\u6B63\u5728\u751F\u6210|\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41|\u8BF7\u7A0D\u540E\u518D\u8BD5/.test(t)) return '';\n t = t.replace(/^ChatGLM\\s*/, '');\n const lines = t.split('\\n').filter(l => {\n const s = l.trim();\n if (!s) return false;\n if (s === 'ChatGLM' || s === '\u601D\u8003\u7ED3\u675F' || s === '\u8DF3\u8FC7\u601D\u8003') return false;\n if (/^\\d+\u4E2A\u6765\u6E90$/.test(s)) return false;\n return true;\n });\n return lines.join('\\n').trim();\n}";
|
|
24
|
+
//# sourceMappingURL=chatglm-dom-extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatglm-dom-extract.d.ts","sourceRoot":"","sources":["../../src/providers/chatglm-dom-extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,mCAAmC;AACnC,eAAO,MAAM,mBAAmB,+1PAsI9B,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,stCAiB5B,CAAC"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatGLM 页面侧的**正文兜底提取**脚本(大脚本,单独成文件)。
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ 只在「完全没有 SSE 捕获」时才调用 —— SSE 捕获存在但 `type=text` 为空
|
|
5
|
+
* 说明模型还在 think 阶段,此时 DOM 的 `.answer` 里装的正是**思考正文**,
|
|
6
|
+
* 取它会把思考过程当成回答流给客户端(实测客户端先收到 3751 字符思考、然后停住)。
|
|
7
|
+
*
|
|
8
|
+
* 里面的过滤规则全是踩出来的:
|
|
9
|
+
* - `_isThinkingContent` / `_finalize`:思考过程与最终答案渲染在同一节点内,
|
|
10
|
+
* 必须取「思考结束 / 跳过思考」锚点**之后**的正文,否则会把用户提问回显 + 思考一起当答案。
|
|
11
|
+
* - `allowNoAnchor=true`(用于 `.answer` 这类纯答案容器):**并非每次回复都有思考块**
|
|
12
|
+
* (实测有回复无「思考结束」),旧逻辑会一直返回空串 → 表现为「等待 90s 后空回复」。
|
|
13
|
+
* - `_isBusyNotice`:并发/限流提示不是 AI 回复,必须丢弃,否则会被当成答案返回。
|
|
14
|
+
* - `_isLandingPage`:落地页强特征只有「今天,有什么新想法」与纯页脚。
|
|
15
|
+
*/
|
|
16
|
+
/** DOM 兜底正文提取(含思考剔除、噪声剔除、锚点截取)。 */
|
|
17
|
+
export const EXTRACT_RESPONSE_JS = `() => {
|
|
18
|
+
function _isThinkingContent(text) {
|
|
19
|
+
if (!text) return false;
|
|
20
|
+
if (/^构思回复|^分析请求|^跳过思考/.test(text)) return true;
|
|
21
|
+
if (/\\n构思回复[::]|\\n分析请求[::]/.test(text)) return true;
|
|
22
|
+
if (/^思考结束$/.test(text.trim())) return true;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function _cleanResponseText(text) {
|
|
27
|
+
if (!text) return text;
|
|
28
|
+
let cleaned = text;
|
|
29
|
+
cleaned = cleaned.replace(/^ChatGLM\\s*/, '');
|
|
30
|
+
cleaned = cleaned.replace(/(^|\\n)思考结束(?!\\S)/g, '');
|
|
31
|
+
cleaned = cleaned.replace(/(^|\\n)(思考中[^\\n]*|跳过思考|构思回复[::][^\\n]*|分析请求[::][^\\n]*)(?=\\n|$)/g, '\\n');
|
|
32
|
+
cleaned = cleaned.trim();
|
|
33
|
+
return cleaned;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 并发/限流提示不是 AI 回复,必须丢弃。
|
|
37
|
+
// ⚠️ 长文本里出现这些词通常只是「提到了它」(历史里带过的限制提示会被拍平进提示词;
|
|
38
|
+
// 对话列表兜底拿到的 raw 里还含用户自己的提问)→ 长度设上限,避免误杀真实回答。
|
|
39
|
+
function _isBusyNotice(text) {
|
|
40
|
+
if (!text) return false;
|
|
41
|
+
if (text.length > 200) return false;
|
|
42
|
+
return /请等待其他对话生成完毕|本次回答已被终止|还有对话正在生成|正在生成其他回答|请求过于频繁|过于频繁|请稍后再试/.test(text);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 未开始对话的欢迎/落地页内容。只用「真实对话中绝不会出现」的强特征。
|
|
46
|
+
function _isLandingPage(text) {
|
|
47
|
+
if (!text) return false;
|
|
48
|
+
if (/今天,有什么新想法/.test(text)) return true;
|
|
49
|
+
if (/^\\s*(内容由AI生成,请仔细甄别\\s*)+$/.test(text)) return true;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 逐行剔除页面边框噪声(侧边栏、页脚、推荐问题、品牌文案等)
|
|
54
|
+
const _DROP_EXACT = new Set([
|
|
55
|
+
'用户协议', '隐私政策', '开源模型', '停止对话', '回答中',
|
|
56
|
+
'新对话', '学习搭子', 'AI画图', 'AI阅读', 'AI生视频',
|
|
57
|
+
'AgentMore', '云知识库', '智能体', '最近对话',
|
|
58
|
+
'和我聊聊天吧', 'GLM-Flash极致', '内容由AI生成,请仔细甄别', '|',
|
|
59
|
+
]);
|
|
60
|
+
function _isNoiseLine(line) {
|
|
61
|
+
if (_DROP_EXACT.has(line)) return true;
|
|
62
|
+
if (/^用户_/.test(line)) return true; // 用户名标签
|
|
63
|
+
if (/^积分\\s*[\\d.]+$/.test(line)) return true; // 积分显示
|
|
64
|
+
if (/^\\d{4} ChatGLM/.test(line)) return true; // 版权年
|
|
65
|
+
if (/^京(ICP备|公网安备)/.test(line)) return true; // 备案号
|
|
66
|
+
if (/^GLM-?Flash(模型)?(极致)?$/.test(line)) return true;
|
|
67
|
+
if (/^[\\d.]+-Flash模型上线$/.test(line)) return true;
|
|
68
|
+
if (/^\\d+个来源$/.test(line)) return true;
|
|
69
|
+
if (/^\\d+\\+?\\d*等于几?$/.test(line)) return true; // 推荐追问
|
|
70
|
+
// 引用来源域名(如 thepaper.cn / khanacademy.org)
|
|
71
|
+
if (/^[a-z0-9-]+(\\.[a-z0-9-]+)*\\.(com|cn|org|net|io|edu|gov|co)(\\.[a-z]{2})?$/i.test(line) && line.indexOf('.') > 0) return true;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
function _stripChrome(text) {
|
|
75
|
+
if (!text) return '';
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const raw of text.split('\\n')) {
|
|
78
|
+
const line = raw.trim();
|
|
79
|
+
if (!line) { out.push(''); continue; }
|
|
80
|
+
if (_isNoiseLine(line)) continue;
|
|
81
|
+
out.push(raw);
|
|
82
|
+
}
|
|
83
|
+
return out.join('\\n').replace(/\\n{3,}/g, '\\n\\n').trim();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 最终整理:优先取最后一个「思考结束 / 跳过思考」锚点之后的正文。
|
|
87
|
+
// ChatGLM 的思考过程与最终答案渲染在同一节点内,锚点之前的内容
|
|
88
|
+
// (用户提问回显、思考、搜索过程)都不是答案。
|
|
89
|
+
//
|
|
90
|
+
// allowNoAnchor=true 时(用于 .answer 这类**纯答案容器**):
|
|
91
|
+
// 若整段都没有锚点,且不含任何思考/搜索痕迹,则整段视为答案。
|
|
92
|
+
// 必要性:并非每次回复都有思考块(实测有回复无「思考结束」),
|
|
93
|
+
// 此时旧逻辑会一直返回空串 → 表现为「等待 90s 后空回复」。
|
|
94
|
+
function _finalize(text, allowNoAnchor) {
|
|
95
|
+
if (!text) return '';
|
|
96
|
+
let t = String(text);
|
|
97
|
+
let idx = -1, alen = 0;
|
|
98
|
+
for (const a of ['思考结束', '跳过思考']) {
|
|
99
|
+
const i = t.lastIndexOf(a);
|
|
100
|
+
if (i > idx) { idx = i; alen = a.length; }
|
|
101
|
+
}
|
|
102
|
+
if (idx < 0) {
|
|
103
|
+
if (!allowNoAnchor) return '';
|
|
104
|
+
if (/思考中|搜索中|正在查询|构思回复|分析请求/.test(t)) return '';
|
|
105
|
+
} else {
|
|
106
|
+
t = t.slice(idx + alen);
|
|
107
|
+
}
|
|
108
|
+
t = _cleanResponseText(t);
|
|
109
|
+
t = _stripChrome(t);
|
|
110
|
+
return t;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 取匹配集合中的最后一个元素(最新一次回答)
|
|
114
|
+
function _lastText(sel) {
|
|
115
|
+
const els = document.querySelectorAll(sel);
|
|
116
|
+
if (!els.length) return '';
|
|
117
|
+
return (els[els.length - 1].innerText || '').trim();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 按优先级尝试各答案容器(ChatGLM 实际结构为 .answer)
|
|
121
|
+
const _SELECTORS = [
|
|
122
|
+
'.answer',
|
|
123
|
+
'.answer-content',
|
|
124
|
+
'.answer-content-wrap',
|
|
125
|
+
'.markdown-body',
|
|
126
|
+
];
|
|
127
|
+
for (const sel of _SELECTORS) {
|
|
128
|
+
const raw = _lastText(sel);
|
|
129
|
+
if (!raw) continue;
|
|
130
|
+
if (_isLandingPage(raw)) continue;
|
|
131
|
+
// 并发限制提示不是答案,直接丢弃(否则会被当成回复返回)
|
|
132
|
+
if (_isBusyNotice(raw)) continue;
|
|
133
|
+
// 这些是纯答案容器,允许「无思考块」时整段作为答案
|
|
134
|
+
const fin = _finalize(raw, true);
|
|
135
|
+
if (fin && !_isBusyNotice(fin)) return fin;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 兜底:整个对话列表(含用户提问与推荐追问,必须依赖锚点)
|
|
139
|
+
const convEls = document.querySelectorAll('.session-block .conversation-container');
|
|
140
|
+
if (convEls.length) {
|
|
141
|
+
const el = convEls[convEls.length - 1];
|
|
142
|
+
const listEl = el.querySelector('.conversation-list-outer');
|
|
143
|
+
const raw = ((listEl || el).innerText || '');
|
|
144
|
+
if (!_isLandingPage(raw) && !_isBusyNotice(raw)) {
|
|
145
|
+
const fin = _finalize(raw, false);
|
|
146
|
+
if (fin && !_isBusyNotice(fin)) return fin;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return '';
|
|
151
|
+
}`;
|
|
152
|
+
/**
|
|
153
|
+
* 整轮零产出时的**强制兜底**提取(再取一次 `.answer` 正文)。
|
|
154
|
+
*
|
|
155
|
+
* 仅当其中不含思考/搜索痕迹、也不是平台限制提示时才返回内容。
|
|
156
|
+
*/
|
|
157
|
+
export const FORCED_EXTRACT_JS = `() => {
|
|
158
|
+
const els = document.querySelectorAll('.answer');
|
|
159
|
+
if (!els.length) return '';
|
|
160
|
+
let t = (els[els.length - 1].innerText || '');
|
|
161
|
+
if (/思考中|搜索中|正在查询|构思回复|分析请求/.test(t)) return '';
|
|
162
|
+
// 并发限制提示不是答案
|
|
163
|
+
// 只有「短文本 + 命中关键词」才算平台限制提示(长文本是真实回答)
|
|
164
|
+
if (t.length < 200 && /请等待其他对话生成完毕|本次回答已被终止|还有对话正在生成|请求过于频繁|请稍后再试/.test(t)) return '';
|
|
165
|
+
t = t.replace(/^ChatGLM\\s*/, '');
|
|
166
|
+
const lines = t.split('\\n').filter(l => {
|
|
167
|
+
const s = l.trim();
|
|
168
|
+
if (!s) return false;
|
|
169
|
+
if (s === 'ChatGLM' || s === '思考结束' || s === '跳过思考') return false;
|
|
170
|
+
if (/^\\d+个来源$/.test(s)) return false;
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
173
|
+
return lines.join('\\n').trim();
|
|
174
|
+
}`;
|
|
175
|
+
//# sourceMappingURL=chatglm-dom-extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatglm-dom-extract.js","sourceRoot":"","sources":["../../src/providers/chatglm-dom-extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,mCAAmC;AACnC,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsIjC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;EAiB/B,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatGLM 页面侧脚本(`page.evaluate()` 执行的 JS)—— 小的那些。
|
|
3
|
+
*
|
|
4
|
+
* 从 `legacy-python/src/providers/chatglm_browser.py` 的 `page.evaluate("""...""")`
|
|
5
|
+
* 字符串**原样抽出**并集中放这里,好处:Provider 类只剩控制流;这些脚本可单独审阅
|
|
6
|
+
* (它们是「页面当前处于什么状态」的唯一真相)。
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ 字符串里的 `\\n` / `\\.` 是**给浏览器看的转义**(等价于 Python 的 `\\n`),
|
|
9
|
+
* 改动时请保持「反斜杠+n」两字符写法。
|
|
10
|
+
* ⚠️ 读 DOM 得到的正文**绝不作为最终答案**(Markdown 渲染会破坏内容)。
|
|
11
|
+
* 正文一律来自 SSE;DOM 只用于「诊断 / 兜底 / 等待循环的活性判断」。
|
|
12
|
+
*
|
|
13
|
+
* 大脚本在 `chatglm-dom-extract.ts`。
|
|
14
|
+
*/
|
|
15
|
+
/** 探测是否**已登录**(访客不算登录)。 */
|
|
16
|
+
export declare const PROBE_LOGGED_IN_JS = "() => {\n const nameEl = document.querySelector(\n '.sidebar-user-name, .userInfoBar, [class*=\"sidebar-user-name\"], ' +\n '[class*=\"userInfoBar\"], [class*=\"user-name\"], [class*=\"userName\"]'\n );\n const raw = nameEl ? (nameEl.innerText || '').replace(/\\n/g, ' ').trim() : '';\n const name = (raw.split(/\\s+/)[0] || '');\n let has_user_el = false;\n for (const sel of ['.user-info', '.avatar', '[class*=\"user-info\"]',\n '[class*=\"avatar\"]', '[class*=\"user\"]']) {\n for (const el of document.querySelectorAll(sel)) {\n if (el.offsetParent !== null &&\n el.getBoundingClientRect().height > 0) {\n has_user_el = true;\n break;\n }\n }\n if (has_user_el) break;\n }\n return { name: name, raw: raw.slice(0, 40), has_user_el: has_user_el };\n}";
|
|
17
|
+
/** 思考阶段检测(5 种策略,任一命中即「仍在思考」)。 */
|
|
18
|
+
export declare const DETECT_THINKING_JS = "() => {\n const thinkingEl = document.querySelector('.advance-thinking');\n if (thinkingEl && !thinkingEl.classList.contains('collapse')) return true;\n\n const statusEls = document.querySelectorAll(\n '.advance-thinking-status, .advance-thinking-status_inner, ' +\n '.advance-thinking-done, .advance-thinking-done_text, ' +\n '[class*=\"thinking-status\"], [class*=\"thinking-done\"]'\n );\n for (const el of statusEls) {\n if (el.offsetHeight <= 0) continue;\n const t = (el.innerText || '').trim();\n if (/\u6784\u601D\u56DE\u590D|\u5206\u6790\u8BF7\u6C42|\u601D\u8003\u4E2D|\u6B63\u5728\u601D\u8003|thinking/i.test(t)) {\n if (!/\u601D\u8003\u7ED3\u675F|\u5DF2\u6DF1\u5EA6\u601D\u8003|\u601D\u8003\u5B8C\u6210|ended|complete/i.test(t)) return true;\n }\n }\n\n const ansEls = document.querySelectorAll(\n '.answer-content-wrap:not(.text-advance-thinking-content), .answer-content'\n );\n for (const el of ansEls) {\n if (el.offsetHeight <= 0) continue;\n const t = (el.innerText || '').trim();\n if (/^\u6784\u601D\u56DE\u590D|^\u5206\u6790\u8BF7\u6C42|^\u8DF3\u8FC7\u601D\u8003/.test(t)) return true;\n if (/\\n\u6784\u601D\u56DE\u590D[:\uFF1A]|\\n\u5206\u6790\u8BF7\u6C42[:\uFF1A]/.test(t)) return true;\n }\n\n const stopSelectors = [\n 'button[aria-label*=\"stop\" i]', 'button[aria-label*=\"\u505C\u6B62\"]',\n 'button[class*=\"stop-generat\"]', '[class*=\"stop-generat\"]',\n ];\n for (const sel of stopSelectors) {\n try {\n const el = document.querySelector(sel);\n if (el && el.offsetHeight > 0 && !el.disabled) return true;\n } catch(e) {}\n }\n\n const loadingEls = document.querySelectorAll(\n '.search-loading-container, .loading-text, [class*=\"search-loading\"]'\n );\n for (const el of loadingEls) {\n if (el.offsetHeight > 0) return true;\n }\n return false;\n}";
|
|
19
|
+
/** 完成检测(DOM 兜底;权威信号是 SSE 的 `status == "finish"`)。 */
|
|
20
|
+
export declare const DETECT_COMPLETION_JS = "() => {\n const regenEls = document.querySelectorAll(\n '.regenerate.canuse, i.regenerate.canuse, .shim.regenerate.canuse'\n );\n for (const el of regenEls) {\n if (el.offsetHeight > 0 && el.offsetWidth > 0) return true;\n }\n\n const containerEls = document.querySelectorAll('.regenerate-part-container');\n for (const el of containerEls) {\n if (el.offsetHeight > 0 && el.offsetWidth > 0) return true;\n }\n\n return false;\n}";
|
|
21
|
+
/**
|
|
22
|
+
* 页面**最后一条** `.answer` 是否就是平台并发/限流提示。
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ 只看 `.answer`,**不看 body**:`document.body.innerText` 里还包含我们自己发出去的
|
|
25
|
+
* 提示词(历史被拍平后一起发过去)。一旦某轮出现过限流提示,这段文字就会随历史进入
|
|
26
|
+
* 后续每一轮的 prompt → 用 body 判断会**永久误判**为限流,把正常请求一律掐掉。
|
|
27
|
+
*/
|
|
28
|
+
export declare const PAGE_SHOWS_BUSY_JS = "() => {\n const els = document.querySelectorAll('.answer');\n if (!els.length) return false;\n const t = (els[els.length - 1].innerText || '').trim();\n if (!t || t.length > 120) return false;\n return /\u8BF7\u7B49\u5F85\u5176\u4ED6\u5BF9\u8BDD\u751F\u6210\u5B8C\u6BD5|\u672C\u6B21\u56DE\u7B54\u5DF2\u88AB\u7EC8\u6B62|\u8FD8\u6709\u5BF9\u8BDD\u6B63\u5728\u751F\u6210|\u6B63\u5728\u751F\u6210\u5176\u4ED6\u56DE\u7B54|\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41|\u8FC7\u4E8E\u9891\u7E41|\u8BF7\u7A0D\u540E\u518D\u8BD5/.test(t);\n}";
|
|
29
|
+
/**
|
|
30
|
+
* 读取平台限制提示**原文**(回传给客户端用)。
|
|
31
|
+
*
|
|
32
|
+
* 只截取「关键词 + 紧跟的短尾」并在第一个句号/换行处截断 ——
|
|
33
|
+
* 不要用 `[^\\n]*` 贪婪匹配整行:页面上一行里可能跟随着我们自己的提示词
|
|
34
|
+
* (曾抓到「请求过于频繁,请稍后再试。…请稍等 1~2 分钟后重试。现在输出工具调用…」)。
|
|
35
|
+
*/
|
|
36
|
+
export declare const READ_BUSY_NOTICE_JS = "() => {\n const els = document.querySelectorAll('.answer');\n const src = els.length\n ? (els[els.length - 1].innerText || '')\n : (document.body.innerText || '');\n const m = src.match(/(\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41|\u8BF7\u7B49\u5F85\u5176\u4ED6\u5BF9\u8BDD\u751F\u6210\u5B8C\u6BD5|\u8FD8\u6709\u5BF9\u8BDD\u6B63\u5728\u751F\u6210|\u6B63\u5728\u751F\u6210\u5176\u4ED6\u56DE\u7B54)([^\\n]{0,20})/);\n if (!m) return '';\n return (m[1] + (m[2] || '')).split(/[\u3002\\n]/)[0].trim();\n}";
|
|
37
|
+
/** 页面是否存在聊天输入框(判断页面是否处于可聊天状态)。 */
|
|
38
|
+
export declare const HAS_INPUT_BOX_JS = "() => {\n if (document.querySelector('textarea')) return true;\n if (document.querySelector('[contenteditable=\"true\"]')) return true;\n return false;\n}";
|
|
39
|
+
/** 统计当前对话轮数(校验消息是否真正发出)。 */
|
|
40
|
+
export declare const COUNT_CONVERSATION_ITEMS_JS = "() => document.querySelectorAll('.conversation-list-outer .item.conversation-item').length";
|
|
41
|
+
/** 读取输入框当前文本长度(-1 表示没找到输入框)。 */
|
|
42
|
+
export declare const INPUT_VALUE_LEN_JS = "() => {\n const t = document.querySelector('textarea');\n if (t) return (t.value || '').length;\n const ce = document.querySelector('[contenteditable=\"true\"]');\n if (ce) return (ce.innerText || '').length;\n return -1;\n}";
|
|
43
|
+
/** 输入框是否已被清空(ChatGLM 提交成功后会清空输入框)。 */
|
|
44
|
+
export declare const IS_INPUT_CLEARED_JS = "() => {\n const t = document.querySelector('textarea');\n if (t) return (t.value || '').trim() === '';\n const ce = document.querySelector('[contenteditable=\"true\"]');\n if (ce) return (ce.innerText || '').trim() === '';\n return false;\n}";
|
|
45
|
+
/** 清空输入框(发送前)。 */
|
|
46
|
+
export declare const CLEAR_INPUT_JS = "() => {\n const t = document.querySelector('textarea');\n if (t) { t.value = ''; }\n const ce = document.querySelector('[contenteditable=\"true\"]');\n if (ce) { ce.innerHTML = ''; }\n}";
|
|
47
|
+
/**
|
|
48
|
+
* 关闭遮挡浮层的第 1 步:点掉浮层内可见的关闭按钮。
|
|
49
|
+
*
|
|
50
|
+
* ChatGLM 基于 Element UI,页面会偶发出现遮罩层(loading/公告弹窗),
|
|
51
|
+
* 导致 Playwright 点击输入框或发送按钮时被拦截
|
|
52
|
+
* (报错 "subtree intercepts pointer events"),消息因此发不出去。
|
|
53
|
+
*/
|
|
54
|
+
export declare const DISMISS_OVERLAY_CLOSE_JS = "() => {\n const sels = [\n '.el-dialog__headerbtn', '.el-dialog__close',\n '.el-message-box__headerbtn', '.el-message-box__close',\n ];\n let n = 0;\n for (const s of sels) {\n document.querySelectorAll(s).forEach(el => {\n try { el.click(); n++; } catch (e) {}\n });\n }\n return n;\n}";
|
|
55
|
+
/** 第 2 步:统计仍然可见的遮挡浮层数量(>0 时按 Esc)。 */
|
|
56
|
+
export declare const DISMISS_OVERLAY_BLOCKING_JS = "() => {\n let n = 0;\n document.querySelectorAll('.el-overlay, .el-overlay-dialog').forEach(el => {\n const st = getComputedStyle(el);\n if (st.display !== 'none' && st.visibility !== 'hidden' &&\n el.getBoundingClientRect().height > 0) n++;\n });\n return n;\n}";
|
|
57
|
+
/** 第 3 步兜底:让残留遮罩不再拦截指针事件。 */
|
|
58
|
+
export declare const DISMISS_OVERLAY_FALLBACK_JS = "() => {\n document.querySelectorAll('.el-overlay, .el-overlay-dialog').forEach(el => {\n const st = getComputedStyle(el);\n if (st.display !== 'none') {\n el.style.pointerEvents = 'none';\n }\n });\n}";
|
|
59
|
+
/** 清空页面上的 SSE 捕获缓冲(每轮请求前调用)。 */
|
|
60
|
+
export declare const RESET_SSE_JS = "() => { if (window.__glmSSEReset) window.__glmSSEReset(); }";
|
|
61
|
+
/** 读取页面捕获到的 SSE 原始文本。 */
|
|
62
|
+
export declare const SSE_GET_JS = "() => (window.__glmSSEGet ? window.__glmSSEGet() : null)";
|
|
63
|
+
/**
|
|
64
|
+
* 侦察:读取页面捕获到的**平台请求体**。
|
|
65
|
+
*
|
|
66
|
+
* 目的:判断「能否把客户端工具定义交给平台、用它的原生函数调用通道承载客户端工具」。
|
|
67
|
+
* 见 `SSE_CAPTURE_JS` 的说明与 `docs/migration-status.md`。
|
|
68
|
+
*/
|
|
69
|
+
export declare const REQUEST_BODIES_GET_JS = "() => (window.__glmSSEBodiesGet ? window.__glmSSEBodiesGet() : null)";
|
|
70
|
+
/**
|
|
71
|
+
* 侦察:找出页面上与「联网搜索 / 深度思考 / 工具」有关的开关控件。
|
|
72
|
+
*
|
|
73
|
+
* ## 为什么要找它们(2026-10-14 实测)
|
|
74
|
+
* 平台自己发出去的 ask 请求体里有 `meta_data.is_networking`(默认 **true**)、
|
|
75
|
+
* `chat_mode: "deep_thinking"`、`reasoning_effort: "max"`:
|
|
76
|
+
* 前者让模型不停去调平台自带的联网搜索(`search` 单轮刷 57 次),
|
|
77
|
+
* 后者让每回合要「想」90~200s。这两个都是**页面 UI 状态**,
|
|
78
|
+
* 只有点到对应开关才能在发请求前改掉它们。
|
|
79
|
+
*
|
|
80
|
+
* 因此先把候选控件与本地存储键 dump 出来(`logs/chatglm_page_toggles.txt`),
|
|
81
|
+
* 拿到稳定选择器后再实现「发送前关掉它们」。
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* 发送前设置页面开关:**关掉「联网搜索」**、**把「思考强度」降到「快速」**。
|
|
85
|
+
*
|
|
86
|
+
* ## 为什么(2026-10-14 实测)
|
|
87
|
+
* 平台 ask 请求体里默认是 `is_networking: true` + `chat_mode: deep_thinking` +
|
|
88
|
+
* `reasoning_effort: max`:
|
|
89
|
+
* - 联网开着 → 模型把「bash 怎么删带空格的目录」当检索问题,单轮刷 57 次 `search`,耗 210s;
|
|
90
|
+
* - 极致思考 → 每回合「想」90~200s。
|
|
91
|
+
* 两个都是**页面 UI 状态**,所以在发送前点掉它们(探针给出选择器:
|
|
92
|
+
* `.search-btn`、`.item-think` → `.think-mode-item`)。
|
|
93
|
+
*
|
|
94
|
+
* 幂等:已经是目标状态就不点(返回 `changed:false`)。
|
|
95
|
+
* 注意:这里用页面内 `el.click()`(不受 Playwright 可见性检查影响),
|
|
96
|
+
* 因为「思考强度」子菜单是 popper —— 渲染在 DOM 里但默认隐藏。
|
|
97
|
+
*/
|
|
98
|
+
export declare const APPLY_CHAT_PREFS_JS = "() => {\n const clip = (s, n) => String(s == null ? '' : s).replace(/\\s+/g, ' ').trim().slice(0, n || 80);\n const popper = (el) => {\n try {\n const p = el.closest('[class*=\"popper\"], [class*=\"dropdown\"], [class*=\"submenu\"], [class*=\"menu\"]');\n return p ? clip(p.className, 60) : '';\n } catch (e) { return ''; }\n };\n const snapItem = (el, i) => ({\n i: i,\n text: clip(el.innerText, 30),\n cls: clip(el.className, 60),\n visible: !!(el.offsetWidth || el.offsetHeight),\n popper: popper(el),\n });\n const snap = () => ({\n searchBtn: (() => {\n const b = document.querySelector('.search-btn') ||\n document.querySelector('.operation-btn[class*=\"search\"]');\n return b ? { cls: clip(b.className, 120), html: clip(b.innerHTML, 80),\n parentCls: clip((b.parentElement || {}).className, 80) } : null;\n })(),\n thinkEntry: (() => {\n const e = document.querySelector('.item-think');\n return e ? { cls: clip(e.className, 60), text: clip(e.innerText, 30),\n visible: !!(e.offsetWidth || e.offsetHeight) } : null;\n })(),\n items: Array.prototype.slice.call(document.querySelectorAll('.think-mode-item')).map(snapItem),\n });\n\n const out = { notes: [], before: snap(), clicked: [], after: null };\n const click = (el) => { try { el.click(); return true; } catch (e) { return false; } };\n const on = (el) => /active|selected|is-checked|checked|open/i.test(String(el.className || ''));\n\n // ---- 1) \u8054\u7F51\u641C\u7D22\uFF1A\u26A0\uFE0F 2026-10-14 \u5B9E\u6D4B**\u4E0D\u6539** ----\n // .search-btn \u70B9\u51FB\u53EA\u4F1A\u5F39\u51FA\u6A21\u578B\u4E0B\u62C9\uFF08el-dropdown/model-select-item\uFF09\uFF0C\n // \u800C\u5E73\u53F0\u8BF7\u6C42\u4F53\u91CC\u7684 is_networking \u59CB\u7EC8\u4E3A true \u2014\u2014 \u5B83\u4E0D\u662F\u9875\u9762\u4E0A\u53EF\u5207\u7684\u5F00\u5173\n // \uFF08\u66F4\u50CF\u300C\u5141\u8BB8\u8054\u7F51\u300D\u7684\u80FD\u529B\u4F4D\uFF09\u3002\u6587\u6863\u89C1 docs/native-parity.md \u9636\u6BB5 3\u3002\n\n // ---- 2) \u601D\u8003\u5F3A\u5EA6\uFF1A\u9010\u4E2A\u300C\u5FEB\u901F\u300D\u9879\u90FD\u70B9\u4E00\u6B21\uFF08\u53EF\u80FD\u6709\u591A\u4E2A popper\uFF0C\u5176\u4E2D\u4E00\u4E2A\u662F\u771F\u83DC\u5355\uFF09----\n try {\n const items = Array.prototype.slice.call(document.querySelectorAll('.think-mode-item'));\n items.forEach((el, i) => {\n if (!/\u5FEB\u901F/.test(el.innerText || '')) return;\n const entry = document.querySelector('.item-think');\n if (entry) {\n try {\n entry.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));\n entry.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));\n } catch (e) {}\n }\n out.clicked.push('fast#' + i + ':onBefore=' + on(el) + ':clicked=' + click(el));\n });\n } catch (e) { out.notes.push('think:' + e); }\n\n out.after = snap();\n return out;\n}";
|
|
99
|
+
export declare const PROBE_CHAT_TOGGLES_JS = "() => {\n const clip = (s, n) => String(s == null ? '' : s).slice(0, n || 60);\n const attr = (el, name) => { try { return String(el.getAttribute(name) || ''); } catch (e) { return ''; } };\n const rec = (el) => ({\n tag: (el.tagName || '').toLowerCase(),\n cls: clip(el.className, 80),\n text: clip((el.innerText || el.textContent || '').replace(/\\s+/g, ' ').trim(), 60),\n aria: clip(attr(el, 'aria-label'), 40),\n role: clip(attr(el, 'role'), 20),\n checked: clip(attr(el, 'aria-checked') || attr(el, 'data-checked'), 10),\n title: clip(attr(el, 'title'), 40),\n visible: !!(el.offsetWidth || el.offsetHeight),\n });\n\n const out = { url: location.href, controls: [], storage: {}, matched: [] };\n const sel = [\n '.el-switch', '[role=\"switch\"]', '[aria-checked]',\n 'button', '[role=\"button\"]',\n '[class*=\"switch\"]', '[class*=\"net\"]', '[class*=\"search\"]',\n '[class*=\"tool\"]', '[class*=\"mode\"]', '[class*=\"deep\"]', '[class*=\"think\"]'\n ].join(',');\n\n document.querySelectorAll(sel).forEach((el) => {\n if (out.controls.length >= 120) return;\n const r = rec(el);\n if (!r.visible && !r.text) return;\n out.controls.push(r);\n if (/\u8054\u7F51|\u641C\u7D22|\u5DE5\u5177|\u6DF1\u5EA6|\u601D\u8003|\u6A21\u5F0F|\u6A21\u578B|net|search|tool|deep|think|mode/i.test(r.text + ' ' + r.aria + ' ' + r.cls)) {\n out.matched.push(r);\n }\n });\n\n const dump = (store, prefix) => {\n try {\n for (const k of Object.keys(store)) {\n if (Object.keys(out.storage).length >= 60) return;\n out.storage[prefix + k] = clip(store.getItem(k), 120);\n }\n } catch (e) {}\n };\n dump(window.localStorage, 'local:');\n dump(window.sessionStorage, 'sess:');\n\n return out;\n}";
|
|
100
|
+
/** 诊断:判定平台限制时的依据(`.answer` 条数 + 最后一条内容 + URL)。 */
|
|
101
|
+
export declare const BUSY_NOTICE_DIAG_JS = "() => {\n const els = document.querySelectorAll('.answer');\n return {\n n: els.length,\n tail: els.length ? (els[els.length - 1].innerText || '').slice(0, 200) : '',\n url: location.href,\n };\n}";
|
|
102
|
+
/**
|
|
103
|
+
* 零产出诊断:区分「模型没答」与「答了但选择器/锚点没匹配上」。
|
|
104
|
+
*/
|
|
105
|
+
export declare const ZERO_OUTPUT_DIAG_JS = "() => {\n const ansEls = document.querySelectorAll('.answer');\n const lastAns = ansEls.length ? (ansEls[ansEls.length-1].innerText || '') : null;\n const conv = document.querySelectorAll('.session-block .conversation-container');\n const lastConv = conv.length ? (conv[conv.length-1].innerText || '') : null;\n return {\n url: location.href,\n answerCount: ansEls.length,\n answerLen: lastAns === null ? -1 : lastAns.length,\n answerHead: lastAns === null ? '' : lastAns.slice(0, 300),\n convLen: lastConv === null ? -1 : lastConv.length,\n convHead: lastConv === null ? '' : lastConv.slice(0, 300),\n regenerate: document.querySelectorAll('.regenerate.canuse').length,\n };\n}";
|
|
106
|
+
/** 输入框与发送按钮的选择器(按优先级)。 */
|
|
107
|
+
export declare const INPUT_SELECTORS: ReadonlyArray<string>;
|
|
108
|
+
/** 发送按钮选择器(Enter 不生效时点击)。 */
|
|
109
|
+
export declare const SEND_BUTTON_SELECTORS: ReadonlyArray<string>;
|
|
110
|
+
//# sourceMappingURL=chatglm-dom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatglm-dom.d.ts","sourceRoot":"","sources":["../../src/providers/chatglm-dom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,2BAA2B;AAC3B,eAAO,MAAM,kBAAkB,04BAoB7B,CAAC;AAEH,iCAAiC;AACjC,eAAO,MAAM,kBAAkB,m9DA6C7B,CAAC;AAEH,qDAAqD;AACrD,eAAO,MAAM,oBAAoB,8dAc/B,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,8hBAM7B,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,ghBAQ9B,CAAC;AAEH,kCAAkC;AAClC,eAAO,MAAM,gBAAgB,wKAI3B,CAAC;AAEH,4BAA4B;AAC5B,eAAO,MAAM,2BAA2B,+FACsD,CAAC;AAE/F,gCAAgC;AAChC,eAAO,MAAM,kBAAkB,oPAM7B,CAAC;AAEH,sCAAsC;AACtC,eAAO,MAAM,mBAAmB,qQAM9B,CAAC;AAEH,kBAAkB;AAClB,eAAO,MAAM,cAAc,0MAKzB,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,4VAYnC,CAAC;AAEH,sCAAsC;AACtC,eAAO,MAAM,2BAA2B,gTAQtC,CAAC;AAEH,6BAA6B;AAC7B,eAAO,MAAM,2BAA2B,mPAOtC,CAAC;AAEH,gCAAgC;AAChC,eAAO,MAAM,YAAY,gEAAgE,CAAC;AAE1F,yBAAyB;AACzB,eAAO,MAAM,UAAU,6DAA6D,CAAC;AAErF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,yEAAyE,CAAC;AAE5G;;;;;;;;;;;;GAYG;AACH;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,2nGAyD9B,CAAC;AAEH,eAAO,MAAM,qBAAqB,04DA4ChC,CAAC;AAEH,kDAAkD;AAClD,eAAO,MAAM,mBAAmB,uOAO9B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,mBAAmB,8uBAc9B,CAAC;AAEH,0BAA0B;AAC1B,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,MAAM,CAKjD,CAAC;AAEF,6BAA6B;AAC7B,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,CAMvD,CAAC"}
|