项目文件夹

文件
wehub-resource-sync 9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:48 +08:00

363 行
14 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
import { cli, Strategy } from '@jackwener/opencli/registry';
import { htmlToMarkdown } from '@jackwener/opencli/utils';
import { ArgumentError, CommandExecutionError, TimeoutError } from '@jackwener/opencli/errors';
import {
YUANBAO_DOMAIN,
IS_VISIBLE_JS,
authRequired,
isOnYuanbao,
ensureYuanbaoPage,
hasLoginGate,
normalizeBooleanFlag,
sendYuanbaoMessage,
} from './shared.js';
const YUANBAO_RESPONSE_POLL_INTERVAL_SECONDS = 2;
const YUANBAO_MIN_WAIT_MS = 8_000;
const YUANBAO_STABLE_POLLS_REQUIRED = 3;
function sendFailure(reason, detail) {
const suffix = detail ? ` Detail: ${detail}` : '';
return new CommandExecutionError(`${reason || 'Unknown Yuanbao send failure.'}${suffix}`, 'Make sure the Yuanbao chat composer is visible and ready before retrying.');
}
function normalizeText(value) {
return typeof value === 'string' ? value.trim() : '';
}
export function convertYuanbaoHtmlToMarkdown(value) {
return htmlToMarkdown(value, (td) => {
td.addRule('table', {
filter: 'table',
replacement: (content) => `\n\n${content}\n\n`,
});
td.addRule('tableSection', {
filter: ['thead', 'tbody', 'tfoot'],
replacement: (content) => content,
});
td.addRule('tableRow', {
filter: 'tr',
replacement: (content, node) => {
const element = node;
const cells = Array.from(element.children);
const isHeaderRow = element.parentElement?.tagName === 'THEAD'
|| (cells.length > 0 && cells.every((cell) => cell.tagName === 'TH'));
const row = `${content}\n`;
if (!isHeaderRow)
return row;
const separator = `| ${cells.map(() => '---').join(' | ')} |\n`;
return `${row}${separator}`;
},
});
td.addRule('tableCell', {
filter: ['th', 'td'],
replacement: (content, node) => {
const element = node;
const index = element.parentElement ? Array.from(element.parentElement.children).indexOf(element) : 0;
const prefix = index === 0 ? '| ' : ' ';
return `${prefix}${content.trim()} |`;
},
});
});
}
export function sanitizeYuanbaoResponseText(value, promptText) {
let sanitized = value
.replace(/内容由AI生成,仅供参考/gi, '')
.replace(/重新回答/gi, '')
.trim();
if (/^(正在搜索资料|搜索资料中|正在思考|思考中)[.。…]*$/u.test(sanitized)) {
return '';
}
const prompt = promptText.trim();
if (!prompt)
return sanitized;
if (sanitized === prompt)
return '';
for (const separator of ['\n\n', '\n', '\r\n\r\n', '\r\n', ' ']) {
const prefix = `${prompt}${separator}`;
if (sanitized.startsWith(prefix)) {
sanitized = sanitized.slice(prefix.length).trim();
break;
}
}
return sanitized;
}
export function collectYuanbaoTranscriptAdditions(beforeLines, currentLines, promptText) {
const beforeSet = new Set(beforeLines);
const additions = currentLines
.filter((line) => !beforeSet.has(line))
.map((line) => sanitizeYuanbaoResponseText(line, promptText))
.filter((line) => line && line !== promptText);
return additions.join('\n').trim();
}
export function pickLatestYuanbaoAssistantCandidate(messages, baselineCount, promptText) {
const freshMessages = messages
.slice(Math.max(0, baselineCount))
.map((message) => sanitizeYuanbaoResponseText(message, promptText))
.filter(Boolean);
for (let i = freshMessages.length - 1; i >= 0; i -= 1) {
if (freshMessages[i] !== promptText.trim())
return freshMessages[i];
}
return '';
}
export function updateStableState(previousText, stableCount, nextText) {
if (!nextText)
return { previousText: '', stableCount: 0 };
if (nextText === previousText)
return { previousText, stableCount: stableCount + 1 };
return { previousText: nextText, stableCount: 0 };
}
function getTranscriptLinesScript() {
return `
(() => {
const clean = (value) => (value || '')
.replace(/\\u00a0/g, ' ')
.replace(/\\n{3,}/g, '\\n\\n')
.trim();
const root = (
document.querySelector('.agent-dialogue__content--common')
|| document.querySelector('.agent-dialogue__content')
|| document.querySelector('.agent-dialogue')
|| document.body
).cloneNode(true);
const removableSelectors = [
'.agent-dialogue__content--common__input',
'.agent-dialogue__tool',
'.agent-dialogue__content-copyright',
'.index_chatLandingBox__G7hAT',
'.index_chatLandingBoxMobile__J8i8v',
'.index_chatLandingHintList__M69Lr',
'.yb-nav',
'.agent-dialogue__content--common__input .ql-toolbar',
'.agent-dialogue__content--common__input .ql-container',
'.agent-dialogue__content--common__input .ql-editor',
'[role="dialog"]',
'iframe',
'button',
'script',
'style',
'noscript',
];
for (const selector of removableSelectors) {
root.querySelectorAll(selector).forEach((node) => node.remove());
}
const stopLines = new Set([
'元宝',
'DeepSeek',
'深度思考',
'联网搜索',
'工具',
'登录',
'安装电脑版',
'内容由AI生成,仅供参考',
'有问题,尽管问,shift+enter换行',
'立即创建团队',
'微信',
'手机',
'QQ',
'微信扫码登录',
'扫码默认已阅读并同意',
'用户服务协议',
'隐私协议',
]);
const noisyPatterns = [
/^支持文件格式[:]/,
/^文件拖动到此处即可上传/,
/^下载元宝电脑版/,
];
return clean(root.innerText || root.textContent || '')
.split('\\n')
.map((line) => clean(line))
.filter((line) => line
&& line.length <= 4000
&& !stopLines.has(line)
&& !noisyPatterns.some((pattern) => pattern.test(line)));
})()
`;
}
async function getYuanbaoTranscriptLines(page) {
const result = await page.evaluate(getTranscriptLinesScript());
return Array.isArray(result) ? result.map(normalizeText).filter(Boolean) : [];
}
async function getYuanbaoAssistantMessages(page) {
const result = await page.evaluate(`(() => {
${IS_VISIBLE_JS}
const roots = Array.from(document.querySelectorAll('.agent-chat__list__item--ai'))
.filter((node) => isVisible(node));
return roots.map((root) => {
const doneContent = root.querySelector('.hyc-content-md-done');
const markdownContent = doneContent || root.querySelector('.hyc-content-md');
const speechContent = root.querySelector('.agent-chat__speech-text');
const bubbleContent = root.querySelector('.agent-chat__bubble__content');
const content = markdownContent || speechContent || bubbleContent;
if (content instanceof HTMLElement) {
return content.innerHTML || content.textContent || '';
}
return root instanceof HTMLElement ? (root.innerHTML || root.textContent || '') : '';
}).filter(Boolean);
})()`);
return Array.isArray(result)
? result
.map((value) => convertYuanbaoHtmlToMarkdown(typeof value === 'string' ? value : ''))
.map(normalizeText)
.filter(Boolean)
: [];
}
async function getYuanbaoInternetSearchState(page) {
const result = await page.evaluate(`(() => {
${IS_VISIBLE_JS}
const button = Array.from(document.querySelectorAll('[dt-button-id="internet_search"]'))
.find((node) => isVisible(node));
if (!(button instanceof HTMLElement)) return { found: false, enabled: false };
const attr = button.getAttribute('dt-internet-search') || '';
const className = button.className || '';
return {
found: true,
enabled: attr === 'openInternetSearch' || className.includes('index_v2_active__'),
};
})()`);
return result;
}
async function setYuanbaoInternetSearch(page, enabled) {
const current = await getYuanbaoInternetSearchState(page);
if (!current.found || current.enabled === enabled)
return;
await page.evaluate(`(() => {
${IS_VISIBLE_JS}
const button = Array.from(document.querySelectorAll('[dt-button-id="internet_search"]'))
.find((node) => isVisible(node));
if (button instanceof HTMLElement) button.click();
})()`);
await page.wait(0.5);
}
async function getYuanbaoDeepThinkState(page) {
const result = await page.evaluate(`(() => {
${IS_VISIBLE_JS}
const button = Array.from(document.querySelectorAll('[dt-button-id="deep_think"]'))
.find((node) => isVisible(node));
if (!(button instanceof HTMLElement)) return { found: false, enabled: false };
const className = button.className || '';
return {
found: true,
enabled: className.includes('ThinkSelector_selected__'),
};
})()`);
return result;
}
async function setYuanbaoDeepThink(page, enabled) {
const current = await getYuanbaoDeepThinkState(page);
if (!current.found || current.enabled === enabled)
return;
await page.evaluate(`(() => {
${IS_VISIBLE_JS}
const button = Array.from(document.querySelectorAll('[dt-button-id="deep_think"]'))
.find((node) => isVisible(node));
if (button instanceof HTMLElement) button.click();
})()`);
await page.wait(0.5);
}
async function waitForYuanbaoResponse(page, baselineAssistantCount, beforeLines, prompt, timeoutSeconds) {
const startTime = Date.now();
let previousText = '';
let stableCount = 0;
let latestCandidate = '';
while (Date.now() - startTime < timeoutSeconds * 1000) {
await page.wait(YUANBAO_RESPONSE_POLL_INTERVAL_SECONDS);
if (await hasLoginGate(page))
return 'blocked';
const assistantMessages = await getYuanbaoAssistantMessages(page);
const assistantCandidate = pickLatestYuanbaoAssistantCandidate(assistantMessages, baselineAssistantCount, prompt);
const candidate = assistantCandidate || collectYuanbaoTranscriptAdditions(beforeLines, await getYuanbaoTranscriptLines(page), prompt);
if (!candidate)
continue;
latestCandidate = candidate;
const nextState = updateStableState(previousText, stableCount, candidate);
previousText = nextState.previousText;
stableCount = nextState.stableCount;
const waitedLongEnough = Date.now() - startTime >= YUANBAO_MIN_WAIT_MS;
if (waitedLongEnough && stableCount >= YUANBAO_STABLE_POLLS_REQUIRED)
return candidate;
}
return latestCandidate || null;
}
export const askCommand = cli({
site: 'yuanbao',
name: 'ask',
access: 'write',
description: 'Send a prompt to Yuanbao web chat and wait for the assistant response',
domain: YUANBAO_DOMAIN,
strategy: Strategy.COOKIE,
browser: true,
siteSession: 'persistent',
navigateBefore: false,
defaultFormat: 'plain',
args: [
{ name: 'prompt', required: true, positional: true, help: 'Prompt to send' },
{ name: 'timeout', type: 'int', required: false, help: 'Max seconds to wait (default: 60)', default: 60 },
{ name: 'search', type: 'boolean', required: false, help: 'Enable Yuanbao internet search (default: true)', default: true },
{ name: 'think', type: 'boolean', required: false, help: 'Enable Yuanbao deep thinking (default: false)', default: false },
],
columns: ['Role', 'Text'],
func: async (page, kwargs) => {
const prompt = kwargs.prompt;
const timeout = kwargs.timeout;
if (!Number.isInteger(timeout) || timeout < 1) {
throw new ArgumentError('--timeout must be a positive integer (seconds)');
}
const useSearch = normalizeBooleanFlag(kwargs.search, true);
const useThink = normalizeBooleanFlag(kwargs.think, false);
await ensureYuanbaoPage(page);
if (await hasLoginGate(page)) {
throw authRequired('Yuanbao opened a login gate before sending the prompt.');
}
await setYuanbaoInternetSearch(page, useSearch);
await setYuanbaoDeepThink(page, useThink);
const beforeAssistantMessages = await getYuanbaoAssistantMessages(page);
const beforeLines = await getYuanbaoTranscriptLines(page);
const sendResult = await sendYuanbaoMessage(page, prompt);
if (!sendResult?.ok) {
if (await hasLoginGate(page)) {
throw authRequired('Yuanbao opened a login gate instead of accepting the prompt.');
}
throw sendFailure(sendResult?.reason, sendResult?.detail);
}
const response = await waitForYuanbaoResponse(page, beforeAssistantMessages.length, beforeLines, prompt, timeout);
if (response === 'blocked') {
throw authRequired('Yuanbao opened a login gate instead of returning a chat response.');
}
if (!response) {
throw new TimeoutError('yuanbao ask', timeout, 'No Yuanbao response was observed before the timeout. Retry with --timeout, and verify the current browser session is still interactive.');
}
return [
{ Role: 'User', Text: prompt },
{ Role: 'Assistant', Text: response },
];
},
});
export const __test__ = {
collectYuanbaoTranscriptAdditions,
convertYuanbaoHtmlToMarkdown,
isOnYuanbao,
normalizeBooleanFlag,
pickLatestYuanbaoAssistantCandidate,
sanitizeYuanbaoResponseText,
updateStableState,
};