ideaco 1.1.5
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/.dockerignore +33 -0
- package/.nvmrc +1 -0
- package/ARCHITECTURE.md +394 -0
- package/Dockerfile +50 -0
- package/LICENSE +29 -0
- package/README.md +206 -0
- package/bin/i18n.js +46 -0
- package/bin/ideaco.js +494 -0
- package/deploy.sh +15 -0
- package/docker-compose.yml +30 -0
- package/electron/main.cjs +986 -0
- package/electron/preload.cjs +14 -0
- package/electron/web-backends.cjs +854 -0
- package/jsconfig.json +8 -0
- package/next.config.mjs +34 -0
- package/package.json +134 -0
- package/postcss.config.mjs +6 -0
- package/public/demo/dashboard.png +0 -0
- package/public/demo/employee.png +0 -0
- package/public/demo/messages.png +0 -0
- package/public/demo/office.png +0 -0
- package/public/demo/requirement.png +0 -0
- package/public/logo.jpeg +0 -0
- package/public/logo.png +0 -0
- package/scripts/prepare-electron.js +67 -0
- package/scripts/release.js +76 -0
- package/src/app/api/agents/[agentId]/chat/route.js +70 -0
- package/src/app/api/agents/[agentId]/conversations/route.js +35 -0
- package/src/app/api/agents/[agentId]/route.js +106 -0
- package/src/app/api/avatar/route.js +104 -0
- package/src/app/api/browse-dir/route.js +44 -0
- package/src/app/api/chat/route.js +265 -0
- package/src/app/api/company/factory-reset/route.js +43 -0
- package/src/app/api/company/route.js +82 -0
- package/src/app/api/departments/[deptId]/agents/[agentId]/dismiss/route.js +19 -0
- package/src/app/api/departments/route.js +92 -0
- package/src/app/api/group-chat-loop/events/route.js +70 -0
- package/src/app/api/group-chat-loop/route.js +94 -0
- package/src/app/api/mailbox/route.js +100 -0
- package/src/app/api/messages/route.js +14 -0
- package/src/app/api/providers/[id]/configure/route.js +21 -0
- package/src/app/api/providers/[id]/refresh-cookie/route.js +38 -0
- package/src/app/api/providers/[id]/test-cookie/route.js +28 -0
- package/src/app/api/providers/route.js +11 -0
- package/src/app/api/requirements/route.js +242 -0
- package/src/app/api/secretary/route.js +65 -0
- package/src/app/api/system/cli-backends/route.js +91 -0
- package/src/app/api/system/cron/route.js +110 -0
- package/src/app/api/system/knowledge/route.js +104 -0
- package/src/app/api/system/plugins/route.js +40 -0
- package/src/app/api/system/skills/route.js +46 -0
- package/src/app/api/system/status/route.js +46 -0
- package/src/app/api/talent-market/[profileId]/recall/route.js +22 -0
- package/src/app/api/talent-market/[profileId]/route.js +17 -0
- package/src/app/api/talent-market/route.js +26 -0
- package/src/app/api/teams/route.js +773 -0
- package/src/app/api/ws-files/[departmentId]/file/route.js +27 -0
- package/src/app/api/ws-files/[departmentId]/files/route.js +22 -0
- package/src/app/globals.css +130 -0
- package/src/app/layout.jsx +40 -0
- package/src/app/page.jsx +97 -0
- package/src/components/AgentChatModal.jsx +164 -0
- package/src/components/AgentDetailModal.jsx +425 -0
- package/src/components/AgentSpyModal.jsx +481 -0
- package/src/components/AvatarGrid.jsx +29 -0
- package/src/components/BossProfileModal.jsx +162 -0
- package/src/components/CachedAvatar.jsx +77 -0
- package/src/components/ChatPanel.jsx +219 -0
- package/src/components/ChatShared.jsx +255 -0
- package/src/components/DepartmentDetail.jsx +842 -0
- package/src/components/DepartmentView.jsx +367 -0
- package/src/components/FileReference.jsx +260 -0
- package/src/components/FilesView.jsx +465 -0
- package/src/components/GroupChatView.jsx +799 -0
- package/src/components/Mailbox.jsx +926 -0
- package/src/components/MessagesView.jsx +112 -0
- package/src/components/OnboardingGuide.jsx +209 -0
- package/src/components/OrgTree.jsx +151 -0
- package/src/components/Overview.jsx +391 -0
- package/src/components/PixelOffice.jsx +2281 -0
- package/src/components/ProviderGrid.jsx +551 -0
- package/src/components/ProvidersBoard.jsx +16 -0
- package/src/components/RequirementDetail.jsx +1279 -0
- package/src/components/RequirementsBoard.jsx +187 -0
- package/src/components/SecretarySettings.jsx +295 -0
- package/src/components/SetupWizard.jsx +388 -0
- package/src/components/Sidebar.jsx +169 -0
- package/src/components/SystemMonitor.jsx +808 -0
- package/src/components/TalentMarket.jsx +183 -0
- package/src/components/TeamDetail.jsx +697 -0
- package/src/core/agent/base-agent.js +104 -0
- package/src/core/agent/chat-store.js +602 -0
- package/src/core/agent/cli-agent/backends/claude-code/README.md +52 -0
- package/src/core/agent/cli-agent/backends/claude-code/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codebuddy/README.md +236 -0
- package/src/core/agent/cli-agent/backends/codebuddy/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codex/README.md +51 -0
- package/src/core/agent/cli-agent/backends/codex/config.js +27 -0
- package/src/core/agent/cli-agent/backends/index.js +27 -0
- package/src/core/agent/cli-agent/backends/registry.js +580 -0
- package/src/core/agent/cli-agent/index.js +154 -0
- package/src/core/agent/index.js +60 -0
- package/src/core/agent/llm-agent/client.js +320 -0
- package/src/core/agent/llm-agent/index.js +97 -0
- package/src/core/agent/message-bus.js +211 -0
- package/src/core/agent/session.js +608 -0
- package/src/core/agent/tools.js +596 -0
- package/src/core/agent/web-agent/backends/base-backend.js +180 -0
- package/src/core/agent/web-agent/backends/chatgpt/client.js +146 -0
- package/src/core/agent/web-agent/backends/chatgpt/config.js +148 -0
- package/src/core/agent/web-agent/backends/chatgpt/dom-scripts.js +303 -0
- package/src/core/agent/web-agent/backends/index.js +91 -0
- package/src/core/agent/web-agent/index.js +278 -0
- package/src/core/agent/web-agent/web-client.js +407 -0
- package/src/core/employee/base-employee.js +1088 -0
- package/src/core/employee/index.js +35 -0
- package/src/core/employee/knowledge.js +327 -0
- package/src/core/employee/lifecycle.js +990 -0
- package/src/core/employee/memory/index.js +642 -0
- package/src/core/employee/memory/store.js +143 -0
- package/src/core/employee/performance.js +224 -0
- package/src/core/employee/secretary.js +625 -0
- package/src/core/employee/skills.js +398 -0
- package/src/core/index.js +38 -0
- package/src/core/organization/company.js +2600 -0
- package/src/core/organization/department.js +737 -0
- package/src/core/organization/group-chat-loop.js +264 -0
- package/src/core/organization/index.js +8 -0
- package/src/core/organization/persistence.js +111 -0
- package/src/core/organization/team.js +267 -0
- package/src/core/organization/workforce/hr.js +377 -0
- package/src/core/organization/workforce/providers.js +468 -0
- package/src/core/organization/workforce/role-archetypes.js +805 -0
- package/src/core/organization/workforce/talent-market.js +205 -0
- package/src/core/prompts.js +532 -0
- package/src/core/requirement.js +1789 -0
- package/src/core/system/audit.js +483 -0
- package/src/core/system/cron.js +449 -0
- package/src/core/system/index.js +7 -0
- package/src/core/system/plugin.js +2183 -0
- package/src/core/utils/json-parse.js +188 -0
- package/src/core/workspace.js +239 -0
- package/src/lib/api-i18n.js +211 -0
- package/src/lib/avatar.js +268 -0
- package/src/lib/client-store.js +1025 -0
- package/src/lib/config-validator.js +483 -0
- package/src/lib/format-time.js +22 -0
- package/src/lib/hooks.js +414 -0
- package/src/lib/i18n.js +134 -0
- package/src/lib/paths.js +23 -0
- package/src/lib/store.js +72 -0
- package/src/locales/de.js +393 -0
- package/src/locales/en.js +1054 -0
- package/src/locales/es.js +393 -0
- package/src/locales/fr.js +393 -0
- package/src/locales/ja.js +501 -0
- package/src/locales/ko.js +513 -0
- package/src/locales/zh.js +828 -0
- package/tailwind.config.mjs +11 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseWebBackend — Web 后端基类
|
|
3
|
+
*
|
|
4
|
+
* 所有 web 后端(ChatGPT、Claude、DeepSeek 等)都继承此类。
|
|
5
|
+
* 定义了统一的接口:DOM 脚本构建、选择器管理、站点配置等。
|
|
6
|
+
*
|
|
7
|
+
* Electron 主进程通过 IPC 调用这些方法来操作隐藏的 BrowserWindow。
|
|
8
|
+
* 注意:DOM 脚本运行在渲染进程(浏览器页面)中,必须是纯 JS 字符串。
|
|
9
|
+
*/
|
|
10
|
+
export class BaseWebBackend {
|
|
11
|
+
constructor() {
|
|
12
|
+
if (new.target === BaseWebBackend) {
|
|
13
|
+
throw new Error('BaseWebBackend is abstract — use a specific backend like ChatGPTBackend');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ======================== 必须实现 ========================
|
|
18
|
+
|
|
19
|
+
/** @returns {string} 后端唯一标识,如 'chatgpt', 'claude', 'deepseek' */
|
|
20
|
+
get id() {
|
|
21
|
+
throw new Error('Subclass must implement get id()');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @returns {string} 显示名称 */
|
|
25
|
+
get displayName() {
|
|
26
|
+
throw new Error('Subclass must implement get displayName()');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @returns {string} 站点首页 URL */
|
|
30
|
+
get siteUrl() {
|
|
31
|
+
throw new Error('Subclass must implement get siteUrl()');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @returns {string} Electron session partition 名称,如 'persist:chatgpt-login' */
|
|
35
|
+
get partition() {
|
|
36
|
+
throw new Error('Subclass must implement get partition()');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 获取指定角色的 CSS 选择器列表(优先使用用户校准的,再用默认的)
|
|
41
|
+
* @param {'input'|'send'|'response'|'stop'|'newChat'} role
|
|
42
|
+
* @returns {string[]}
|
|
43
|
+
*/
|
|
44
|
+
getSelectors(role) {
|
|
45
|
+
throw new Error('Subclass must implement getSelectors()');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 构建"发送消息"的 DOM 脚本字符串
|
|
50
|
+
* @param {string} message - 要发送的消息文本
|
|
51
|
+
* @returns {string} 可在 webContents.executeJavaScript() 中执行的脚本
|
|
52
|
+
*/
|
|
53
|
+
buildSendMessageScript(message) {
|
|
54
|
+
throw new Error('Subclass must implement buildSendMessageScript()');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 构建"读取回复"的 DOM 脚本字符串
|
|
59
|
+
* @returns {string} 返回 { text, isStreaming, matchedSelector, elementCount } 的脚本
|
|
60
|
+
*/
|
|
61
|
+
buildReadResponseScript() {
|
|
62
|
+
throw new Error('Subclass must implement buildReadResponseScript()');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 构建"新建对话"的 DOM 脚本字符串
|
|
67
|
+
* @returns {string} 返回 { ok: true } 或导航到首页的脚本
|
|
68
|
+
*/
|
|
69
|
+
buildNewChatScript() {
|
|
70
|
+
throw new Error('Subclass must implement buildNewChatScript()');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 获取默认选择器配置
|
|
75
|
+
* @returns {object} { input: string[], send: string[], response: string[], stop: string[], newChat: string[] }
|
|
76
|
+
*/
|
|
77
|
+
getDefaultSelectors() {
|
|
78
|
+
throw new Error('Subclass must implement getDefaultSelectors()');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ======================== 可选覆写 ========================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 判断页面是否已跳转到登录页(session 失效)
|
|
85
|
+
* @param {string} url - 当前页面 URL
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
isLoginPage(url) {
|
|
89
|
+
return url.includes('auth0') || url.includes('/auth/') || url.includes('login');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 判断 URL 是否属于本后端的站点
|
|
94
|
+
* @param {string} url
|
|
95
|
+
* @returns {boolean}
|
|
96
|
+
*/
|
|
97
|
+
isOwnSite(url) {
|
|
98
|
+
return url.includes(new URL(this.siteUrl).hostname);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 判断 URL 是否是一个已有对话页面(用于会话复用)
|
|
103
|
+
* @param {string} url
|
|
104
|
+
* @returns {boolean}
|
|
105
|
+
*/
|
|
106
|
+
isConversationUrl(url) {
|
|
107
|
+
return url.includes('/c/');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 构建带模型选择的 URL(某些后端支持通过 URL 参数切换模型)
|
|
112
|
+
* @param {string} model
|
|
113
|
+
* @returns {string|null} 返回 URL 或 null 表示不支持
|
|
114
|
+
*/
|
|
115
|
+
buildModelUrl(model) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 验证 cookies 中是否包含有效的 session token
|
|
121
|
+
* @param {Array} cookies - Electron cookie 对象数组
|
|
122
|
+
* @returns {boolean}
|
|
123
|
+
*/
|
|
124
|
+
hasValidSession(cookies) {
|
|
125
|
+
return cookies.length > 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 获取收集 cookies 时需要查询的域名列表
|
|
130
|
+
* @returns {string[]}
|
|
131
|
+
*/
|
|
132
|
+
getCookieDomains() {
|
|
133
|
+
const hostname = new URL(this.siteUrl).hostname;
|
|
134
|
+
return [`.${hostname}`, hostname];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 校准流程的步骤定义(可覆写以适配不同站点的 UI)
|
|
139
|
+
* @returns {Array<{role: string|null, icon: string, type: 'select'|'pause', zh: string, en: string}>}
|
|
140
|
+
*/
|
|
141
|
+
getCalibrationSteps() {
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
role: 'newChat', icon: '➕', type: 'select',
|
|
145
|
+
zh: `这是 ${this.displayName} 首页,请点击【新建对话按钮】`,
|
|
146
|
+
en: `Click the NEW CHAT button on ${this.displayName}`,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
role: null, icon: '⌨️', type: 'pause',
|
|
150
|
+
zh: `请在 ${this.displayName} 页面中点击输入框,准备好后点击下方【继续】`,
|
|
151
|
+
en: `Click the input box in ${this.displayName}, then click CONTINUE below`,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
role: 'input', icon: '⌨️', type: 'select',
|
|
155
|
+
zh: '现在请点击【消息输入框】来录制',
|
|
156
|
+
en: 'Now click the MESSAGE INPUT BOX to record it',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
role: null, icon: '✏️', type: 'pause',
|
|
160
|
+
zh: '请在输入框中输入一条消息(不要发送),准备好后点击【继续】',
|
|
161
|
+
en: 'Type a message in the input box (do NOT send yet), then click CONTINUE',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
role: 'send', icon: '📤', type: 'select',
|
|
165
|
+
zh: '现在请点击【发送按钮】来录制',
|
|
166
|
+
en: 'Now click the SEND BUTTON to record it',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
role: null, icon: '⏳', type: 'pause',
|
|
170
|
+
zh: '请点击发送并等待 AI 回复完成,然后点击【继续】',
|
|
171
|
+
en: 'Click send and wait for the AI reply to finish, then click CONTINUE',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
role: 'response', icon: '💬', type: 'select',
|
|
175
|
+
zh: '请点击最后一条【AI 回复气泡】(回复文字区域,注意选最后一条)',
|
|
176
|
+
en: 'Click the LAST AI reply bubble (the text area of the reply)',
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatGPTBackend — ChatGPT Web 后端实现
|
|
3
|
+
*
|
|
4
|
+
* 继承 BaseWebBackend,实现 ChatGPT 特有的:
|
|
5
|
+
* - DOM 脚本(输入、发送、读取回复、新建对话)
|
|
6
|
+
* - 选择器管理
|
|
7
|
+
* - 登录/Session 验证
|
|
8
|
+
* - 站点判断
|
|
9
|
+
*
|
|
10
|
+
* Electron 主进程通过此后端获取 DOM 脚本,操作隐藏的 BrowserWindow。
|
|
11
|
+
*/
|
|
12
|
+
import { BaseWebBackend } from '../base-backend.js';
|
|
13
|
+
import {
|
|
14
|
+
CHATGPT_CONFIG,
|
|
15
|
+
DEFAULT_SELECTORS,
|
|
16
|
+
getSelectors,
|
|
17
|
+
loadRecordedSelectors,
|
|
18
|
+
saveRecordedSelectors,
|
|
19
|
+
setSelectorsFilePath,
|
|
20
|
+
} from './config.js';
|
|
21
|
+
import {
|
|
22
|
+
buildSendMessageScript,
|
|
23
|
+
buildReadResponseScript,
|
|
24
|
+
buildNewChatScript,
|
|
25
|
+
buildDiagnosticScript,
|
|
26
|
+
buildLastResortExtractionScript,
|
|
27
|
+
buildResponseSelectorGeneralizationScript,
|
|
28
|
+
} from './dom-scripts.js';
|
|
29
|
+
|
|
30
|
+
export class ChatGPTBackend extends BaseWebBackend {
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ======================== 基本信息 ========================
|
|
36
|
+
|
|
37
|
+
get id() {
|
|
38
|
+
return CHATGPT_CONFIG.id;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get displayName() {
|
|
42
|
+
return CHATGPT_CONFIG.displayName;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get siteUrl() {
|
|
46
|
+
return CHATGPT_CONFIG.siteUrl;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get partition() {
|
|
50
|
+
return CHATGPT_CONFIG.partition;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get userAgent() {
|
|
54
|
+
return CHATGPT_CONFIG.userAgent;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ======================== 选择器 ========================
|
|
58
|
+
|
|
59
|
+
getSelectors(role) {
|
|
60
|
+
return getSelectors(role);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getDefaultSelectors() {
|
|
64
|
+
return { ...DEFAULT_SELECTORS };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 设置选择器文件路径(由 Electron 层调用)
|
|
69
|
+
*/
|
|
70
|
+
setSelectorsFilePath(filePath) {
|
|
71
|
+
setSelectorsFilePath(filePath);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 加载已校准的选择器
|
|
76
|
+
*/
|
|
77
|
+
loadRecordedSelectors() {
|
|
78
|
+
return loadRecordedSelectors();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 保存校准的选择器
|
|
83
|
+
*/
|
|
84
|
+
saveRecordedSelectors(data) {
|
|
85
|
+
saveRecordedSelectors(data);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ======================== DOM 脚本 ========================
|
|
89
|
+
|
|
90
|
+
buildSendMessageScript(message) {
|
|
91
|
+
return buildSendMessageScript(message);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
buildReadResponseScript() {
|
|
95
|
+
return buildReadResponseScript();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
buildNewChatScript() {
|
|
99
|
+
return buildNewChatScript();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
buildDiagnosticScript() {
|
|
103
|
+
return buildDiagnosticScript();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
buildLastResortExtractionScript() {
|
|
107
|
+
return buildLastResortExtractionScript();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
buildResponseSelectorGeneralizationScript(specificSelector) {
|
|
111
|
+
return buildResponseSelectorGeneralizationScript(specificSelector);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ======================== 站点与登录 ========================
|
|
115
|
+
|
|
116
|
+
isLoginPage(url) {
|
|
117
|
+
return url.includes('auth0') || url.includes('/auth/') || url.includes('login');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
isOwnSite(url) {
|
|
121
|
+
return url.includes('chatgpt.com');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
isConversationUrl(url) {
|
|
125
|
+
return url.includes('/c/');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
buildModelUrl(model) {
|
|
129
|
+
if (model && model !== 'auto') {
|
|
130
|
+
return `https://chatgpt.com/?model=${model}`;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
hasValidSession(cookies) {
|
|
136
|
+
const tokenNames = CHATGPT_CONFIG.sessionTokenNames;
|
|
137
|
+
return cookies.some(c => tokenNames.includes(c.name));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getCookieDomains() {
|
|
141
|
+
return CHATGPT_CONFIG.cookieDomains;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** 全局单例 */
|
|
146
|
+
export const chatgptBackend = new ChatGPTBackend();
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatGPT 配置 — 选择器、URL、Session 配置
|
|
3
|
+
*
|
|
4
|
+
* 集中管理所有 ChatGPT 特有的配置项:
|
|
5
|
+
* - CSS 选择器(输入框、发送按钮、回复气泡、停止按钮、新建对话)
|
|
6
|
+
* - 站点 URL 和 session partition
|
|
7
|
+
* - Cookie 域名和 session token 名称
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
// 选择器持久化文件路径(运行时由 Electron 层注入)
|
|
13
|
+
let _selectorsFilePath = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 设置选择器持久化文件路径(由 Electron 主进程调用)
|
|
17
|
+
* @param {string} filePath
|
|
18
|
+
*/
|
|
19
|
+
export function setSelectorsFilePath(filePath) {
|
|
20
|
+
_selectorsFilePath = filePath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ChatGPT 站点配置
|
|
25
|
+
*/
|
|
26
|
+
export const CHATGPT_CONFIG = {
|
|
27
|
+
id: 'chatgpt',
|
|
28
|
+
displayName: 'ChatGPT',
|
|
29
|
+
siteUrl: 'https://chatgpt.com/',
|
|
30
|
+
partition: 'persist:chatgpt-login',
|
|
31
|
+
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.191 Safari/537.36',
|
|
32
|
+
|
|
33
|
+
/** Cookie 相关 */
|
|
34
|
+
cookieDomains: ['.chatgpt.com', 'chatgpt.com', '.openai.com'],
|
|
35
|
+
sessionTokenNames: [
|
|
36
|
+
'__Secure-next-auth.session-token',
|
|
37
|
+
'__Secure-next-auth.session-token.0',
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 默认 CSS 选择器
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_SELECTORS = {
|
|
45
|
+
input: [
|
|
46
|
+
// Calibrated 2026-03
|
|
47
|
+
'p.placeholder',
|
|
48
|
+
'#prompt-textarea',
|
|
49
|
+
'textarea[placeholder]',
|
|
50
|
+
'textarea',
|
|
51
|
+
'[contenteditable="true"][data-placeholder]',
|
|
52
|
+
"[contenteditable='true']",
|
|
53
|
+
],
|
|
54
|
+
send: [
|
|
55
|
+
// Calibrated 2026-03
|
|
56
|
+
'#composer-submit-button',
|
|
57
|
+
'button[data-testid="send-button"]',
|
|
58
|
+
'button[aria-label="Send prompt"]',
|
|
59
|
+
'button[aria-label*="Send"]',
|
|
60
|
+
'button[aria-label*="send"]',
|
|
61
|
+
"form button[type='submit']",
|
|
62
|
+
"form button:last-child",
|
|
63
|
+
],
|
|
64
|
+
response: [
|
|
65
|
+
// Calibrated 2026-03
|
|
66
|
+
'div.text-base.my-auto',
|
|
67
|
+
// ChatGPT 2025+ structures
|
|
68
|
+
'article[data-testid^="conversation-turn-"] div[data-message-author-role="assistant"]',
|
|
69
|
+
'article[data-testid^="conversation-turn-"]',
|
|
70
|
+
'[data-testid^="conversation-turn-"]',
|
|
71
|
+
// Classic structures
|
|
72
|
+
'div[data-message-author-role="assistant"]',
|
|
73
|
+
'.agent-turn [data-message-author-role="assistant"]',
|
|
74
|
+
// Broad fallbacks
|
|
75
|
+
'[class*="markdown"]',
|
|
76
|
+
'.prose',
|
|
77
|
+
],
|
|
78
|
+
stop: [
|
|
79
|
+
'button[data-testid="stop-button"]',
|
|
80
|
+
'button[aria-label="Stop streaming"]',
|
|
81
|
+
'button[aria-label="Stop generating"]',
|
|
82
|
+
'button[aria-label*="Stop"]',
|
|
83
|
+
'button.bg-black .icon-lg',
|
|
84
|
+
],
|
|
85
|
+
newChat: [
|
|
86
|
+
// Calibrated 2026-03
|
|
87
|
+
'a[data-testid="create-new-chat-button"]',
|
|
88
|
+
'a[href="/"]',
|
|
89
|
+
'nav a:first-child',
|
|
90
|
+
'button[aria-label*="New chat"]',
|
|
91
|
+
'button[aria-label*="new chat"]',
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 加载用户校准过的选择器(如果有的话)
|
|
97
|
+
* @returns {{ recorded: object, timestamp: string|null }}
|
|
98
|
+
*/
|
|
99
|
+
export function loadRecordedSelectors() {
|
|
100
|
+
if (!_selectorsFilePath) return { recorded: {}, timestamp: null };
|
|
101
|
+
try {
|
|
102
|
+
if (fs.existsSync(_selectorsFilePath)) {
|
|
103
|
+
const saved = JSON.parse(fs.readFileSync(_selectorsFilePath, 'utf8'));
|
|
104
|
+
// 超过 30 天自动过期
|
|
105
|
+
if (saved.timestamp) {
|
|
106
|
+
const ageMs = Date.now() - new Date(saved.timestamp).getTime();
|
|
107
|
+
const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
108
|
+
if (ageMs > MAX_AGE_MS) {
|
|
109
|
+
console.log('[chatgpt-config] Recorded selectors expired (>30 days old), clearing');
|
|
110
|
+
fs.unlinkSync(_selectorsFilePath);
|
|
111
|
+
return { recorded: {}, timestamp: null };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return saved;
|
|
115
|
+
}
|
|
116
|
+
} catch (e) {
|
|
117
|
+
console.error('[chatgpt-config] Failed to load selectors:', e.message);
|
|
118
|
+
}
|
|
119
|
+
return { recorded: {}, timestamp: null };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 保存用户校准的选择器
|
|
124
|
+
* @param {object} data - { recorded: object, timestamp: string }
|
|
125
|
+
*/
|
|
126
|
+
export function saveRecordedSelectors(data) {
|
|
127
|
+
if (!_selectorsFilePath) return;
|
|
128
|
+
try {
|
|
129
|
+
fs.writeFileSync(_selectorsFilePath, JSON.stringify(data, null, 2), 'utf8');
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.error('[chatgpt-config] Failed to save selectors:', e.message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 获取指定角色的有效选择器列表(用户校准优先)
|
|
137
|
+
* @param {'input'|'send'|'response'|'stop'|'newChat'} role
|
|
138
|
+
* @returns {string[]}
|
|
139
|
+
*/
|
|
140
|
+
export function getSelectors(role) {
|
|
141
|
+
const saved = loadRecordedSelectors();
|
|
142
|
+
const recorded = saved.recorded?.[role];
|
|
143
|
+
const defaults = DEFAULT_SELECTORS[role] || [];
|
|
144
|
+
if (recorded) {
|
|
145
|
+
return [recorded, ...defaults.filter(s => s !== recorded)];
|
|
146
|
+
}
|
|
147
|
+
return defaults;
|
|
148
|
+
}
|