@love-moon/conductor-cli 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/bin/conductor-chrome.js +376 -0
- package/bin/conductor-config.js +82 -0
- package/bin/conductor-daemon.js +67 -0
- package/bin/conductor-fire.js +903 -0
- package/package.json +34 -0
- package/src/daemon.js +376 -0
- package/src/fire/history.js +605 -0
- package/src/pageAutomation.js +131 -0
- package/src/providers/deepseek.js +405 -0
- package/src/providers/generic.js +6 -0
- package/src/providers/qwen.js +203 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import createDeepseekProvider from './deepseek.js';
|
|
2
|
+
|
|
3
|
+
export default function createQwenProvider(context) {
|
|
4
|
+
const base = createDeepseekProvider(context);
|
|
5
|
+
|
|
6
|
+
function isVisible(el) {
|
|
7
|
+
if (!el || !(el instanceof HTMLElement)) return false;
|
|
8
|
+
const rect = el.getBoundingClientRect();
|
|
9
|
+
if (rect.width === 0 || rect.height === 0) return false;
|
|
10
|
+
const style = window.getComputedStyle(el);
|
|
11
|
+
return style.visibility !== 'hidden' && style.display !== 'none' && style.opacity !== '0';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function findNewChatButton() {
|
|
15
|
+
const candidates = [
|
|
16
|
+
'button.sidebar-new-chat',
|
|
17
|
+
'.sidebar-new-chat',
|
|
18
|
+
'#sidebar button.qwen-chat-btn',
|
|
19
|
+
'#sidebar [aria-label*="New Chat"]',
|
|
20
|
+
'#sidebar [aria-label*="新建"]',
|
|
21
|
+
];
|
|
22
|
+
for (const selector of candidates) {
|
|
23
|
+
const el = document.querySelector(selector);
|
|
24
|
+
if (el instanceof HTMLElement) {
|
|
25
|
+
return el;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const sidebarButtons = Array.from(document.querySelectorAll('#sidebar button, #sidebar [role="button"]'));
|
|
29
|
+
return sidebarButtons.find(btn => {
|
|
30
|
+
const text = (btn.innerText || btn.textContent || '').toLowerCase();
|
|
31
|
+
const aria = `${btn.getAttribute('aria-label') || ''}`.toLowerCase();
|
|
32
|
+
return (text.includes('new chat') || aria.includes('new chat')) && isVisible(btn);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function findQwenMessages() {
|
|
37
|
+
const blocks = Array.from(
|
|
38
|
+
document.querySelectorAll('.qwen-chat-message-assistant .response-message-content .qwen-markdown'),
|
|
39
|
+
);
|
|
40
|
+
return blocks
|
|
41
|
+
.map(el => {
|
|
42
|
+
const text = (el.innerText || '').trim();
|
|
43
|
+
return text ? { element: el, text } : null;
|
|
44
|
+
})
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
base.receive_message = () => {
|
|
49
|
+
const messages = findQwenMessages();
|
|
50
|
+
if (messages.length === 0) {
|
|
51
|
+
return base.receive_message(); // fallback to generic heuristic
|
|
52
|
+
}
|
|
53
|
+
const latest = messages[messages.length - 1];
|
|
54
|
+
context.highlight(latest.element);
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
message: '获取到 AI 回复',
|
|
58
|
+
latest: latest.text,
|
|
59
|
+
count: messages.length,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
async function waitForNewChatButton(timeoutMs = 3000) {
|
|
64
|
+
const start = Date.now();
|
|
65
|
+
while (Date.now() - start < timeoutMs) {
|
|
66
|
+
const btn = findNewChatButton();
|
|
67
|
+
if (btn && isVisible(btn)) {
|
|
68
|
+
return btn;
|
|
69
|
+
}
|
|
70
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function create_task() {
|
|
76
|
+
const btn = await waitForNewChatButton();
|
|
77
|
+
if (btn) {
|
|
78
|
+
context.highlight(btn);
|
|
79
|
+
setTimeout(() => btn.click(), 500);
|
|
80
|
+
return { ok: true, message: '找到 Qwen 新建对话按钮' };
|
|
81
|
+
}
|
|
82
|
+
const fallback = base.create_task();
|
|
83
|
+
return fallback;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function findQwenInput() {
|
|
87
|
+
const selectors = ['#chat-input', '.chat-input-container textarea', '.chat-input textarea', '.chat-input'];
|
|
88
|
+
for (const selector of selectors) {
|
|
89
|
+
const el = document.querySelector(selector);
|
|
90
|
+
if (el instanceof HTMLTextAreaElement || el instanceof HTMLInputElement) {
|
|
91
|
+
// Qwen placeholder view may start with opacity 0; allow even if not visible yet.
|
|
92
|
+
if (isVisible(el) || selector === '#chat-input') return el;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const placeholderInput = Array.from(document.querySelectorAll('textarea, input[type="text"], input[type="search"]')).find(el => {
|
|
96
|
+
const placeholder = (el.getAttribute('placeholder') || '').toLowerCase();
|
|
97
|
+
return placeholder.includes('help you today') || placeholder.includes('请输入') || placeholder.includes('message');
|
|
98
|
+
});
|
|
99
|
+
return placeholderInput || null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function waitForInput(timeoutMs = 3000) {
|
|
103
|
+
const start = Date.now();
|
|
104
|
+
while (Date.now() - start < timeoutMs) {
|
|
105
|
+
const input = findQwenInput();
|
|
106
|
+
if (input) return input;
|
|
107
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function setInputValue(input, text) {
|
|
113
|
+
// Apply to all candidate inputs to handle layered placeholders.
|
|
114
|
+
const inputs = new Set([
|
|
115
|
+
input,
|
|
116
|
+
...Array.from(
|
|
117
|
+
document.querySelectorAll('#chat-input, .chat-message-input textarea, .chat-input textarea, textarea[name="chat-input"]'),
|
|
118
|
+
),
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
inputs.forEach(el => {
|
|
122
|
+
if (!(el instanceof HTMLTextAreaElement || el instanceof HTMLInputElement)) return;
|
|
123
|
+
const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
124
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, 'value');
|
|
125
|
+
const setter = descriptor?.set;
|
|
126
|
+
el.focus();
|
|
127
|
+
el.click();
|
|
128
|
+
if (setter) {
|
|
129
|
+
setter.call(el, text);
|
|
130
|
+
} else {
|
|
131
|
+
el.value = text;
|
|
132
|
+
}
|
|
133
|
+
el.dispatchEvent(
|
|
134
|
+
new InputEvent('input', {
|
|
135
|
+
bubbles: true,
|
|
136
|
+
cancelable: true,
|
|
137
|
+
data: text,
|
|
138
|
+
inputType: 'insertText',
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function dispatchEnter(input) {
|
|
146
|
+
const keyboardEventInit = {
|
|
147
|
+
bubbles: true,
|
|
148
|
+
cancelable: true,
|
|
149
|
+
key: 'Enter',
|
|
150
|
+
code: 'Enter',
|
|
151
|
+
which: 13,
|
|
152
|
+
keyCode: 13,
|
|
153
|
+
};
|
|
154
|
+
input.dispatchEvent(new KeyboardEvent('keydown', keyboardEventInit));
|
|
155
|
+
input.dispatchEvent(new KeyboardEvent('keypress', keyboardEventInit));
|
|
156
|
+
input.dispatchEvent(new KeyboardEvent('keyup', keyboardEventInit));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function findQwenSendButton() {
|
|
160
|
+
const selectors = [
|
|
161
|
+
'.chat-message-input button[type="submit"]',
|
|
162
|
+
'.chat-message-input .ant-btn-primary',
|
|
163
|
+
'.chat-message-input .ant-btn-icon-only',
|
|
164
|
+
'.chat-message-input [aria-label*="send" i]',
|
|
165
|
+
'.chat-message-input [data-testid*="send"]',
|
|
166
|
+
];
|
|
167
|
+
for (const selector of selectors) {
|
|
168
|
+
const el = document.querySelector(selector);
|
|
169
|
+
if (el instanceof HTMLElement && isVisible(el)) {
|
|
170
|
+
return el;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function send_message(text) {
|
|
177
|
+
const input = await waitForInput();
|
|
178
|
+
if (!input) {
|
|
179
|
+
return base.send_message(text);
|
|
180
|
+
}
|
|
181
|
+
setInputValue(input, text || '');
|
|
182
|
+
const sendBtn = findQwenSendButton();
|
|
183
|
+
if (sendBtn) {
|
|
184
|
+
context.highlight(sendBtn);
|
|
185
|
+
setTimeout(() => sendBtn.click(), 200);
|
|
186
|
+
} else {
|
|
187
|
+
context.highlight(input);
|
|
188
|
+
setTimeout(() => dispatchEnter(input), 200);
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
ok: true,
|
|
192
|
+
message: `填充消息${sendBtn ? '并尝试点击发送' : '并回车发送'}`,
|
|
193
|
+
sendButton: sendBtn ? 'Qwen send button' : null,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
...base,
|
|
199
|
+
create_task,
|
|
200
|
+
send_message,
|
|
201
|
+
receive_message: base.receive_message,
|
|
202
|
+
};
|
|
203
|
+
}
|