chatgpt-pro-research 0.2.1 → 0.2.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/lib/client.js +224 -27
- package/lib/index.js +105 -9
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -34,8 +34,135 @@ var import_react = require("react");
|
|
|
34
34
|
var RESEARCH_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-40 -76 200 200"><title>Anyvia R</title><defs><mask id="R-mono-black-standard-gap" maskUnits="userSpaceOnUse" x="-100" y="-100" width="400" height="400"><rect x="-100" y="-100" width="400" height="400" fill="white"/><path d="M77.18032292691355 52.74975779269961 A3 3 0 0 1 79.72446721538282 48.16 L108.02474889607315 48.16 A3 3 0 0 1 110.56889318454242 49.57024220730038 L139.21290427606598 95.41024220730039 A3 3 0 0 1 136.6687599875967 100 L108.36847830690638 100 A3 3 0 0 1 105.82433401843711 98.58975779269961 Z" transform="translate(-30 -12)" fill="black" stroke="black" stroke-width="6" stroke-linejoin="round"/></mask></defs><g mask="url(#R-mono-black-standard-gap)"><path d="M3.46 -40 H67 A39 39 0 0 1 67 38 H36.81354211962983 V84.54 Q36.81354211962983 88 33.35354211962983 88 H3.46 Q0 88 0 84.54 V-36.54 Q0 -40 3.46 -40 Z M36.81354211962983 -14 V12 H67 A13 13 0 0 0 67 -14 Z" fill="currentColor" fill-rule="evenodd"/></g><path d="M77.18032292691355 52.74975779269961 A3 3 0 0 1 79.72446721538282 48.16 L108.02474889607315 48.16 A3 3 0 0 1 110.56889318454242 49.57024220730038 L139.21290427606598 95.41024220730039 A3 3 0 0 1 136.6687599875967 100 L108.36847830690638 100 A3 3 0 0 1 105.82433401843711 98.58975779269961 Z" transform="translate(-30 -12)" fill="currentColor"/></svg>';
|
|
35
35
|
var DESIGN_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-22.599456503942804 -56 200 200"><title>Anyvia X</title><defs><mask id="X-mono-black-standard-gap" maskUnits="userSpaceOnUse" x="-100" y="-100" width="400" height="400"><rect x="-100" y="-100" width="400" height="400" fill="white"/><path d="M77.18032292691355 52.74975779269961 A3 3 0 0 1 79.72446721538282 48.16 L108.02474889607315 48.16 A3 3 0 0 1 110.56889318454242 49.57024220730038 L139.21290427606598 95.41024220730039 A3 3 0 0 1 136.6687599875967 100 L108.36847830690638 100 A3 3 0 0 1 105.82433401843711 98.58975779269961 Z" transform="translate(-12 0)" fill="black" stroke="black" stroke-width="6" stroke-linejoin="round"/></mask></defs><g mask="url(#X-mono-black-standard-gap)"><path d="M89.67202319894069 -10.373520654246889 A3.46 3.46 0 0 1 92.60626961164192 -12 L127.85608887910462 -12 A3.46 3.46 0 0 1 130.79033529180586 -6.706479345753113 L65.12906379317373 98.37352065424689 A3.46 3.46 0 0 1 62.19481738047249 100 L26.94499811300981 100 A3.46 3.46 0 0 1 24.010751700308575 94.70647934575311 Z" fill="currentColor"/><path d="M28.027942559241307 -6.706479345753113 A3.46 3.46 0 0 1 30.96218897194254 -12 L58.17762652153973 -12 A3.46 3.46 0 0 1 61.111872934240964 -10.373520654246889 L91.78046072595076 38.70647934575311 A3.46 3.46 0 0 1 88.84621431324952 44 L61.63077676365233 44 A3.46 3.46 0 0 1 58.69653035095109 42.37352065424689 Z" fill="currentColor"/></g><path d="M77.18032292691355 52.74975779269961 A3 3 0 0 1 79.72446721538282 48.16 L108.02474889607315 48.16 A3 3 0 0 1 110.56889318454242 49.57024220730038 L139.21290427606598 95.41024220730039 A3 3 0 0 1 136.6687599875967 100 L108.36847830690638 100 A3 3 0 0 1 105.82433401843711 98.58975779269961 Z" transform="translate(-12 0)" fill="currentColor"/></svg>';
|
|
36
36
|
|
|
37
|
+
// client/sidebar-status.ts
|
|
38
|
+
var POLL_MS = 5e3;
|
|
39
|
+
function countTopics(topics) {
|
|
40
|
+
const counts = { active: 0, completed: 0, failed: 0 };
|
|
41
|
+
for (const topic of topics) {
|
|
42
|
+
switch (topic.status) {
|
|
43
|
+
case "queued":
|
|
44
|
+
case "uploading":
|
|
45
|
+
case "submitted":
|
|
46
|
+
case "waiting":
|
|
47
|
+
case "answered":
|
|
48
|
+
case "adjudicating":
|
|
49
|
+
counts.active += 1;
|
|
50
|
+
break;
|
|
51
|
+
case "completed":
|
|
52
|
+
counts.completed += 1;
|
|
53
|
+
break;
|
|
54
|
+
case "failed":
|
|
55
|
+
case "stalled":
|
|
56
|
+
counts.failed += 1;
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return counts;
|
|
63
|
+
}
|
|
64
|
+
function badgeText(counts) {
|
|
65
|
+
const parts = [];
|
|
66
|
+
if (counts.active > 0) parts.push(`${counts.active} \u8FDB\u884C`);
|
|
67
|
+
if (counts.completed > 0) parts.push(`${counts.completed} \u5B8C\u6210`);
|
|
68
|
+
if (counts.failed > 0) parts.push(`${counts.failed} \u5931\u8D25`);
|
|
69
|
+
return parts.join(" \xB7 ");
|
|
70
|
+
}
|
|
71
|
+
function mountSidebarStatus(options) {
|
|
72
|
+
if (typeof document === "undefined") return () => {
|
|
73
|
+
};
|
|
74
|
+
let badge;
|
|
75
|
+
let text = "";
|
|
76
|
+
let disposed = false;
|
|
77
|
+
let polling = false;
|
|
78
|
+
let attachTimer;
|
|
79
|
+
let observer;
|
|
80
|
+
const rowFor = () => {
|
|
81
|
+
const buttons = document.querySelectorAll("button[aria-label]");
|
|
82
|
+
for (const button of buttons) {
|
|
83
|
+
if (button.getAttribute("aria-label") === options.label) return button;
|
|
84
|
+
}
|
|
85
|
+
return void 0;
|
|
86
|
+
};
|
|
87
|
+
const applyText = (next) => {
|
|
88
|
+
text = next;
|
|
89
|
+
if (badge !== void 0 && badge.textContent !== text) badge.textContent = text;
|
|
90
|
+
ensureMounted();
|
|
91
|
+
};
|
|
92
|
+
const ensureMounted = () => {
|
|
93
|
+
if (disposed) return;
|
|
94
|
+
if (text === "") {
|
|
95
|
+
badge?.remove();
|
|
96
|
+
badge = void 0;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const row = rowFor();
|
|
100
|
+
if (row === void 0) return;
|
|
101
|
+
const title = row.querySelector('[class*="panelTitle"]');
|
|
102
|
+
if (title === null) {
|
|
103
|
+
badge?.remove();
|
|
104
|
+
badge = void 0;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (badge !== void 0 && badge.parentElement === row) {
|
|
108
|
+
if (badge.textContent !== text) badge.textContent = text;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
badge = document.createElement("span");
|
|
112
|
+
badge.dataset.dshProBadge = options.panelId;
|
|
113
|
+
badge.textContent = text;
|
|
114
|
+
badge.setAttribute("aria-hidden", "true");
|
|
115
|
+
badge.style.cssText = [
|
|
116
|
+
"margin-left:auto",
|
|
117
|
+
"display:inline-flex",
|
|
118
|
+
"align-items:center",
|
|
119
|
+
"flex:0 0 auto",
|
|
120
|
+
"font-size:10px",
|
|
121
|
+
"line-height:1",
|
|
122
|
+
"opacity:.62",
|
|
123
|
+
"white-space:nowrap",
|
|
124
|
+
"font-variant-numeric:tabular-nums",
|
|
125
|
+
"pointer-events:none"
|
|
126
|
+
].join(";");
|
|
127
|
+
row.appendChild(badge);
|
|
128
|
+
};
|
|
129
|
+
const refresh = async () => {
|
|
130
|
+
if (disposed || polling) return;
|
|
131
|
+
polling = true;
|
|
132
|
+
try {
|
|
133
|
+
const response = await fetch(`${options.routes}/topics`, { headers: { accept: "application/json" } });
|
|
134
|
+
if (!response.ok) return;
|
|
135
|
+
const data = await response.json();
|
|
136
|
+
applyText(badgeText(countTopics(data.topics ?? [])));
|
|
137
|
+
} catch {
|
|
138
|
+
} finally {
|
|
139
|
+
polling = false;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
observer = new MutationObserver(() => {
|
|
143
|
+
if (attachTimer !== void 0) return;
|
|
144
|
+
attachTimer = window.setTimeout(() => {
|
|
145
|
+
attachTimer = void 0;
|
|
146
|
+
ensureMounted();
|
|
147
|
+
}, 100);
|
|
148
|
+
});
|
|
149
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
150
|
+
const timer = window.setInterval(() => {
|
|
151
|
+
void refresh();
|
|
152
|
+
}, POLL_MS);
|
|
153
|
+
void refresh();
|
|
154
|
+
return () => {
|
|
155
|
+
disposed = true;
|
|
156
|
+
window.clearInterval(timer);
|
|
157
|
+
if (attachTimer !== void 0) window.clearTimeout(attachTimer);
|
|
158
|
+
observer?.disconnect();
|
|
159
|
+
badge?.remove();
|
|
160
|
+
badge = void 0;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
37
164
|
// client/panel.tsx
|
|
38
|
-
var
|
|
165
|
+
var POLL_MS2 = 3e3;
|
|
39
166
|
var config = {
|
|
40
167
|
routes: "/chatgpt-pro-research",
|
|
41
168
|
panelId: "chatgpt-pro-research",
|
|
@@ -126,7 +253,7 @@ function Panel() {
|
|
|
126
253
|
void loadList();
|
|
127
254
|
const timer = setInterval(() => {
|
|
128
255
|
void loadList();
|
|
129
|
-
},
|
|
256
|
+
}, POLL_MS2);
|
|
130
257
|
return () => clearInterval(timer);
|
|
131
258
|
}, [loadList]);
|
|
132
259
|
(0, import_react.useEffect)(() => {
|
|
@@ -134,7 +261,7 @@ function Panel() {
|
|
|
134
261
|
void loadDetail(selected);
|
|
135
262
|
const timer = setInterval(() => {
|
|
136
263
|
void loadDetail(selected);
|
|
137
|
-
},
|
|
264
|
+
}, POLL_MS2);
|
|
138
265
|
return () => clearInterval(timer);
|
|
139
266
|
}, [selected, loadDetail, topics]);
|
|
140
267
|
const act = (0, import_react.useCallback)(async (action, conclusion) => {
|
|
@@ -216,26 +343,7 @@ function Panel() {
|
|
|
216
343
|
) : void 0
|
|
217
344
|
),
|
|
218
345
|
(0, import_react.createElement)("div", { style: { fontWeight: 600, marginBottom: 4 } }, "\u8F6E\u6B21"),
|
|
219
|
-
(detail.rounds_detail ?? []).length === 0 ? (0, import_react.createElement)("div", { style: { opacity: 0.6 } }, "\u8FD8\u6CA1\u6709\u63D0\u4EA4\u8BB0\u5F55\u3002") : (0, import_react.createElement)(
|
|
220
|
-
"table",
|
|
221
|
-
{ style: { width: "100%", borderCollapse: "collapse", fontSize: 12 } },
|
|
222
|
-
(0, import_react.createElement)("thead", {}, (0, import_react.createElement)(
|
|
223
|
-
"tr",
|
|
224
|
-
{},
|
|
225
|
-
["\u8F6E", "\u88C1\u51B3", "\u6587\u4EF6", "\u63D0\u4EA4\u65F6\u95F4"].map((label) => (0, import_react.createElement)("th", {
|
|
226
|
-
key: label,
|
|
227
|
-
style: { textAlign: "left", padding: "4px 6px", borderBottom: "1px solid rgba(127,127,127,.25)", opacity: 0.7, fontWeight: 500 }
|
|
228
|
-
}, label))
|
|
229
|
-
)),
|
|
230
|
-
(0, import_react.createElement)("tbody", {}, (detail.rounds_detail ?? []).map((round) => (0, import_react.createElement)(
|
|
231
|
-
"tr",
|
|
232
|
-
{ key: round.n },
|
|
233
|
-
(0, import_react.createElement)("td", { style: cellStyle() }, String(round.n)),
|
|
234
|
-
(0, import_react.createElement)("td", { style: cellStyle() }, round.verdict ?? "\u2014"),
|
|
235
|
-
(0, import_react.createElement)("td", { style: cellStyle() }, String(round.files)),
|
|
236
|
-
(0, import_react.createElement)("td", { style: cellStyle() }, round.submittedAt?.slice(0, 16).replace("T", " ") ?? "\u2014")
|
|
237
|
-
)))
|
|
238
|
-
),
|
|
346
|
+
(detail.rounds_detail ?? []).length === 0 ? (0, import_react.createElement)("div", { style: { opacity: 0.6 } }, "\u8FD8\u6CA1\u6709\u63D0\u4EA4\u8BB0\u5F55\u3002") : (0, import_react.createElement)("div", {}, (detail.rounds_detail ?? []).map((round) => renderRound(detail, round))),
|
|
239
347
|
config.designMode && detail.designs != null ? (0, import_react.createElement)("div", { style: { marginTop: 8, opacity: 0.75 } }, `\u6BCF\u8F6E\u65B9\u5411\uFF1A${detail.designs}`) : void 0,
|
|
240
348
|
renderArtifacts(detail)
|
|
241
349
|
)
|
|
@@ -249,7 +357,16 @@ function renderArtifacts(detail) {
|
|
|
249
357
|
const artifacts = detail.artifacts ?? [];
|
|
250
358
|
if (artifacts.length === 0) return void 0;
|
|
251
359
|
if (!config.designMode) {
|
|
252
|
-
return (0, import_react.createElement)(
|
|
360
|
+
return (0, import_react.createElement)(
|
|
361
|
+
"div",
|
|
362
|
+
{ style: { marginTop: 10 } },
|
|
363
|
+
(0, import_react.createElement)("div", { style: { fontWeight: 600, marginBottom: 4 } }, "\u5168\u90E8\u4EA7\u7269"),
|
|
364
|
+
(0, import_react.createElement)(
|
|
365
|
+
"div",
|
|
366
|
+
{ style: { display: "flex", gap: 6, flexWrap: "wrap" } },
|
|
367
|
+
artifacts.map((path) => artifactChip(detail.topic_id, path))
|
|
368
|
+
)
|
|
369
|
+
);
|
|
253
370
|
}
|
|
254
371
|
const images = artifacts.filter((path) => /\.(png|jpe?g|webp|gif|svg)$/i.test(path));
|
|
255
372
|
const demos = artifacts.filter((path) => /\.html?$/i.test(path));
|
|
@@ -290,6 +407,87 @@ function renderArtifacts(detail) {
|
|
|
290
407
|
(0, import_react.createElement)("div", { style: { marginTop: 8, opacity: 0.6, fontSize: 11, wordBreak: "break-all" } }, artifacts.join("\u3001"))
|
|
291
408
|
);
|
|
292
409
|
}
|
|
410
|
+
function renderRound(detail, round) {
|
|
411
|
+
const prefix = `/round-${String(round.n).padStart(2, "0")}/`;
|
|
412
|
+
const artifacts = round.artifacts ?? (detail.artifacts ?? []).filter((path) => path.includes(prefix));
|
|
413
|
+
const attempts = round.attempts ?? 1;
|
|
414
|
+
return (0, import_react.createElement)(
|
|
415
|
+
"div",
|
|
416
|
+
{
|
|
417
|
+
key: round.n,
|
|
418
|
+
style: { padding: "8px 0", borderTop: "1px solid rgba(127,127,127,.18)" }
|
|
419
|
+
},
|
|
420
|
+
(0, import_react.createElement)(
|
|
421
|
+
"div",
|
|
422
|
+
{ style: { display: "flex", gap: 10, flexWrap: "wrap", alignItems: "baseline", fontSize: 12 } },
|
|
423
|
+
(0, import_react.createElement)("strong", {}, `\u7B2C ${round.n} \u8F6E`),
|
|
424
|
+
(0, import_react.createElement)("span", { style: { opacity: 0.72 } }, round.verdict ?? (round.answeredAt !== void 0 ? "\u5DF2\u7B54\u590D" : "\u8FDB\u884C\u4E2D")),
|
|
425
|
+
(0, import_react.createElement)("span", { style: { opacity: 0.6 } }, `${round.files} \u6587\u4EF6`),
|
|
426
|
+
(0, import_react.createElement)("span", { style: { opacity: 0.6 } }, `\u63D0\u4EA4 ${formatTime(round.submittedAt)}`),
|
|
427
|
+
(0, import_react.createElement)("span", { style: { opacity: 0.6 } }, `\u5B8C\u6210 ${formatTime(round.answeredAt)}`),
|
|
428
|
+
attempts > 1 ? (0, import_react.createElement)("span", { style: { color: "#ed6c02" } }, `\u91CD\u8BD5 ${attempts - 1}`) : void 0
|
|
429
|
+
),
|
|
430
|
+
(0, import_react.createElement)(
|
|
431
|
+
"div",
|
|
432
|
+
{ style: { display: "flex", gap: 6, flexWrap: "wrap", marginTop: 6 } },
|
|
433
|
+
(0, import_react.createElement)("button", {
|
|
434
|
+
onClick: () => window.open(`${config.routes}/answer?id=${encodeURIComponent(detail.topic_id)}&round=${round.n}`, "_blank", "noopener,noreferrer"),
|
|
435
|
+
style: chipStyle()
|
|
436
|
+
}, "\u67E5\u770B\u56DE\u7B54"),
|
|
437
|
+
artifacts.map((path) => artifactChip(detail.topic_id, path))
|
|
438
|
+
)
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
function artifactChip(topicId, path) {
|
|
442
|
+
const name = path.split("/").pop() ?? path;
|
|
443
|
+
if (/\.(png|jpe?g|webp|gif|svg)$/i.test(path)) {
|
|
444
|
+
return (0, import_react.createElement)("a", {
|
|
445
|
+
key: path,
|
|
446
|
+
href: assetUrl(topicId, path),
|
|
447
|
+
target: "_blank",
|
|
448
|
+
rel: "noreferrer",
|
|
449
|
+
style: chipStyle()
|
|
450
|
+
}, name);
|
|
451
|
+
}
|
|
452
|
+
if (/\.html?$/i.test(path)) {
|
|
453
|
+
return (0, import_react.createElement)("a", {
|
|
454
|
+
key: path,
|
|
455
|
+
href: assetUrl(topicId, path),
|
|
456
|
+
target: "_blank",
|
|
457
|
+
rel: "noreferrer",
|
|
458
|
+
style: chipStyle()
|
|
459
|
+
}, `${name} \u2197`);
|
|
460
|
+
}
|
|
461
|
+
return (0, import_react.createElement)("button", {
|
|
462
|
+
key: path,
|
|
463
|
+
onClick: () => void revealArtifact(topicId, path),
|
|
464
|
+
style: chipStyle()
|
|
465
|
+
}, `${name} \xB7 \u76EE\u5F55`);
|
|
466
|
+
}
|
|
467
|
+
async function revealArtifact(topicId, path) {
|
|
468
|
+
await fetch(`${config.routes}/open`, {
|
|
469
|
+
method: "POST",
|
|
470
|
+
headers: { "content-type": "application/json" },
|
|
471
|
+
body: JSON.stringify({ topic_id: topicId, path })
|
|
472
|
+
}).catch(() => void 0);
|
|
473
|
+
}
|
|
474
|
+
function formatTime(value) {
|
|
475
|
+
return value === void 0 ? "\u2014" : value.slice(0, 16).replace("T", " ");
|
|
476
|
+
}
|
|
477
|
+
function chipStyle() {
|
|
478
|
+
return {
|
|
479
|
+
padding: "2px 8px",
|
|
480
|
+
borderRadius: 999,
|
|
481
|
+
border: "1px solid rgba(127,127,127,.35)",
|
|
482
|
+
background: "transparent",
|
|
483
|
+
color: "inherit",
|
|
484
|
+
cursor: "pointer",
|
|
485
|
+
fontSize: 11,
|
|
486
|
+
textDecoration: "none",
|
|
487
|
+
display: "inline-flex",
|
|
488
|
+
alignItems: "center"
|
|
489
|
+
};
|
|
490
|
+
}
|
|
293
491
|
function buttonStyle() {
|
|
294
492
|
return {
|
|
295
493
|
padding: "4px 10px",
|
|
@@ -301,9 +499,6 @@ function buttonStyle() {
|
|
|
301
499
|
fontSize: 12
|
|
302
500
|
};
|
|
303
501
|
}
|
|
304
|
-
function cellStyle() {
|
|
305
|
-
return { padding: "4px 6px", borderBottom: "1px solid rgba(127,127,127,.12)" };
|
|
306
|
-
}
|
|
307
502
|
function Icon({ kind }) {
|
|
308
503
|
const svg = kind === "design" ? DESIGN_ICON : RESEARCH_ICON;
|
|
309
504
|
return (0, import_react.createElement)("span", {
|
|
@@ -315,6 +510,8 @@ function Icon({ kind }) {
|
|
|
315
510
|
function apply(ctx) {
|
|
316
511
|
ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: config.panelId }, () => (0, import_react.createElement)(Panel)));
|
|
317
512
|
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({ name: "sidebar.panellist", id: config.panelId, order: 21, label: () => config.label }, () => (0, import_react.createElement)(Icon, { kind: config.icon })));
|
|
513
|
+
const disposeBadge = mountSidebarStatus({ label: config.label, routes: config.routes, panelId: config.panelId });
|
|
514
|
+
if (typeof ctx.effect === "function") ctx.effect(() => disposeBadge, `${config.panelId}: sidebar status badge`);
|
|
318
515
|
}
|
|
319
516
|
|
|
320
517
|
// client/index.tsx
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// chatgpt-pro-research — built from packages/research/src/index.ts
|
|
2
2
|
|
|
3
3
|
// src/plugin.ts
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
import { dirname as dirname2, extname, isAbsolute, join as join3, resolve as resolve3, sep } from "node:path";
|
|
6
|
-
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
7
|
+
import { access, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import z from "@deepseek-ai/schemastery";
|
|
9
10
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -649,7 +650,7 @@ var ResearchEngine = class {
|
|
|
649
650
|
}
|
|
650
651
|
let round = pending ?? topic.rounds.find((item) => item.n === n);
|
|
651
652
|
if (round === void 0) {
|
|
652
|
-
round = { n, files: 0 };
|
|
653
|
+
round = { n, files: 0, attempts: 0 };
|
|
653
654
|
topic.rounds.push(round);
|
|
654
655
|
const stored = n === 1 ? await this.options.store.readQuestion(topic, 0) : await this.options.store.readQuestion(topic, n);
|
|
655
656
|
const brief = stored ?? this.followUpBrief(topic);
|
|
@@ -667,7 +668,10 @@ var ResearchEngine = class {
|
|
|
667
668
|
const fetched = await this.waitAnswer(round, signal);
|
|
668
669
|
if (fetched === void 0) return;
|
|
669
670
|
await this.options.store.writeAnswer(topic, n, fetched.answer);
|
|
670
|
-
await this.options.store.writeArtifacts(topic, n, fetched);
|
|
671
|
+
const written = await this.options.store.writeArtifacts(topic, n, fetched);
|
|
672
|
+
if (written.length > 0) {
|
|
673
|
+
round.artifacts = [.../* @__PURE__ */ new Set([...round.artifacts ?? [], ...written])];
|
|
674
|
+
}
|
|
671
675
|
round.answeredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
672
676
|
await this.transition(topic, "answered");
|
|
673
677
|
this.emit({ type: "answered", topicId: topic.id, round: n });
|
|
@@ -690,6 +694,7 @@ var ResearchEngine = class {
|
|
|
690
694
|
fallbackFiles = allFiles;
|
|
691
695
|
}
|
|
692
696
|
round.files = files.length;
|
|
697
|
+
round.attempts = (round.attempts ?? 0) + 1;
|
|
693
698
|
const result = await this.options.transport.submit({
|
|
694
699
|
topicId: topic.id,
|
|
695
700
|
round: round.n,
|
|
@@ -733,7 +738,9 @@ var ResearchEngine = class {
|
|
|
733
738
|
try {
|
|
734
739
|
return await this.options.transport.fetch({ submissionId });
|
|
735
740
|
} catch (error) {
|
|
736
|
-
if (polled.answer !== void 0)
|
|
741
|
+
if (polled.answer !== void 0) {
|
|
742
|
+
return { answer: polled.answer.startsWith("image:") ? "" : polled.answer };
|
|
743
|
+
}
|
|
737
744
|
throw error;
|
|
738
745
|
}
|
|
739
746
|
}
|
|
@@ -932,6 +939,16 @@ function advanceCompletion(previous, text, streaming) {
|
|
|
932
939
|
return { lastText: text, stable: 1, answered: false };
|
|
933
940
|
}
|
|
934
941
|
|
|
942
|
+
// ../transport/src/assets.ts
|
|
943
|
+
function parseDataUrlImage(source) {
|
|
944
|
+
const match = /^data:([^;,]+);base64,(.*)$/s.exec(source);
|
|
945
|
+
if (match === null) return void 0;
|
|
946
|
+
const mime = match[1];
|
|
947
|
+
const base64 = match[2];
|
|
948
|
+
if (mime === void 0 || base64 === void 0) return void 0;
|
|
949
|
+
return { mime, base64: base64.replace(/\s/g, "") };
|
|
950
|
+
}
|
|
951
|
+
|
|
935
952
|
// ../transport/src/chatgpt.ts
|
|
936
953
|
function sha256(value) {
|
|
937
954
|
return createHash3("sha256").update(value, "utf8").digest("hex");
|
|
@@ -959,6 +976,8 @@ var ChatGptWebTransport = class {
|
|
|
959
976
|
context;
|
|
960
977
|
launching;
|
|
961
978
|
submissions = /* @__PURE__ */ new Map();
|
|
979
|
+
/** One visible tab per topic so later rounds update the same conversation. */
|
|
980
|
+
topicPages = /* @__PURE__ */ new Map();
|
|
962
981
|
options;
|
|
963
982
|
constructor(options) {
|
|
964
983
|
this.options = options;
|
|
@@ -1030,7 +1049,9 @@ var ChatGptWebTransport = class {
|
|
|
1030
1049
|
}
|
|
1031
1050
|
async submit(request) {
|
|
1032
1051
|
const context = await this.ensureContext();
|
|
1033
|
-
const
|
|
1052
|
+
const existing = this.topicPages.get(request.topicId);
|
|
1053
|
+
const page = existing !== void 0 && !existing.isClosed() ? existing : await context.newPage();
|
|
1054
|
+
this.topicPages.set(request.topicId, page);
|
|
1034
1055
|
try {
|
|
1035
1056
|
const target = request.session?.conversationUrl ?? this.appUrl;
|
|
1036
1057
|
let reused = request.session?.conversationUrl !== void 0;
|
|
@@ -1140,6 +1161,17 @@ ${attachments.join("\n")}`);
|
|
|
1140
1161
|
const images = [];
|
|
1141
1162
|
for (const [index, source] of sources.slice(0, 8).entries()) {
|
|
1142
1163
|
try {
|
|
1164
|
+
if (source.startsWith("data:")) {
|
|
1165
|
+
const parsed = parseDataUrlImage(source);
|
|
1166
|
+
if (parsed === void 0) continue;
|
|
1167
|
+
if (Buffer.from(parsed.base64, "base64").byteLength < 2e4) continue;
|
|
1168
|
+
images.push({
|
|
1169
|
+
name: `image-${String(index + 1).padStart(2, "0")}.${extensionFor(parsed.mime)}`,
|
|
1170
|
+
mime: parsed.mime,
|
|
1171
|
+
base64: parsed.base64
|
|
1172
|
+
});
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1143
1175
|
if (source.startsWith("blob:")) {
|
|
1144
1176
|
const dataUrl = await page.evaluate(async (src) => {
|
|
1145
1177
|
const response = await fetch(src);
|
|
@@ -1181,10 +1213,15 @@ ${attachments.join("\n")}`);
|
|
|
1181
1213
|
})).filter((block) => block.text.trim() !== ""));
|
|
1182
1214
|
}
|
|
1183
1215
|
async cancel() {
|
|
1184
|
-
|
|
1185
|
-
|
|
1216
|
+
const pages = /* @__PURE__ */ new Set([
|
|
1217
|
+
...[...this.submissions.values()].map((submission) => submission.page),
|
|
1218
|
+
...this.topicPages.values()
|
|
1219
|
+
]);
|
|
1220
|
+
for (const page of pages) {
|
|
1221
|
+
await page.close().catch(() => void 0);
|
|
1186
1222
|
}
|
|
1187
1223
|
this.submissions.clear();
|
|
1224
|
+
this.topicPages.clear();
|
|
1188
1225
|
}
|
|
1189
1226
|
async close() {
|
|
1190
1227
|
await this.cancel();
|
|
@@ -1237,8 +1274,8 @@ ${attachments.join("\n")}`);
|
|
|
1237
1274
|
async richAnswerText(page) {
|
|
1238
1275
|
const text = await this.lastAssistantText(page);
|
|
1239
1276
|
if (text !== "") return text;
|
|
1240
|
-
const images = await page.evaluate(() => [...document.querySelectorAll("main img")].filter((image) => image.naturalWidth >= 300).map((image) => image.
|
|
1241
|
-
return images.length > 0 ? `image:${images.join("
|
|
1277
|
+
const images = await page.evaluate(() => [...document.querySelectorAll("main img")].filter((image) => image.naturalWidth >= 300).map((image) => `${image.naturalWidth}x${image.naturalHeight}`));
|
|
1278
|
+
return images.length > 0 ? `image:${images.length}:${images.join(",")}` : "";
|
|
1242
1279
|
}
|
|
1243
1280
|
async lastAssistantText(page) {
|
|
1244
1281
|
const messages = page.locator(SELECTORS.assistantMessage);
|
|
@@ -1601,6 +1638,28 @@ ${input.answer.slice(0, 3e4)}`
|
|
|
1601
1638
|
const bytes = await readFile3(target);
|
|
1602
1639
|
return { bytes, mime: mimeFor(target) };
|
|
1603
1640
|
}
|
|
1641
|
+
/** Raw answer text for one round; shown in a new tab from the panel. */
|
|
1642
|
+
async answerText(id, round) {
|
|
1643
|
+
const entry = await this.entryFor(id);
|
|
1644
|
+
if (entry === void 0) return void 0;
|
|
1645
|
+
const store = this.storeAt(entry.root, entry.mode);
|
|
1646
|
+
const topic = await store.load(id);
|
|
1647
|
+
return await store.readAnswer(topic, round);
|
|
1648
|
+
}
|
|
1649
|
+
/** Reveal a topic-relative file in the OS file manager. */
|
|
1650
|
+
async revealFile(id, relativePath) {
|
|
1651
|
+
const entry = await this.entryFor(id);
|
|
1652
|
+
if (entry === void 0) throw new Error(`unknown topic: ${id}`);
|
|
1653
|
+
const store = this.storeAt(entry.root, entry.mode);
|
|
1654
|
+
const dir = await store.dirOf(id);
|
|
1655
|
+
const target = resolve3(dir, relativePath);
|
|
1656
|
+
if (target !== dir && !target.startsWith(`${dir}${sep}`)) {
|
|
1657
|
+
throw new Error("path escapes the topic");
|
|
1658
|
+
}
|
|
1659
|
+
await access(target);
|
|
1660
|
+
const child = process.platform === "win32" ? spawn("explorer.exe", ["/select,", target], { detached: true, stdio: "ignore" }) : process.platform === "darwin" ? spawn("open", ["-R", target], { detached: true, stdio: "ignore" }) : spawn("xdg-open", [dirname2(target)], { detached: true, stdio: "ignore" });
|
|
1661
|
+
child.unref();
|
|
1662
|
+
}
|
|
1604
1663
|
async act(id, action, conclusion, note) {
|
|
1605
1664
|
const entry = await this.entryFor(id);
|
|
1606
1665
|
if (entry === void 0) throw new Error(`unknown topic: ${id}`);
|
|
@@ -1881,6 +1940,43 @@ function registerRoutes(scoped, service, spec2) {
|
|
|
1881
1940
|
}
|
|
1882
1941
|
}
|
|
1883
1942
|
}), `${spec2.name}: asset route`);
|
|
1943
|
+
scoped.effect(() => webServer.register({
|
|
1944
|
+
kind: "exact",
|
|
1945
|
+
path: `${spec2.routes}/answer`,
|
|
1946
|
+
handler: async (req, res) => {
|
|
1947
|
+
const denied = reject(req);
|
|
1948
|
+
if (denied !== void 0) return sendJson(res, denied, { error: "unauthorized" });
|
|
1949
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
1950
|
+
const id = url.searchParams.get("id") ?? "";
|
|
1951
|
+
const round = Number(url.searchParams.get("round") ?? "0");
|
|
1952
|
+
try {
|
|
1953
|
+
const text = await service.answerText(id, round);
|
|
1954
|
+
if (text === void 0) return sendJson(res, 404, { error: "answer not found" });
|
|
1955
|
+
res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" });
|
|
1956
|
+
res.end(text);
|
|
1957
|
+
} catch (error) {
|
|
1958
|
+
sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}), `${spec2.name}: answer route`);
|
|
1962
|
+
scoped.effect(() => webServer.register({
|
|
1963
|
+
kind: "exact",
|
|
1964
|
+
path: `${spec2.routes}/open`,
|
|
1965
|
+
handler: async (req, res) => {
|
|
1966
|
+
const denied = reject(req);
|
|
1967
|
+
if (denied !== void 0) return sendJson(res, denied, { error: "unauthorized" });
|
|
1968
|
+
if (req.method !== "POST") return sendJson(res, 405, { error: "method not allowed" });
|
|
1969
|
+
try {
|
|
1970
|
+
const body = await readJsonBody(req);
|
|
1971
|
+
const id = typeof body.topic_id === "string" ? body.topic_id : "";
|
|
1972
|
+
const relativePath = typeof body.path === "string" ? body.path : "";
|
|
1973
|
+
await service.revealFile(id, relativePath);
|
|
1974
|
+
sendJson(res, 200, { ok: true });
|
|
1975
|
+
} catch (error) {
|
|
1976
|
+
sendJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
}), `${spec2.name}: open route`);
|
|
1884
1980
|
}
|
|
1885
1981
|
function createPlugin(spec2) {
|
|
1886
1982
|
return (ctx, config) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chatgpt-pro-research",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "ChatGPT Pro Research: unattended multi-round research through a real ChatGPT Pro web session, with local records and a DSH sidebar.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|