chatgpt-pro-research 0.2.3 → 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 CHANGED
@@ -343,26 +343,7 @@ function Panel() {
343
343
  ) : void 0
344
344
  ),
345
345
  (0, import_react.createElement)("div", { style: { fontWeight: 600, marginBottom: 4 } }, "\u8F6E\u6B21"),
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)(
347
- "table",
348
- { style: { width: "100%", borderCollapse: "collapse", fontSize: 12 } },
349
- (0, import_react.createElement)("thead", {}, (0, import_react.createElement)(
350
- "tr",
351
- {},
352
- ["\u8F6E", "\u88C1\u51B3", "\u6587\u4EF6", "\u63D0\u4EA4\u65F6\u95F4"].map((label) => (0, import_react.createElement)("th", {
353
- key: label,
354
- style: { textAlign: "left", padding: "4px 6px", borderBottom: "1px solid rgba(127,127,127,.25)", opacity: 0.7, fontWeight: 500 }
355
- }, label))
356
- )),
357
- (0, import_react.createElement)("tbody", {}, (detail.rounds_detail ?? []).map((round) => (0, import_react.createElement)(
358
- "tr",
359
- { key: round.n },
360
- (0, import_react.createElement)("td", { style: cellStyle() }, String(round.n)),
361
- (0, import_react.createElement)("td", { style: cellStyle() }, round.verdict ?? "\u2014"),
362
- (0, import_react.createElement)("td", { style: cellStyle() }, String(round.files)),
363
- (0, import_react.createElement)("td", { style: cellStyle() }, round.submittedAt?.slice(0, 16).replace("T", " ") ?? "\u2014")
364
- )))
365
- ),
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))),
366
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,
367
348
  renderArtifacts(detail)
368
349
  )
@@ -376,7 +357,16 @@ function renderArtifacts(detail) {
376
357
  const artifacts = detail.artifacts ?? [];
377
358
  if (artifacts.length === 0) return void 0;
378
359
  if (!config.designMode) {
379
- return (0, import_react.createElement)("div", { style: { marginTop: 10, opacity: 0.75 } }, `\u4EA7\u7269\uFF1A${artifacts.join("\u3001")}`);
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
+ );
380
370
  }
381
371
  const images = artifacts.filter((path) => /\.(png|jpe?g|webp|gif|svg)$/i.test(path));
382
372
  const demos = artifacts.filter((path) => /\.html?$/i.test(path));
@@ -417,6 +407,87 @@ function renderArtifacts(detail) {
417
407
  (0, import_react.createElement)("div", { style: { marginTop: 8, opacity: 0.6, fontSize: 11, wordBreak: "break-all" } }, artifacts.join("\u3001"))
418
408
  );
419
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
+ }
420
491
  function buttonStyle() {
421
492
  return {
422
493
  padding: "4px 10px",
@@ -428,9 +499,6 @@ function buttonStyle() {
428
499
  fontSize: 12
429
500
  };
430
501
  }
431
- function cellStyle() {
432
- return { padding: "4px 6px", borderBottom: "1px solid rgba(127,127,127,.12)" };
433
- }
434
502
  function Icon({ kind }) {
435
503
  const svg = kind === "design" ? DESIGN_ICON : RESEARCH_ICON;
436
504
  return (0, import_react.createElement)("span", {
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,
@@ -971,6 +976,8 @@ var ChatGptWebTransport = class {
971
976
  context;
972
977
  launching;
973
978
  submissions = /* @__PURE__ */ new Map();
979
+ /** One visible tab per topic so later rounds update the same conversation. */
980
+ topicPages = /* @__PURE__ */ new Map();
974
981
  options;
975
982
  constructor(options) {
976
983
  this.options = options;
@@ -1042,7 +1049,9 @@ var ChatGptWebTransport = class {
1042
1049
  }
1043
1050
  async submit(request) {
1044
1051
  const context = await this.ensureContext();
1045
- const page = await context.newPage();
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);
1046
1055
  try {
1047
1056
  const target = request.session?.conversationUrl ?? this.appUrl;
1048
1057
  let reused = request.session?.conversationUrl !== void 0;
@@ -1204,10 +1213,15 @@ ${attachments.join("\n")}`);
1204
1213
  })).filter((block) => block.text.trim() !== ""));
1205
1214
  }
1206
1215
  async cancel() {
1207
- for (const submission of this.submissions.values()) {
1208
- await submission.page.close().catch(() => void 0);
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);
1209
1222
  }
1210
1223
  this.submissions.clear();
1224
+ this.topicPages.clear();
1211
1225
  }
1212
1226
  async close() {
1213
1227
  await this.cancel();
@@ -1624,6 +1638,28 @@ ${input.answer.slice(0, 3e4)}`
1624
1638
  const bytes = await readFile3(target);
1625
1639
  return { bytes, mime: mimeFor(target) };
1626
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
+ }
1627
1663
  async act(id, action, conclusion, note) {
1628
1664
  const entry = await this.entryFor(id);
1629
1665
  if (entry === void 0) throw new Error(`unknown topic: ${id}`);
@@ -1904,6 +1940,43 @@ function registerRoutes(scoped, service, spec2) {
1904
1940
  }
1905
1941
  }
1906
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`);
1907
1980
  }
1908
1981
  function createPlugin(spec2) {
1909
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",
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",