pxengine 0.1.112 → 0.1.114
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/dist/index.cjs +590 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -1
- package/dist/index.d.ts +76 -1
- package/dist/index.mjs +590 -37
- package/dist/index.mjs.map +1 -1
- package/dist/registry.json +62 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -16125,6 +16125,22 @@ var ShareIcon = () => /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("svg", { w
|
|
|
16125
16125
|
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
|
|
16126
16126
|
] });
|
|
16127
16127
|
var CheckIcon = () => /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("polyline", { points: "20 6 9 17 4 12" }) });
|
|
16128
|
+
var RefreshIcon = () => /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
16129
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("polyline", { points: "23 4 23 10 17 10" }),
|
|
16130
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
|
|
16131
|
+
] });
|
|
16132
|
+
var SlideTypeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
16133
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("rect", { x: "3", y: "4", width: "18", height: "14", rx: "2" }),
|
|
16134
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("line", { x1: "3", y1: "10", x2: "21", y2: "10" })
|
|
16135
|
+
] });
|
|
16136
|
+
function formatTemplateLabel(templateId) {
|
|
16137
|
+
if (!templateId) return null;
|
|
16138
|
+
return templateId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
16139
|
+
}
|
|
16140
|
+
function deriveJobStatusUrl(newJobId, pollUrl, regenerateUrl) {
|
|
16141
|
+
const base = pollUrl?.replace(/\/api\/jobs\/[^/]+\/status.*$/, "") ?? regenerateUrl?.replace(/\/api\/presentations\/[^/]+\/regenerate.*$/, "") ?? "";
|
|
16142
|
+
return `${base}/api/jobs/${newJobId}/status`;
|
|
16143
|
+
}
|
|
16128
16144
|
var FORMATS = [
|
|
16129
16145
|
{
|
|
16130
16146
|
key: "pptx_url",
|
|
@@ -16330,11 +16346,20 @@ var PresentationJobCard = ({
|
|
|
16330
16346
|
status: initialStatus,
|
|
16331
16347
|
slide_count: initialSlideCount,
|
|
16332
16348
|
formats: initialFormats = {},
|
|
16349
|
+
generation_mode: initialGenerationMode,
|
|
16350
|
+
template_id: initialTemplateId,
|
|
16351
|
+
template_version_id: initialTemplateVersionId,
|
|
16352
|
+
review_status: initialReviewStatus,
|
|
16353
|
+
outline: initialOutline,
|
|
16333
16354
|
error: initialError,
|
|
16334
16355
|
progress: initialProgress,
|
|
16335
16356
|
pollUrl,
|
|
16336
16357
|
authToken,
|
|
16337
16358
|
shareUrl,
|
|
16359
|
+
approveUrl,
|
|
16360
|
+
approveOutlineUrl,
|
|
16361
|
+
regenerateUrl,
|
|
16362
|
+
hideApproval = false,
|
|
16338
16363
|
className,
|
|
16339
16364
|
theme,
|
|
16340
16365
|
onComplete,
|
|
@@ -16348,9 +16373,23 @@ var PresentationJobCard = ({
|
|
|
16348
16373
|
const [formats, setFormats] = (0, import_react78.useState)(initialFormats);
|
|
16349
16374
|
const [error, setError] = (0, import_react78.useState)(initialError);
|
|
16350
16375
|
const [progress, setProgress] = (0, import_react78.useState)(initialProgress);
|
|
16376
|
+
const [generationMode, setGenerationMode] = (0, import_react78.useState)(
|
|
16377
|
+
initialGenerationMode || (initialFormats?.html_url ? "template" : "")
|
|
16378
|
+
);
|
|
16379
|
+
const [templateId, setTemplateId] = (0, import_react78.useState)(initialTemplateId || "");
|
|
16380
|
+
const [, setTemplateVersionId] = (0, import_react78.useState)(initialTemplateVersionId || "");
|
|
16381
|
+
const [reviewStatus, setReviewStatus] = (0, import_react78.useState)(initialReviewStatus || "");
|
|
16382
|
+
const [outline, setOutline] = (0, import_react78.useState)(initialOutline);
|
|
16351
16383
|
const [showExport, setShowExport] = (0, import_react78.useState)(false);
|
|
16352
16384
|
const [showFullscreen, setShowFullscreen] = (0, import_react78.useState)(false);
|
|
16353
16385
|
const [copied, setCopied] = (0, import_react78.useState)(false);
|
|
16386
|
+
const [approving, setApproving] = (0, import_react78.useState)(false);
|
|
16387
|
+
const [regenerating, setRegenerating] = (0, import_react78.useState)(false);
|
|
16388
|
+
const [approveError, setApproveError] = (0, import_react78.useState)(null);
|
|
16389
|
+
const [messageEdits, setMessageEdits] = (0, import_react78.useState)({});
|
|
16390
|
+
const [approvingOutline, setApprovingOutline] = (0, import_react78.useState)(false);
|
|
16391
|
+
const [outlineWritePollUrl, setOutlineWritePollUrl] = (0, import_react78.useState)(null);
|
|
16392
|
+
const [regenPollUrl, setRegenPollUrl] = (0, import_react78.useState)(null);
|
|
16354
16393
|
const [currentSlide, setCurrentSlide] = (0, import_react78.useState)(1);
|
|
16355
16394
|
const [previewScale, setPreviewScale] = (0, import_react78.useState)(1);
|
|
16356
16395
|
const [iframeReady, setIframeReady] = (0, import_react78.useState)(false);
|
|
@@ -16377,6 +16416,22 @@ var PresentationJobCard = ({
|
|
|
16377
16416
|
(0, import_react78.useEffect)(() => {
|
|
16378
16417
|
if (initialTitle) setTitle(initialTitle);
|
|
16379
16418
|
}, [initialTitle]);
|
|
16419
|
+
(0, import_react78.useEffect)(() => {
|
|
16420
|
+
if (initialGenerationMode) setGenerationMode(initialGenerationMode);
|
|
16421
|
+
}, [initialGenerationMode]);
|
|
16422
|
+
(0, import_react78.useEffect)(() => {
|
|
16423
|
+
if (initialTemplateId) setTemplateId(initialTemplateId);
|
|
16424
|
+
}, [initialTemplateId]);
|
|
16425
|
+
(0, import_react78.useEffect)(() => {
|
|
16426
|
+
if (initialTemplateVersionId) setTemplateVersionId(initialTemplateVersionId);
|
|
16427
|
+
}, [initialTemplateVersionId]);
|
|
16428
|
+
(0, import_react78.useEffect)(() => {
|
|
16429
|
+
if (initialReviewStatus) setReviewStatus(initialReviewStatus);
|
|
16430
|
+
}, [initialReviewStatus]);
|
|
16431
|
+
const initialOutlineSlideCount = initialOutline?.slides?.length;
|
|
16432
|
+
(0, import_react78.useEffect)(() => {
|
|
16433
|
+
if (initialOutline) setOutline(initialOutline);
|
|
16434
|
+
}, [initialOutlineSlideCount]);
|
|
16380
16435
|
const updateScale = (0, import_react78.useCallback)(() => {
|
|
16381
16436
|
if (previewRef.current) setPreviewScale(previewRef.current.offsetWidth / 1280);
|
|
16382
16437
|
}, []);
|
|
@@ -16440,13 +16495,21 @@ var PresentationJobCard = ({
|
|
|
16440
16495
|
setProgress(data.progress);
|
|
16441
16496
|
}
|
|
16442
16497
|
if (newStatus === "complete" && data.output) {
|
|
16443
|
-
const
|
|
16444
|
-
const
|
|
16445
|
-
const
|
|
16498
|
+
const output = data.output;
|
|
16499
|
+
const isOutlinePhase = output.review_status === "pending_outline_approval" || output.type === "presentation-outline";
|
|
16500
|
+
const newTitle = output.title || initialTitle;
|
|
16501
|
+
const newSlideCount = output.slide_count || 0;
|
|
16502
|
+
const newFormats = output.formats || {};
|
|
16446
16503
|
setTitle(newTitle);
|
|
16447
16504
|
setSlideCount(newSlideCount);
|
|
16448
16505
|
setFormats(newFormats);
|
|
16449
|
-
if (
|
|
16506
|
+
if (output.generation_mode) setGenerationMode(output.generation_mode);
|
|
16507
|
+
if (output.template_id) setTemplateId(output.template_id);
|
|
16508
|
+
if (output.template_version_id) setTemplateVersionId(output.template_version_id);
|
|
16509
|
+
if (output.review_status) setReviewStatus(output.review_status);
|
|
16510
|
+
if (output.outline) setOutline(output.outline);
|
|
16511
|
+
else if (output.deck_plan) setOutline(output.deck_plan);
|
|
16512
|
+
if (!isOutlinePhase && !hasNotifiedRef.current && onCompleteRef.current) {
|
|
16450
16513
|
hasNotifiedRef.current = true;
|
|
16451
16514
|
onCompleteRef.current({
|
|
16452
16515
|
title: newTitle,
|
|
@@ -16466,6 +16529,184 @@ var PresentationJobCard = ({
|
|
|
16466
16529
|
}
|
|
16467
16530
|
// Transient fetch errors are ignored — the shared loop retries next tick.
|
|
16468
16531
|
);
|
|
16532
|
+
const applyDeckOutput = (0, import_react78.useCallback)((out, opts) => {
|
|
16533
|
+
if (out.title) setTitle(out.title);
|
|
16534
|
+
if (out.slide_count !== void 0) setSlideCount(out.slide_count);
|
|
16535
|
+
if (out.formats) setFormats(out.formats);
|
|
16536
|
+
if (out.generation_mode) setGenerationMode(out.generation_mode);
|
|
16537
|
+
if (out.template_id) setTemplateId(out.template_id);
|
|
16538
|
+
setReviewStatus(out.review_status || "pending_review");
|
|
16539
|
+
setTemplateVersionId(opts?.regenerated ? "" : out.template_version_id || "");
|
|
16540
|
+
if (!opts?.regenerated && !hasNotifiedRef.current && onCompleteRef.current) {
|
|
16541
|
+
hasNotifiedRef.current = true;
|
|
16542
|
+
onCompleteRef.current({
|
|
16543
|
+
title: out.title ?? title,
|
|
16544
|
+
slide_count: out.slide_count ?? slideCount,
|
|
16545
|
+
formats: out.formats ?? formats
|
|
16546
|
+
});
|
|
16547
|
+
}
|
|
16548
|
+
}, [title, slideCount, formats]);
|
|
16549
|
+
const refetchSourceAndApply = (0, import_react78.useCallback)(async () => {
|
|
16550
|
+
if (!pollUrl) return;
|
|
16551
|
+
try {
|
|
16552
|
+
const headers = {};
|
|
16553
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
16554
|
+
const res = await fetch(pollUrl, { headers });
|
|
16555
|
+
if (!res.ok) return;
|
|
16556
|
+
const data = await res.json();
|
|
16557
|
+
if (data.output) applyDeckOutput(data.output);
|
|
16558
|
+
} catch {
|
|
16559
|
+
}
|
|
16560
|
+
}, [pollUrl, authToken, applyDeckOutput]);
|
|
16561
|
+
useSharedPoll(
|
|
16562
|
+
{
|
|
16563
|
+
key: outlineWritePollUrl,
|
|
16564
|
+
intervalMs: 3e3,
|
|
16565
|
+
fetcher: async () => {
|
|
16566
|
+
const headers = {};
|
|
16567
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
16568
|
+
const res = await fetch(outlineWritePollUrl, { headers });
|
|
16569
|
+
if (!res.ok) throw new Error(`poll ${res.status}`);
|
|
16570
|
+
return res.json();
|
|
16571
|
+
},
|
|
16572
|
+
shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
|
|
16573
|
+
},
|
|
16574
|
+
(data) => {
|
|
16575
|
+
if (data.status === "complete") {
|
|
16576
|
+
setOutlineWritePollUrl(null);
|
|
16577
|
+
setApprovingOutline(false);
|
|
16578
|
+
void refetchSourceAndApply();
|
|
16579
|
+
} else if (data.status === "failed") {
|
|
16580
|
+
setApproveError(data.error || "Could not build the presentation");
|
|
16581
|
+
setOutlineWritePollUrl(null);
|
|
16582
|
+
setApprovingOutline(false);
|
|
16583
|
+
}
|
|
16584
|
+
}
|
|
16585
|
+
);
|
|
16586
|
+
useSharedPoll(
|
|
16587
|
+
{
|
|
16588
|
+
key: regenPollUrl,
|
|
16589
|
+
intervalMs: 3e3,
|
|
16590
|
+
fetcher: async () => {
|
|
16591
|
+
const headers = {};
|
|
16592
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
16593
|
+
const res = await fetch(regenPollUrl, { headers });
|
|
16594
|
+
if (!res.ok) throw new Error(`poll ${res.status}`);
|
|
16595
|
+
return res.json();
|
|
16596
|
+
},
|
|
16597
|
+
shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
|
|
16598
|
+
},
|
|
16599
|
+
(data) => {
|
|
16600
|
+
if (data.status === "complete") {
|
|
16601
|
+
if (data.output) applyDeckOutput(data.output, { regenerated: true });
|
|
16602
|
+
setRegenPollUrl(null);
|
|
16603
|
+
setRegenerating(false);
|
|
16604
|
+
} else if (data.status === "failed") {
|
|
16605
|
+
setApproveError(data.error || "Could not switch template");
|
|
16606
|
+
setRegenPollUrl(null);
|
|
16607
|
+
setRegenerating(false);
|
|
16608
|
+
}
|
|
16609
|
+
}
|
|
16610
|
+
);
|
|
16611
|
+
const buildEditedOutline = () => {
|
|
16612
|
+
if (!outline || !Array.isArray(outline.slides)) return void 0;
|
|
16613
|
+
let changed = false;
|
|
16614
|
+
const slides = outline.slides.map((s, i) => {
|
|
16615
|
+
const edited = messageEdits[i];
|
|
16616
|
+
if (edited !== void 0 && edited !== s.message) {
|
|
16617
|
+
changed = true;
|
|
16618
|
+
return { ...s, message: edited };
|
|
16619
|
+
}
|
|
16620
|
+
return s;
|
|
16621
|
+
});
|
|
16622
|
+
return changed ? { ...outline, slides } : void 0;
|
|
16623
|
+
};
|
|
16624
|
+
const handleApproveOutline = async () => {
|
|
16625
|
+
if (!_job_id || approvingOutline) return;
|
|
16626
|
+
const endpoint = approveOutlineUrl || `/api/presentations/${_job_id}/approve-outline`;
|
|
16627
|
+
setApprovingOutline(true);
|
|
16628
|
+
setApproveError(null);
|
|
16629
|
+
try {
|
|
16630
|
+
const headers = { "Content-Type": "application/json" };
|
|
16631
|
+
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
|
16632
|
+
const editedOutline = buildEditedOutline();
|
|
16633
|
+
const res = await fetch(endpoint, {
|
|
16634
|
+
method: "POST",
|
|
16635
|
+
headers,
|
|
16636
|
+
body: JSON.stringify(editedOutline ? { outline: editedOutline } : {})
|
|
16637
|
+
});
|
|
16638
|
+
if (!res.ok) {
|
|
16639
|
+
const err = await res.json().catch(() => ({}));
|
|
16640
|
+
throw new Error(err.detail || err.error || `Approve failed (${res.status})`);
|
|
16641
|
+
}
|
|
16642
|
+
const data = await res.json();
|
|
16643
|
+
if (typeof data.job_id === "string" && data.job_id !== _job_id) {
|
|
16644
|
+
setOutlineWritePollUrl(deriveJobStatusUrl(data.job_id, pollUrl, regenerateUrl));
|
|
16645
|
+
return;
|
|
16646
|
+
}
|
|
16647
|
+
setApprovingOutline(false);
|
|
16648
|
+
void refetchSourceAndApply();
|
|
16649
|
+
} catch (e) {
|
|
16650
|
+
setApproveError(e instanceof Error ? e.message : "Could not build the presentation");
|
|
16651
|
+
setApprovingOutline(false);
|
|
16652
|
+
}
|
|
16653
|
+
};
|
|
16654
|
+
const handleApprove = async () => {
|
|
16655
|
+
if (!_job_id || approving) return;
|
|
16656
|
+
const endpoint = approveUrl || `/api/presentations/${_job_id}/approve-standard`;
|
|
16657
|
+
setApproving(true);
|
|
16658
|
+
setApproveError(null);
|
|
16659
|
+
try {
|
|
16660
|
+
const headers = { "Content-Type": "application/json" };
|
|
16661
|
+
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
|
16662
|
+
const res = await fetch(endpoint, {
|
|
16663
|
+
method: "POST",
|
|
16664
|
+
headers,
|
|
16665
|
+
body: JSON.stringify({ label: title })
|
|
16666
|
+
});
|
|
16667
|
+
if (!res.ok) {
|
|
16668
|
+
const err = await res.json().catch(() => ({}));
|
|
16669
|
+
throw new Error(err.detail || err.error || `Approve failed (${res.status})`);
|
|
16670
|
+
}
|
|
16671
|
+
const data = await res.json();
|
|
16672
|
+
setReviewStatus(data.review_status || "approved");
|
|
16673
|
+
if (data.template_version_id) setTemplateVersionId(data.template_version_id);
|
|
16674
|
+
if (data.template_id) setTemplateId(data.template_id);
|
|
16675
|
+
} catch (e) {
|
|
16676
|
+
setApproveError(e instanceof Error ? e.message : "Could not save the format");
|
|
16677
|
+
} finally {
|
|
16678
|
+
setApproving(false);
|
|
16679
|
+
}
|
|
16680
|
+
};
|
|
16681
|
+
const handleRegenerate = async () => {
|
|
16682
|
+
if (!_job_id || regenerating) return;
|
|
16683
|
+
const endpoint = regenerateUrl || `/api/presentations/${_job_id}/regenerate`;
|
|
16684
|
+
setRegenerating(true);
|
|
16685
|
+
setApproveError(null);
|
|
16686
|
+
try {
|
|
16687
|
+
const headers = { "Content-Type": "application/json" };
|
|
16688
|
+
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
|
16689
|
+
const res = await fetch(endpoint, {
|
|
16690
|
+
method: "POST",
|
|
16691
|
+
headers,
|
|
16692
|
+
body: JSON.stringify({})
|
|
16693
|
+
});
|
|
16694
|
+
if (!res.ok) {
|
|
16695
|
+
const err = await res.json().catch(() => ({}));
|
|
16696
|
+
throw new Error(err.detail || err.error || `Regenerate failed (${res.status})`);
|
|
16697
|
+
}
|
|
16698
|
+
const data = await res.json();
|
|
16699
|
+
if (typeof data.job_id === "string" && data.job_id !== _job_id) {
|
|
16700
|
+
setRegenPollUrl(deriveJobStatusUrl(data.job_id, pollUrl, regenerateUrl));
|
|
16701
|
+
return;
|
|
16702
|
+
}
|
|
16703
|
+
applyDeckOutput(data.output || data, { regenerated: true });
|
|
16704
|
+
setRegenerating(false);
|
|
16705
|
+
} catch (e) {
|
|
16706
|
+
setApproveError(e instanceof Error ? e.message : "Could not switch template");
|
|
16707
|
+
setRegenerating(false);
|
|
16708
|
+
}
|
|
16709
|
+
};
|
|
16469
16710
|
const handleShare = async () => {
|
|
16470
16711
|
const link = shareUrl ?? (typeof window !== "undefined" && _job_id ? `${window.location.origin}/p/${_job_id}` : formats.html_url);
|
|
16471
16712
|
if (!link) return;
|
|
@@ -16586,8 +16827,85 @@ var PresentationJobCard = ({
|
|
|
16586
16827
|
] })
|
|
16587
16828
|
] }) }) });
|
|
16588
16829
|
}
|
|
16830
|
+
if (status === "complete" && reviewStatus === "pending_outline_approval") {
|
|
16831
|
+
const slides = Array.isArray(outline?.slides) ? outline.slides : [];
|
|
16832
|
+
const building = Boolean(outlineWritePollUrl) || approvingOutline;
|
|
16833
|
+
return /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", style: t.root, children: [
|
|
16834
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)(
|
|
16835
|
+
"div",
|
|
16836
|
+
{
|
|
16837
|
+
className: "h-[3px]",
|
|
16838
|
+
style: { background: accentGradient || "linear-gradient(90deg, #8B7D5E, #C0AE82, #DCC99B)" }
|
|
16839
|
+
}
|
|
16840
|
+
),
|
|
16841
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "p-5", children: [
|
|
16842
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "flex items-start gap-3", children: [
|
|
16843
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)(
|
|
16844
|
+
"div",
|
|
16845
|
+
{
|
|
16846
|
+
className: "w-10 h-10 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center flex-shrink-0 text-indigo-400",
|
|
16847
|
+
style: { ...t.accentSubtle(0.1), ...t.accentBorder, ...t.accent },
|
|
16848
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(SlidesIcon, {})
|
|
16849
|
+
}
|
|
16850
|
+
),
|
|
16851
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "flex-1 min-w-0", children: [
|
|
16852
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("h3", { className: "text-sm font-semibold text-zinc-100 truncate leading-tight", style: t.text, children: title || "Presentation outline" }),
|
|
16853
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("p", { className: "text-xs text-amber-400/90 mt-0.5", children: [
|
|
16854
|
+
"Review the slide outline",
|
|
16855
|
+
slides.length > 0 ? " \u2014 edit any slide message" : "",
|
|
16856
|
+
", then build the deck."
|
|
16857
|
+
] })
|
|
16858
|
+
] })
|
|
16859
|
+
] }),
|
|
16860
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "mt-4 space-y-2", children: [
|
|
16861
|
+
slides.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
16862
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { className: "text-xs text-zinc-600 w-5 text-right tabular-nums flex-shrink-0", children: s.n ?? i + 1 }),
|
|
16863
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(
|
|
16864
|
+
"span",
|
|
16865
|
+
{
|
|
16866
|
+
className: "flex items-center gap-1 text-[10px] uppercase tracking-wide text-zinc-500 w-20 flex-shrink-0 truncate",
|
|
16867
|
+
title: s.type,
|
|
16868
|
+
children: [
|
|
16869
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)(SlideTypeIcon, {}),
|
|
16870
|
+
s.type || "slide"
|
|
16871
|
+
]
|
|
16872
|
+
}
|
|
16873
|
+
),
|
|
16874
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)(
|
|
16875
|
+
"input",
|
|
16876
|
+
{
|
|
16877
|
+
value: messageEdits[i] ?? s.message ?? "",
|
|
16878
|
+
onChange: (e) => setMessageEdits((prev) => ({ ...prev, [i]: e.target.value })),
|
|
16879
|
+
disabled: building,
|
|
16880
|
+
className: "flex-1 min-w-0 bg-zinc-800/60 border border-zinc-700 rounded-lg px-3 h-8 text-xs text-zinc-200 focus:border-indigo-500 outline-none disabled:opacity-60"
|
|
16881
|
+
}
|
|
16882
|
+
)
|
|
16883
|
+
] }, i)),
|
|
16884
|
+
slides.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("p", { className: "text-xs text-zinc-500", children: "No outline slides were returned; you can still build the deck." })
|
|
16885
|
+
] }),
|
|
16886
|
+
approveError && /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("p", { className: "mt-3 text-xs text-red-400/90", children: approveError }),
|
|
16887
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(
|
|
16888
|
+
"button",
|
|
16889
|
+
{
|
|
16890
|
+
onClick: handleApproveOutline,
|
|
16891
|
+
disabled: building,
|
|
16892
|
+
title: "Approve this outline and build the full deck",
|
|
16893
|
+
className: "flex items-center gap-1.5 px-3 h-8 rounded-lg border border-emerald-700/50 bg-emerald-950/40 hover:border-emerald-500 text-emerald-300 text-xs font-medium transition-colors disabled:opacity-50",
|
|
16894
|
+
children: [
|
|
16895
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)(CheckIcon, {}),
|
|
16896
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { children: building ? "Building deck..." : "Approve & Build" })
|
|
16897
|
+
]
|
|
16898
|
+
}
|
|
16899
|
+
) })
|
|
16900
|
+
] })
|
|
16901
|
+
] }) });
|
|
16902
|
+
}
|
|
16589
16903
|
const total = slideCount || 1;
|
|
16590
16904
|
const hasHtml = Boolean(formats.html_url);
|
|
16905
|
+
const templateLabel = formatTemplateLabel(templateId);
|
|
16906
|
+
const isApproved = reviewStatus === "approved";
|
|
16907
|
+
const canApprove = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(_job_id);
|
|
16908
|
+
const canRegenerate = canApprove;
|
|
16591
16909
|
return /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(import_jsx_runtime147.Fragment, { children: [
|
|
16592
16910
|
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", style: t.root, children: [
|
|
16593
16911
|
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("div", { className: "h-[3px]", style: { background: accentGradient || "linear-gradient(90deg, #8B7D5E, #C0AE82, #DCC99B)" } }),
|
|
@@ -16605,10 +16923,39 @@ var PresentationJobCard = ({
|
|
|
16605
16923
|
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("h3", { className: "text-sm font-semibold text-zinc-100 truncate leading-tight", style: t.text, children: title }),
|
|
16606
16924
|
/* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("p", { className: "text-xs text-zinc-500 mt-0.5", style: t.muted, children: [
|
|
16607
16925
|
slideCount ? `${slideCount} slides` : "Presentation",
|
|
16608
|
-
" \xB7 Ready to export"
|
|
16609
|
-
|
|
16926
|
+
" \xB7 Ready to export",
|
|
16927
|
+
templateLabel && ` \xB7 ${templateLabel}`
|
|
16928
|
+
] }),
|
|
16929
|
+
generationMode === "template" && /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("p", { className: "text-xs mt-1", children: isApproved ? /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { className: "text-emerald-400/90", children: "Approved \u2014 future decks use this format" }) : reviewStatus === "pending_review" ? /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { className: "text-amber-400/90", children: "Review this deck, then approve to lock the format" }) : null })
|
|
16610
16930
|
] }),
|
|
16611
|
-
/* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "flex items-center gap-1.5 flex-shrink-0", children: [
|
|
16931
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "flex items-center gap-1.5 flex-shrink-0 flex-wrap justify-end", children: [
|
|
16932
|
+
canRegenerate && /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(
|
|
16933
|
+
"button",
|
|
16934
|
+
{
|
|
16935
|
+
onClick: handleRegenerate,
|
|
16936
|
+
disabled: regenerating || approving,
|
|
16937
|
+
title: "Try another design template with the same content",
|
|
16938
|
+
className: "flex items-center gap-1.5 px-2.5 h-7 rounded-lg border border-zinc-700 bg-zinc-800 hover:border-zinc-500 text-zinc-300 text-xs font-medium transition-colors disabled:opacity-50",
|
|
16939
|
+
children: [
|
|
16940
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)(RefreshIcon, {}),
|
|
16941
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { children: regenerating ? "Switching..." : "Regenerate" })
|
|
16942
|
+
]
|
|
16943
|
+
}
|
|
16944
|
+
),
|
|
16945
|
+
canApprove && /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(
|
|
16946
|
+
"button",
|
|
16947
|
+
{
|
|
16948
|
+
onClick: handleApprove,
|
|
16949
|
+
disabled: approving,
|
|
16950
|
+
title: "Approve and save this deck format for future runs",
|
|
16951
|
+
className: "flex items-center gap-1.5 px-2.5 h-7 rounded-lg border border-emerald-700/50 bg-emerald-950/40 hover:border-emerald-500 text-emerald-300 text-xs font-medium transition-colors disabled:opacity-50",
|
|
16952
|
+
children: [
|
|
16953
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)(CheckIcon, {}),
|
|
16954
|
+
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { children: approving ? "Saving..." : "Approve" })
|
|
16955
|
+
]
|
|
16956
|
+
}
|
|
16957
|
+
),
|
|
16958
|
+
isApproved && /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { className: "flex items-center justify-center w-7 h-7 rounded-lg border border-emerald-500/40 bg-emerald-500/10 text-emerald-400", title: "Format saved", children: /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(CheckIcon, {}) }),
|
|
16612
16959
|
/* @__PURE__ */ (0, import_jsx_runtime147.jsx)(
|
|
16613
16960
|
"button",
|
|
16614
16961
|
{
|
|
@@ -16650,6 +16997,7 @@ var PresentationJobCard = ({
|
|
|
16650
16997
|
)
|
|
16651
16998
|
] })
|
|
16652
16999
|
] }),
|
|
17000
|
+
approveError && /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("p", { className: "mt-3 text-xs text-red-400/90", children: approveError }),
|
|
16653
17001
|
hasHtml && /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "mt-4", children: [
|
|
16654
17002
|
/* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(
|
|
16655
17003
|
"div",
|
|
@@ -16784,11 +17132,11 @@ var ShareIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("svg", {
|
|
|
16784
17132
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
|
|
16785
17133
|
] });
|
|
16786
17134
|
var CheckIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("polyline", { points: "20 6 9 17 4 12" }) });
|
|
16787
|
-
var
|
|
17135
|
+
var RefreshIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
16788
17136
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("polyline", { points: "23 4 23 10 17 10" }),
|
|
16789
17137
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
|
|
16790
17138
|
] });
|
|
16791
|
-
function
|
|
17139
|
+
function formatTemplateLabel2(templateId) {
|
|
16792
17140
|
if (!templateId) return null;
|
|
16793
17141
|
return templateId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
16794
17142
|
}
|
|
@@ -16835,6 +17183,10 @@ var FullscreenPreviewModal = ({ url, title, onClose }) => {
|
|
|
16835
17183
|
) })
|
|
16836
17184
|
] });
|
|
16837
17185
|
};
|
|
17186
|
+
function deriveJobStatusUrl2(newJobId, pollUrl, regenerateUrl) {
|
|
17187
|
+
const base = pollUrl?.replace(/\/api\/jobs\/[^/]+\/status.*$/, "") ?? regenerateUrl?.replace(/\/api\/reports\/[^/]+\/regenerate.*$/, "") ?? "";
|
|
17188
|
+
return `${base}/api/jobs/${newJobId}/status`;
|
|
17189
|
+
}
|
|
16838
17190
|
var ResearchReportJobCard = (props) => {
|
|
16839
17191
|
const {
|
|
16840
17192
|
job_id,
|
|
@@ -16852,6 +17204,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
16852
17204
|
template_id: initialTemplateId,
|
|
16853
17205
|
template_version_id: initialTemplateVersionId,
|
|
16854
17206
|
review_status: initialReviewStatus,
|
|
17207
|
+
outline: initialOutline,
|
|
16855
17208
|
theme: initialTheme,
|
|
16856
17209
|
error: initialError,
|
|
16857
17210
|
progress: initialProgress,
|
|
@@ -16859,7 +17212,9 @@ var ResearchReportJobCard = (props) => {
|
|
|
16859
17212
|
authToken,
|
|
16860
17213
|
shareUrl,
|
|
16861
17214
|
approveUrl,
|
|
17215
|
+
approveOutlineUrl,
|
|
16862
17216
|
regenerateUrl,
|
|
17217
|
+
hideApproval = false,
|
|
16863
17218
|
className,
|
|
16864
17219
|
onComplete,
|
|
16865
17220
|
onFailed,
|
|
@@ -16882,6 +17237,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
16882
17237
|
const [reviewStatus, setReviewStatus] = (0, import_react79.useState)(
|
|
16883
17238
|
initialReviewStatus || (initialHtmlUrl ? "pending_review" : "")
|
|
16884
17239
|
);
|
|
17240
|
+
const [outline, setOutline] = (0, import_react79.useState)(initialOutline);
|
|
16885
17241
|
const [theme, setTheme] = (0, import_react79.useState)(initialTheme || DEFAULT_THEME);
|
|
16886
17242
|
const [error, setError] = (0, import_react79.useState)(initialError);
|
|
16887
17243
|
const [progress, setProgress] = (0, import_react79.useState)(initialProgress);
|
|
@@ -16891,6 +17247,10 @@ var ResearchReportJobCard = (props) => {
|
|
|
16891
17247
|
const [approving, setApproving] = (0, import_react79.useState)(false);
|
|
16892
17248
|
const [regenerating, setRegenerating] = (0, import_react79.useState)(false);
|
|
16893
17249
|
const [approveError, setApproveError] = (0, import_react79.useState)(null);
|
|
17250
|
+
const [headingEdits, setHeadingEdits] = (0, import_react79.useState)({});
|
|
17251
|
+
const [approvingOutline, setApprovingOutline] = (0, import_react79.useState)(false);
|
|
17252
|
+
const [outlineWritePollUrl, setOutlineWritePollUrl] = (0, import_react79.useState)(null);
|
|
17253
|
+
const [regenPollUrl, setRegenPollUrl] = (0, import_react79.useState)(null);
|
|
16894
17254
|
const previewRef = (0, import_react79.useRef)(null);
|
|
16895
17255
|
const onCompleteRef = (0, import_react79.useRef)(onComplete);
|
|
16896
17256
|
const onFailedRef = (0, import_react79.useRef)(onFailed);
|
|
@@ -16919,6 +17279,10 @@ var ResearchReportJobCard = (props) => {
|
|
|
16919
17279
|
(0, import_react79.useEffect)(() => {
|
|
16920
17280
|
if (initialReviewStatus) setReviewStatus(initialReviewStatus);
|
|
16921
17281
|
}, [initialReviewStatus]);
|
|
17282
|
+
const initialOutlineSectionCount = initialOutline?.sections?.length;
|
|
17283
|
+
(0, import_react79.useEffect)(() => {
|
|
17284
|
+
if (initialOutline) setOutline(initialOutline);
|
|
17285
|
+
}, [initialOutlineSectionCount]);
|
|
16922
17286
|
(0, import_react79.useEffect)(() => {
|
|
16923
17287
|
if (initialDepth) setDepth(initialDepth);
|
|
16924
17288
|
}, [initialDepth]);
|
|
@@ -16984,6 +17348,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
16984
17348
|
}
|
|
16985
17349
|
if (newStatus === "complete" && data.output) {
|
|
16986
17350
|
const output = data.output;
|
|
17351
|
+
const isOutlinePhase = output.review_status === "pending_outline_approval" || output.type === "research-report-outline";
|
|
16987
17352
|
setTitle(output.title || initialTitle);
|
|
16988
17353
|
setDepth(output.depth || "");
|
|
16989
17354
|
setSectionCount(output.section_count || 0);
|
|
@@ -16995,10 +17360,11 @@ var ResearchReportJobCard = (props) => {
|
|
|
16995
17360
|
if (output.template_id) setTemplateId(output.template_id);
|
|
16996
17361
|
if (output.template_version_id) setTemplateVersionId(output.template_version_id);
|
|
16997
17362
|
if (output.review_status) setReviewStatus(output.review_status);
|
|
17363
|
+
if (output.outline) setOutline(output.outline);
|
|
16998
17364
|
if (output.theme) {
|
|
16999
17365
|
setTheme(output.theme);
|
|
17000
17366
|
}
|
|
17001
|
-
if (!hasNotifiedRef.current && onCompleteRef.current) {
|
|
17367
|
+
if (!isOutlinePhase && !hasNotifiedRef.current && onCompleteRef.current) {
|
|
17002
17368
|
hasNotifiedRef.current = true;
|
|
17003
17369
|
onCompleteRef.current(output);
|
|
17004
17370
|
}
|
|
@@ -17014,6 +17380,156 @@ var ResearchReportJobCard = (props) => {
|
|
|
17014
17380
|
}
|
|
17015
17381
|
// Transient fetch errors are ignored — the shared loop retries next tick.
|
|
17016
17382
|
);
|
|
17383
|
+
const applyRegeneratedOutput = (0, import_react79.useCallback)(
|
|
17384
|
+
(out) => {
|
|
17385
|
+
if (out.html_url) setHtmlUrl(out.html_url);
|
|
17386
|
+
if (out.template_id) setTemplateId(out.template_id);
|
|
17387
|
+
setReviewStatus("pending_review");
|
|
17388
|
+
setTemplateVersionId("");
|
|
17389
|
+
if (out.section_count !== void 0) setSectionCount(out.section_count);
|
|
17390
|
+
if (out.title) setTitle(out.title);
|
|
17391
|
+
if (out.depth) setDepth(out.depth);
|
|
17392
|
+
if (out.theme) setTheme(out.theme);
|
|
17393
|
+
if (!onCompleteRef.current) return;
|
|
17394
|
+
onCompleteRef.current({
|
|
17395
|
+
title: out.title ?? title,
|
|
17396
|
+
depth: out.depth ?? depth,
|
|
17397
|
+
section_count: out.section_count ?? sectionCount,
|
|
17398
|
+
source_count: out.source_count ?? sourceCount,
|
|
17399
|
+
word_count: out.word_count ?? wordCount,
|
|
17400
|
+
executive_summary: out.executive_summary ?? summary,
|
|
17401
|
+
html_url: out.html_url ?? htmlUrl,
|
|
17402
|
+
generation_mode: "template",
|
|
17403
|
+
template_id: out.template_id ?? templateId,
|
|
17404
|
+
template_version_id: void 0,
|
|
17405
|
+
review_status: "pending_review",
|
|
17406
|
+
theme: out.theme ?? theme
|
|
17407
|
+
});
|
|
17408
|
+
},
|
|
17409
|
+
[title, depth, sectionCount, sourceCount, wordCount, summary, htmlUrl, templateId, theme]
|
|
17410
|
+
);
|
|
17411
|
+
useSharedPoll(
|
|
17412
|
+
{
|
|
17413
|
+
key: regenPollUrl,
|
|
17414
|
+
intervalMs: 3e3,
|
|
17415
|
+
fetcher: async () => {
|
|
17416
|
+
const headers = {};
|
|
17417
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
17418
|
+
const res = await fetch(regenPollUrl, { headers });
|
|
17419
|
+
if (!res.ok) throw new Error(`poll ${res.status}`);
|
|
17420
|
+
return res.json();
|
|
17421
|
+
},
|
|
17422
|
+
shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
|
|
17423
|
+
},
|
|
17424
|
+
(data) => {
|
|
17425
|
+
if (data.status === "complete") {
|
|
17426
|
+
if (data.output) applyRegeneratedOutput(data.output);
|
|
17427
|
+
setRegenPollUrl(null);
|
|
17428
|
+
setRegenerating(false);
|
|
17429
|
+
} else if (data.status === "failed") {
|
|
17430
|
+
setApproveError(data.error || "Could not switch template");
|
|
17431
|
+
setRegenPollUrl(null);
|
|
17432
|
+
setRegenerating(false);
|
|
17433
|
+
}
|
|
17434
|
+
}
|
|
17435
|
+
);
|
|
17436
|
+
const refetchSourceAndApply = (0, import_react79.useCallback)(async () => {
|
|
17437
|
+
if (!pollUrl) return;
|
|
17438
|
+
try {
|
|
17439
|
+
const headers = {};
|
|
17440
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
17441
|
+
const res = await fetch(pollUrl, { headers });
|
|
17442
|
+
if (!res.ok) return;
|
|
17443
|
+
const data = await res.json();
|
|
17444
|
+
const out = data.output;
|
|
17445
|
+
if (!out) return;
|
|
17446
|
+
if (out.title) setTitle(out.title);
|
|
17447
|
+
if (out.depth) setDepth(out.depth);
|
|
17448
|
+
if (out.section_count !== void 0) setSectionCount(out.section_count);
|
|
17449
|
+
if (out.source_count !== void 0) setSourceCount(out.source_count);
|
|
17450
|
+
if (out.word_count !== void 0) setWordCount(out.word_count);
|
|
17451
|
+
if (out.executive_summary) setSummary(out.executive_summary);
|
|
17452
|
+
if (out.html_url) setHtmlUrl(out.html_url);
|
|
17453
|
+
if (out.generation_mode) setGenerationMode(out.generation_mode);
|
|
17454
|
+
if (out.template_id) setTemplateId(out.template_id);
|
|
17455
|
+
setTemplateVersionId(out.template_version_id || "");
|
|
17456
|
+
setReviewStatus(out.review_status || "pending_review");
|
|
17457
|
+
if (out.theme) setTheme(out.theme);
|
|
17458
|
+
if (!hasNotifiedRef.current && onCompleteRef.current) {
|
|
17459
|
+
hasNotifiedRef.current = true;
|
|
17460
|
+
onCompleteRef.current(out);
|
|
17461
|
+
}
|
|
17462
|
+
} catch {
|
|
17463
|
+
}
|
|
17464
|
+
}, [pollUrl, authToken]);
|
|
17465
|
+
useSharedPoll(
|
|
17466
|
+
{
|
|
17467
|
+
key: outlineWritePollUrl,
|
|
17468
|
+
intervalMs: 3e3,
|
|
17469
|
+
fetcher: async () => {
|
|
17470
|
+
const headers = {};
|
|
17471
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
17472
|
+
const res = await fetch(outlineWritePollUrl, { headers });
|
|
17473
|
+
if (!res.ok) throw new Error(`poll ${res.status}`);
|
|
17474
|
+
return res.json();
|
|
17475
|
+
},
|
|
17476
|
+
shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
|
|
17477
|
+
},
|
|
17478
|
+
(data) => {
|
|
17479
|
+
if (data.status === "complete") {
|
|
17480
|
+
setOutlineWritePollUrl(null);
|
|
17481
|
+
setApprovingOutline(false);
|
|
17482
|
+
void refetchSourceAndApply();
|
|
17483
|
+
} else if (data.status === "failed") {
|
|
17484
|
+
setApproveError(data.error || "Could not build the report");
|
|
17485
|
+
setOutlineWritePollUrl(null);
|
|
17486
|
+
setApprovingOutline(false);
|
|
17487
|
+
}
|
|
17488
|
+
}
|
|
17489
|
+
);
|
|
17490
|
+
const buildEditedOutline = () => {
|
|
17491
|
+
if (!outline || !Array.isArray(outline.sections)) return void 0;
|
|
17492
|
+
let changed = false;
|
|
17493
|
+
const sections = outline.sections.map((s, i) => {
|
|
17494
|
+
const edited = headingEdits[i];
|
|
17495
|
+
if (edited !== void 0 && edited !== s.heading) {
|
|
17496
|
+
changed = true;
|
|
17497
|
+
return { ...s, heading: edited };
|
|
17498
|
+
}
|
|
17499
|
+
return s;
|
|
17500
|
+
});
|
|
17501
|
+
return changed ? { ...outline, sections } : void 0;
|
|
17502
|
+
};
|
|
17503
|
+
const handleApproveOutline = async () => {
|
|
17504
|
+
if (!job_id || approvingOutline) return;
|
|
17505
|
+
const endpoint = approveOutlineUrl || `/api/reports/${job_id}/approve-outline`;
|
|
17506
|
+
setApprovingOutline(true);
|
|
17507
|
+
setApproveError(null);
|
|
17508
|
+
try {
|
|
17509
|
+
const headers = { "Content-Type": "application/json" };
|
|
17510
|
+
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
|
17511
|
+
const editedOutline = buildEditedOutline();
|
|
17512
|
+
const res = await fetch(endpoint, {
|
|
17513
|
+
method: "POST",
|
|
17514
|
+
headers,
|
|
17515
|
+
body: JSON.stringify(editedOutline ? { outline: editedOutline } : {})
|
|
17516
|
+
});
|
|
17517
|
+
if (!res.ok) {
|
|
17518
|
+
const err = await res.json().catch(() => ({}));
|
|
17519
|
+
throw new Error(err.detail || err.error || `Approve failed (${res.status})`);
|
|
17520
|
+
}
|
|
17521
|
+
const data = await res.json();
|
|
17522
|
+
if (typeof data.job_id === "string" && data.job_id !== job_id) {
|
|
17523
|
+
setOutlineWritePollUrl(deriveJobStatusUrl2(data.job_id, pollUrl, regenerateUrl));
|
|
17524
|
+
return;
|
|
17525
|
+
}
|
|
17526
|
+
setApprovingOutline(false);
|
|
17527
|
+
void refetchSourceAndApply();
|
|
17528
|
+
} catch (e) {
|
|
17529
|
+
setApproveError(e instanceof Error ? e.message : "Could not build the report");
|
|
17530
|
+
setApprovingOutline(false);
|
|
17531
|
+
}
|
|
17532
|
+
};
|
|
17017
17533
|
const formatWordCount = (count) => {
|
|
17018
17534
|
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
|
|
17019
17535
|
return count.toString();
|
|
@@ -17119,6 +17635,61 @@ var ResearchReportJobCard = (props) => {
|
|
|
17119
17635
|
] })
|
|
17120
17636
|
] }) }) });
|
|
17121
17637
|
}
|
|
17638
|
+
if (status === "complete" && reviewStatus === "pending_outline_approval") {
|
|
17639
|
+
const sections = Array.isArray(outline?.sections) ? outline.sections : [];
|
|
17640
|
+
const building = Boolean(outlineWritePollUrl) || approvingOutline;
|
|
17641
|
+
return /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
|
|
17642
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
|
|
17643
|
+
"div",
|
|
17644
|
+
{
|
|
17645
|
+
className: "h-[3px]",
|
|
17646
|
+
style: { background: "linear-gradient(90deg, #8B7D5E, #C0AE82, #DCC99B)" }
|
|
17647
|
+
}
|
|
17648
|
+
),
|
|
17649
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "p-5", children: [
|
|
17650
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "flex items-start gap-3", children: [
|
|
17651
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("div", { className: "w-10 h-10 rounded-xl bg-violet-500/10 border border-violet-500/20 flex items-center justify-center flex-shrink-0 text-violet-300", children: /* @__PURE__ */ (0, import_jsx_runtime148.jsx)(DocumentIcon, {}) }),
|
|
17652
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "flex-1 min-w-0", children: [
|
|
17653
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("h3", { className: "text-sm font-semibold text-zinc-100 truncate leading-tight", children: title || "Report outline" }),
|
|
17654
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("p", { className: "text-xs text-amber-400/90 mt-0.5", children: [
|
|
17655
|
+
"Review the outline",
|
|
17656
|
+
sections.length > 0 ? " \u2014 edit any heading" : "",
|
|
17657
|
+
", then build the full report."
|
|
17658
|
+
] })
|
|
17659
|
+
] })
|
|
17660
|
+
] }),
|
|
17661
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "mt-4 space-y-2", children: [
|
|
17662
|
+
sections.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
17663
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("span", { className: "text-xs text-zinc-600 w-5 text-right tabular-nums flex-shrink-0", children: i + 1 }),
|
|
17664
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
|
|
17665
|
+
"input",
|
|
17666
|
+
{
|
|
17667
|
+
value: headingEdits[i] ?? s.heading ?? "",
|
|
17668
|
+
onChange: (e) => setHeadingEdits((prev) => ({ ...prev, [i]: e.target.value })),
|
|
17669
|
+
disabled: building,
|
|
17670
|
+
className: "flex-1 min-w-0 bg-zinc-800/60 border border-zinc-700 rounded-lg px-3 h-8 text-xs text-zinc-200 focus:border-violet-500 outline-none disabled:opacity-60"
|
|
17671
|
+
}
|
|
17672
|
+
)
|
|
17673
|
+
] }, i)),
|
|
17674
|
+
sections.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("p", { className: "text-xs text-zinc-500", children: "No outline sections were returned; you can still build the report." })
|
|
17675
|
+
] }),
|
|
17676
|
+
approveError && /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("p", { className: "mt-3 text-xs text-red-400/90", children: approveError }),
|
|
17677
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)(
|
|
17678
|
+
"button",
|
|
17679
|
+
{
|
|
17680
|
+
onClick: handleApproveOutline,
|
|
17681
|
+
disabled: building,
|
|
17682
|
+
title: "Approve this outline and build the full report",
|
|
17683
|
+
className: "flex items-center gap-1.5 px-3 h-8 rounded-lg border border-emerald-700/50 bg-emerald-950/40 hover:border-emerald-500 text-emerald-300 text-xs font-medium transition-colors disabled:opacity-50",
|
|
17684
|
+
children: [
|
|
17685
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(CheckIcon2, {}),
|
|
17686
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("span", { children: building ? "Building report..." : "Approve & Build" })
|
|
17687
|
+
]
|
|
17688
|
+
}
|
|
17689
|
+
) })
|
|
17690
|
+
] })
|
|
17691
|
+
] }) });
|
|
17692
|
+
}
|
|
17122
17693
|
if (status === "complete" && !hasHTML) {
|
|
17123
17694
|
return /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
|
|
17124
17695
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
|
|
@@ -17182,8 +17753,8 @@ var ResearchReportJobCard = (props) => {
|
|
|
17182
17753
|
window.open(link, "_blank", "noopener,noreferrer");
|
|
17183
17754
|
}
|
|
17184
17755
|
};
|
|
17185
|
-
const canApprove = generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
|
|
17186
|
-
const canRegenerate = generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
|
|
17756
|
+
const canApprove = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
|
|
17757
|
+
const canRegenerate = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
|
|
17187
17758
|
const canShare = Boolean(hasHTML || job_id) && (generationMode !== "template" || reviewStatus !== "pending_review");
|
|
17188
17759
|
const handleApprove = async () => {
|
|
17189
17760
|
if (!job_id || approving) return;
|
|
@@ -17247,36 +17818,18 @@ var ResearchReportJobCard = (props) => {
|
|
|
17247
17818
|
throw new Error(err.detail || err.error || `Regenerate failed (${res.status})`);
|
|
17248
17819
|
}
|
|
17249
17820
|
const data = await res.json();
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
setReviewStatus("pending_review");
|
|
17254
|
-
setTemplateVersionId("");
|
|
17255
|
-
if (out.section_count !== void 0) setSectionCount(out.section_count);
|
|
17256
|
-
if (out.title) setTitle(out.title);
|
|
17257
|
-
if (onCompleteRef.current) {
|
|
17258
|
-
onCompleteRef.current({
|
|
17259
|
-
title: out.title || title,
|
|
17260
|
-
depth,
|
|
17261
|
-
section_count: out.section_count ?? sectionCount,
|
|
17262
|
-
source_count: sourceCount,
|
|
17263
|
-
word_count: wordCount,
|
|
17264
|
-
executive_summary: summary,
|
|
17265
|
-
html_url: out.html_url || htmlUrl,
|
|
17266
|
-
generation_mode: "template",
|
|
17267
|
-
template_id: data.template_id || out.template_id || templateId,
|
|
17268
|
-
template_version_id: void 0,
|
|
17269
|
-
review_status: "pending_review",
|
|
17270
|
-
theme
|
|
17271
|
-
});
|
|
17821
|
+
if (typeof data.job_id === "string" && data.job_id !== job_id) {
|
|
17822
|
+
setRegenPollUrl(deriveJobStatusUrl2(data.job_id, pollUrl, regenerateUrl));
|
|
17823
|
+
return;
|
|
17272
17824
|
}
|
|
17825
|
+
applyRegeneratedOutput(data.output || data);
|
|
17826
|
+
setRegenerating(false);
|
|
17273
17827
|
} catch (e) {
|
|
17274
17828
|
setApproveError(e instanceof Error ? e.message : "Could not switch template");
|
|
17275
|
-
} finally {
|
|
17276
17829
|
setRegenerating(false);
|
|
17277
17830
|
}
|
|
17278
17831
|
};
|
|
17279
|
-
const templateLabel =
|
|
17832
|
+
const templateLabel = formatTemplateLabel2(templateId);
|
|
17280
17833
|
const isApproved = reviewStatus === "approved";
|
|
17281
17834
|
return /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)(import_jsx_runtime148.Fragment, { children: [
|
|
17282
17835
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
|
|
@@ -17325,7 +17878,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17325
17878
|
title: "Try another report template with the same data",
|
|
17326
17879
|
className: "flex items-center gap-1.5 px-2.5 h-7 rounded-lg border border-zinc-700 bg-zinc-800 hover:border-zinc-500 text-zinc-300 text-xs font-medium transition-colors disabled:opacity-50",
|
|
17327
17880
|
children: [
|
|
17328
|
-
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
|
|
17881
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(RefreshIcon2, {}),
|
|
17329
17882
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("span", { children: regenerating ? "Switching..." : "Regenerate" })
|
|
17330
17883
|
]
|
|
17331
17884
|
}
|