pxengine 0.1.113 → 0.1.115
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 +569 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -1
- package/dist/index.d.ts +79 -1
- package/dist/index.mjs +569 -17
- package/dist/index.mjs.map +1 -1
- package/dist/registry.json +67 -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,7 +17183,7 @@ var FullscreenPreviewModal = ({ url, title, onClose }) => {
|
|
|
16835
17183
|
) })
|
|
16836
17184
|
] });
|
|
16837
17185
|
};
|
|
16838
|
-
function
|
|
17186
|
+
function deriveJobStatusUrl2(newJobId, pollUrl, regenerateUrl) {
|
|
16839
17187
|
const base = pollUrl?.replace(/\/api\/jobs\/[^/]+\/status.*$/, "") ?? regenerateUrl?.replace(/\/api\/reports\/[^/]+\/regenerate.*$/, "") ?? "";
|
|
16840
17188
|
return `${base}/api/jobs/${newJobId}/status`;
|
|
16841
17189
|
}
|
|
@@ -16852,10 +17200,12 @@ var ResearchReportJobCard = (props) => {
|
|
|
16852
17200
|
executive_summary: initialSummary,
|
|
16853
17201
|
key_findings: _initialFindings,
|
|
16854
17202
|
html_url: initialHtmlUrl,
|
|
17203
|
+
pdf_url: initialPdfUrl,
|
|
16855
17204
|
generation_mode: initialGenerationMode,
|
|
16856
17205
|
template_id: initialTemplateId,
|
|
16857
17206
|
template_version_id: initialTemplateVersionId,
|
|
16858
17207
|
review_status: initialReviewStatus,
|
|
17208
|
+
outline: initialOutline,
|
|
16859
17209
|
theme: initialTheme,
|
|
16860
17210
|
error: initialError,
|
|
16861
17211
|
progress: initialProgress,
|
|
@@ -16863,7 +17213,9 @@ var ResearchReportJobCard = (props) => {
|
|
|
16863
17213
|
authToken,
|
|
16864
17214
|
shareUrl,
|
|
16865
17215
|
approveUrl,
|
|
17216
|
+
approveOutlineUrl,
|
|
16866
17217
|
regenerateUrl,
|
|
17218
|
+
hideApproval = false,
|
|
16867
17219
|
className,
|
|
16868
17220
|
onComplete,
|
|
16869
17221
|
onFailed,
|
|
@@ -16878,6 +17230,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
16878
17230
|
const [wordCount, setWordCount] = (0, import_react79.useState)(initialWordCount ?? 0);
|
|
16879
17231
|
const [summary, setSummary] = (0, import_react79.useState)(initialSummary || "");
|
|
16880
17232
|
const [htmlUrl, setHtmlUrl] = (0, import_react79.useState)(initialHtmlUrl || "");
|
|
17233
|
+
const [pdfUrl, setPdfUrl] = (0, import_react79.useState)(initialPdfUrl || "");
|
|
16881
17234
|
const [generationMode, setGenerationMode] = (0, import_react79.useState)(
|
|
16882
17235
|
initialGenerationMode || (initialHtmlUrl ? "template" : "")
|
|
16883
17236
|
);
|
|
@@ -16886,6 +17239,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
16886
17239
|
const [reviewStatus, setReviewStatus] = (0, import_react79.useState)(
|
|
16887
17240
|
initialReviewStatus || (initialHtmlUrl ? "pending_review" : "")
|
|
16888
17241
|
);
|
|
17242
|
+
const [outline, setOutline] = (0, import_react79.useState)(initialOutline);
|
|
16889
17243
|
const [theme, setTheme] = (0, import_react79.useState)(initialTheme || DEFAULT_THEME);
|
|
16890
17244
|
const [error, setError] = (0, import_react79.useState)(initialError);
|
|
16891
17245
|
const [progress, setProgress] = (0, import_react79.useState)(initialProgress);
|
|
@@ -16895,6 +17249,9 @@ var ResearchReportJobCard = (props) => {
|
|
|
16895
17249
|
const [approving, setApproving] = (0, import_react79.useState)(false);
|
|
16896
17250
|
const [regenerating, setRegenerating] = (0, import_react79.useState)(false);
|
|
16897
17251
|
const [approveError, setApproveError] = (0, import_react79.useState)(null);
|
|
17252
|
+
const [headingEdits, setHeadingEdits] = (0, import_react79.useState)({});
|
|
17253
|
+
const [approvingOutline, setApprovingOutline] = (0, import_react79.useState)(false);
|
|
17254
|
+
const [outlineWritePollUrl, setOutlineWritePollUrl] = (0, import_react79.useState)(null);
|
|
16898
17255
|
const [regenPollUrl, setRegenPollUrl] = (0, import_react79.useState)(null);
|
|
16899
17256
|
const previewRef = (0, import_react79.useRef)(null);
|
|
16900
17257
|
const onCompleteRef = (0, import_react79.useRef)(onComplete);
|
|
@@ -16924,6 +17281,10 @@ var ResearchReportJobCard = (props) => {
|
|
|
16924
17281
|
(0, import_react79.useEffect)(() => {
|
|
16925
17282
|
if (initialReviewStatus) setReviewStatus(initialReviewStatus);
|
|
16926
17283
|
}, [initialReviewStatus]);
|
|
17284
|
+
const initialOutlineSectionCount = initialOutline?.sections?.length;
|
|
17285
|
+
(0, import_react79.useEffect)(() => {
|
|
17286
|
+
if (initialOutline) setOutline(initialOutline);
|
|
17287
|
+
}, [initialOutlineSectionCount]);
|
|
16927
17288
|
(0, import_react79.useEffect)(() => {
|
|
16928
17289
|
if (initialDepth) setDepth(initialDepth);
|
|
16929
17290
|
}, [initialDepth]);
|
|
@@ -16989,6 +17350,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
16989
17350
|
}
|
|
16990
17351
|
if (newStatus === "complete" && data.output) {
|
|
16991
17352
|
const output = data.output;
|
|
17353
|
+
const isOutlinePhase = output.review_status === "pending_outline_approval" || output.type === "research-report-outline";
|
|
16992
17354
|
setTitle(output.title || initialTitle);
|
|
16993
17355
|
setDepth(output.depth || "");
|
|
16994
17356
|
setSectionCount(output.section_count || 0);
|
|
@@ -16996,14 +17358,16 @@ var ResearchReportJobCard = (props) => {
|
|
|
16996
17358
|
setWordCount(output.word_count || 0);
|
|
16997
17359
|
setSummary(output.executive_summary || "");
|
|
16998
17360
|
setHtmlUrl(output.html_url || "");
|
|
17361
|
+
setPdfUrl(output.pdf_url || "");
|
|
16999
17362
|
if (output.generation_mode) setGenerationMode(output.generation_mode);
|
|
17000
17363
|
if (output.template_id) setTemplateId(output.template_id);
|
|
17001
17364
|
if (output.template_version_id) setTemplateVersionId(output.template_version_id);
|
|
17002
17365
|
if (output.review_status) setReviewStatus(output.review_status);
|
|
17366
|
+
if (output.outline) setOutline(output.outline);
|
|
17003
17367
|
if (output.theme) {
|
|
17004
17368
|
setTheme(output.theme);
|
|
17005
17369
|
}
|
|
17006
|
-
if (!hasNotifiedRef.current && onCompleteRef.current) {
|
|
17370
|
+
if (!isOutlinePhase && !hasNotifiedRef.current && onCompleteRef.current) {
|
|
17007
17371
|
hasNotifiedRef.current = true;
|
|
17008
17372
|
onCompleteRef.current(output);
|
|
17009
17373
|
}
|
|
@@ -17022,6 +17386,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17022
17386
|
const applyRegeneratedOutput = (0, import_react79.useCallback)(
|
|
17023
17387
|
(out) => {
|
|
17024
17388
|
if (out.html_url) setHtmlUrl(out.html_url);
|
|
17389
|
+
setPdfUrl(out.pdf_url || "");
|
|
17025
17390
|
if (out.template_id) setTemplateId(out.template_id);
|
|
17026
17391
|
setReviewStatus("pending_review");
|
|
17027
17392
|
setTemplateVersionId("");
|
|
@@ -17038,6 +17403,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17038
17403
|
word_count: out.word_count ?? wordCount,
|
|
17039
17404
|
executive_summary: out.executive_summary ?? summary,
|
|
17040
17405
|
html_url: out.html_url ?? htmlUrl,
|
|
17406
|
+
pdf_url: out.pdf_url ?? pdfUrl,
|
|
17041
17407
|
generation_mode: "template",
|
|
17042
17408
|
template_id: out.template_id ?? templateId,
|
|
17043
17409
|
template_version_id: void 0,
|
|
@@ -17045,7 +17411,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17045
17411
|
theme: out.theme ?? theme
|
|
17046
17412
|
});
|
|
17047
17413
|
},
|
|
17048
|
-
[title, depth, sectionCount, sourceCount, wordCount, summary, htmlUrl, templateId, theme]
|
|
17414
|
+
[title, depth, sectionCount, sourceCount, wordCount, summary, htmlUrl, pdfUrl, templateId, theme]
|
|
17049
17415
|
);
|
|
17050
17416
|
useSharedPoll(
|
|
17051
17417
|
{
|
|
@@ -17072,6 +17438,104 @@ var ResearchReportJobCard = (props) => {
|
|
|
17072
17438
|
}
|
|
17073
17439
|
}
|
|
17074
17440
|
);
|
|
17441
|
+
const refetchSourceAndApply = (0, import_react79.useCallback)(async () => {
|
|
17442
|
+
if (!pollUrl) return;
|
|
17443
|
+
try {
|
|
17444
|
+
const headers = {};
|
|
17445
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
17446
|
+
const res = await fetch(pollUrl, { headers });
|
|
17447
|
+
if (!res.ok) return;
|
|
17448
|
+
const data = await res.json();
|
|
17449
|
+
const out = data.output;
|
|
17450
|
+
if (!out) return;
|
|
17451
|
+
if (out.title) setTitle(out.title);
|
|
17452
|
+
if (out.depth) setDepth(out.depth);
|
|
17453
|
+
if (out.section_count !== void 0) setSectionCount(out.section_count);
|
|
17454
|
+
if (out.source_count !== void 0) setSourceCount(out.source_count);
|
|
17455
|
+
if (out.word_count !== void 0) setWordCount(out.word_count);
|
|
17456
|
+
if (out.executive_summary) setSummary(out.executive_summary);
|
|
17457
|
+
if (out.html_url) setHtmlUrl(out.html_url);
|
|
17458
|
+
setPdfUrl(out.pdf_url || "");
|
|
17459
|
+
if (out.generation_mode) setGenerationMode(out.generation_mode);
|
|
17460
|
+
if (out.template_id) setTemplateId(out.template_id);
|
|
17461
|
+
setTemplateVersionId(out.template_version_id || "");
|
|
17462
|
+
setReviewStatus(out.review_status || "pending_review");
|
|
17463
|
+
if (out.theme) setTheme(out.theme);
|
|
17464
|
+
if (!hasNotifiedRef.current && onCompleteRef.current) {
|
|
17465
|
+
hasNotifiedRef.current = true;
|
|
17466
|
+
onCompleteRef.current(out);
|
|
17467
|
+
}
|
|
17468
|
+
} catch {
|
|
17469
|
+
}
|
|
17470
|
+
}, [pollUrl, authToken]);
|
|
17471
|
+
useSharedPoll(
|
|
17472
|
+
{
|
|
17473
|
+
key: outlineWritePollUrl,
|
|
17474
|
+
intervalMs: 3e3,
|
|
17475
|
+
fetcher: async () => {
|
|
17476
|
+
const headers = {};
|
|
17477
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
17478
|
+
const res = await fetch(outlineWritePollUrl, { headers });
|
|
17479
|
+
if (!res.ok) throw new Error(`poll ${res.status}`);
|
|
17480
|
+
return res.json();
|
|
17481
|
+
},
|
|
17482
|
+
shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
|
|
17483
|
+
},
|
|
17484
|
+
(data) => {
|
|
17485
|
+
if (data.status === "complete") {
|
|
17486
|
+
setOutlineWritePollUrl(null);
|
|
17487
|
+
setApprovingOutline(false);
|
|
17488
|
+
void refetchSourceAndApply();
|
|
17489
|
+
} else if (data.status === "failed") {
|
|
17490
|
+
setApproveError(data.error || "Could not build the report");
|
|
17491
|
+
setOutlineWritePollUrl(null);
|
|
17492
|
+
setApprovingOutline(false);
|
|
17493
|
+
}
|
|
17494
|
+
}
|
|
17495
|
+
);
|
|
17496
|
+
const buildEditedOutline = () => {
|
|
17497
|
+
if (!outline || !Array.isArray(outline.sections)) return void 0;
|
|
17498
|
+
let changed = false;
|
|
17499
|
+
const sections = outline.sections.map((s, i) => {
|
|
17500
|
+
const edited = headingEdits[i];
|
|
17501
|
+
if (edited !== void 0 && edited !== s.heading) {
|
|
17502
|
+
changed = true;
|
|
17503
|
+
return { ...s, heading: edited };
|
|
17504
|
+
}
|
|
17505
|
+
return s;
|
|
17506
|
+
});
|
|
17507
|
+
return changed ? { ...outline, sections } : void 0;
|
|
17508
|
+
};
|
|
17509
|
+
const handleApproveOutline = async () => {
|
|
17510
|
+
if (!job_id || approvingOutline) return;
|
|
17511
|
+
const endpoint = approveOutlineUrl || `/api/reports/${job_id}/approve-outline`;
|
|
17512
|
+
setApprovingOutline(true);
|
|
17513
|
+
setApproveError(null);
|
|
17514
|
+
try {
|
|
17515
|
+
const headers = { "Content-Type": "application/json" };
|
|
17516
|
+
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
|
17517
|
+
const editedOutline = buildEditedOutline();
|
|
17518
|
+
const res = await fetch(endpoint, {
|
|
17519
|
+
method: "POST",
|
|
17520
|
+
headers,
|
|
17521
|
+
body: JSON.stringify(editedOutline ? { outline: editedOutline } : {})
|
|
17522
|
+
});
|
|
17523
|
+
if (!res.ok) {
|
|
17524
|
+
const err = await res.json().catch(() => ({}));
|
|
17525
|
+
throw new Error(err.detail || err.error || `Approve failed (${res.status})`);
|
|
17526
|
+
}
|
|
17527
|
+
const data = await res.json();
|
|
17528
|
+
if (typeof data.job_id === "string" && data.job_id !== job_id) {
|
|
17529
|
+
setOutlineWritePollUrl(deriveJobStatusUrl2(data.job_id, pollUrl, regenerateUrl));
|
|
17530
|
+
return;
|
|
17531
|
+
}
|
|
17532
|
+
setApprovingOutline(false);
|
|
17533
|
+
void refetchSourceAndApply();
|
|
17534
|
+
} catch (e) {
|
|
17535
|
+
setApproveError(e instanceof Error ? e.message : "Could not build the report");
|
|
17536
|
+
setApprovingOutline(false);
|
|
17537
|
+
}
|
|
17538
|
+
};
|
|
17075
17539
|
const formatWordCount = (count) => {
|
|
17076
17540
|
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
|
|
17077
17541
|
return count.toString();
|
|
@@ -17177,6 +17641,61 @@ var ResearchReportJobCard = (props) => {
|
|
|
17177
17641
|
] })
|
|
17178
17642
|
] }) }) });
|
|
17179
17643
|
}
|
|
17644
|
+
if (status === "complete" && reviewStatus === "pending_outline_approval") {
|
|
17645
|
+
const sections = Array.isArray(outline?.sections) ? outline.sections : [];
|
|
17646
|
+
const building = Boolean(outlineWritePollUrl) || approvingOutline;
|
|
17647
|
+
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: [
|
|
17648
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
|
|
17649
|
+
"div",
|
|
17650
|
+
{
|
|
17651
|
+
className: "h-[3px]",
|
|
17652
|
+
style: { background: "linear-gradient(90deg, #8B7D5E, #C0AE82, #DCC99B)" }
|
|
17653
|
+
}
|
|
17654
|
+
),
|
|
17655
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "p-5", children: [
|
|
17656
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "flex items-start gap-3", children: [
|
|
17657
|
+
/* @__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, {}) }),
|
|
17658
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "flex-1 min-w-0", children: [
|
|
17659
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("h3", { className: "text-sm font-semibold text-zinc-100 truncate leading-tight", children: title || "Report outline" }),
|
|
17660
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("p", { className: "text-xs text-amber-400/90 mt-0.5", children: [
|
|
17661
|
+
"Review the outline",
|
|
17662
|
+
sections.length > 0 ? " \u2014 edit any heading" : "",
|
|
17663
|
+
", then build the full report."
|
|
17664
|
+
] })
|
|
17665
|
+
] })
|
|
17666
|
+
] }),
|
|
17667
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "mt-4 space-y-2", children: [
|
|
17668
|
+
sections.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
17669
|
+
/* @__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 }),
|
|
17670
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
|
|
17671
|
+
"input",
|
|
17672
|
+
{
|
|
17673
|
+
value: headingEdits[i] ?? s.heading ?? "",
|
|
17674
|
+
onChange: (e) => setHeadingEdits((prev) => ({ ...prev, [i]: e.target.value })),
|
|
17675
|
+
disabled: building,
|
|
17676
|
+
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"
|
|
17677
|
+
}
|
|
17678
|
+
)
|
|
17679
|
+
] }, i)),
|
|
17680
|
+
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." })
|
|
17681
|
+
] }),
|
|
17682
|
+
approveError && /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("p", { className: "mt-3 text-xs text-red-400/90", children: approveError }),
|
|
17683
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)(
|
|
17684
|
+
"button",
|
|
17685
|
+
{
|
|
17686
|
+
onClick: handleApproveOutline,
|
|
17687
|
+
disabled: building,
|
|
17688
|
+
title: "Approve this outline and build the full report",
|
|
17689
|
+
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",
|
|
17690
|
+
children: [
|
|
17691
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(CheckIcon2, {}),
|
|
17692
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("span", { children: building ? "Building report..." : "Approve & Build" })
|
|
17693
|
+
]
|
|
17694
|
+
}
|
|
17695
|
+
) })
|
|
17696
|
+
] })
|
|
17697
|
+
] }) });
|
|
17698
|
+
}
|
|
17180
17699
|
if (status === "complete" && !hasHTML) {
|
|
17181
17700
|
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: [
|
|
17182
17701
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
|
|
@@ -17222,6 +17741,26 @@ var ResearchReportJobCard = (props) => {
|
|
|
17222
17741
|
window.open(htmlUrl, "_blank", "noopener,noreferrer");
|
|
17223
17742
|
}
|
|
17224
17743
|
};
|
|
17744
|
+
const handleDownloadPdf = async () => {
|
|
17745
|
+
if (!pdfUrl) return;
|
|
17746
|
+
const filename = `${(title ?? "").replace(/[^a-z0-9]/gi, "-").toLowerCase()}.pdf`;
|
|
17747
|
+
const href = `/api/download?url=${encodeURIComponent(pdfUrl)}&filename=${encodeURIComponent(filename)}`;
|
|
17748
|
+
try {
|
|
17749
|
+
const response = await fetch(href);
|
|
17750
|
+
if (!response.ok) throw new Error(`status ${response.status}`);
|
|
17751
|
+
const blob = await response.blob();
|
|
17752
|
+
const objectUrl = window.URL.createObjectURL(blob);
|
|
17753
|
+
const anchor = document.createElement("a");
|
|
17754
|
+
anchor.href = objectUrl;
|
|
17755
|
+
anchor.download = filename;
|
|
17756
|
+
document.body.appendChild(anchor);
|
|
17757
|
+
anchor.click();
|
|
17758
|
+
anchor.remove();
|
|
17759
|
+
window.URL.revokeObjectURL(objectUrl);
|
|
17760
|
+
} catch {
|
|
17761
|
+
window.open(pdfUrl, "_blank", "noopener,noreferrer");
|
|
17762
|
+
}
|
|
17763
|
+
};
|
|
17225
17764
|
const resolveShareLink = () => {
|
|
17226
17765
|
if (shareUrl) return shareUrl;
|
|
17227
17766
|
if (typeof window !== "undefined" && job_id) {
|
|
@@ -17240,8 +17779,8 @@ var ResearchReportJobCard = (props) => {
|
|
|
17240
17779
|
window.open(link, "_blank", "noopener,noreferrer");
|
|
17241
17780
|
}
|
|
17242
17781
|
};
|
|
17243
|
-
const canApprove = generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
|
|
17244
|
-
const canRegenerate = generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
|
|
17782
|
+
const canApprove = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
|
|
17783
|
+
const canRegenerate = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
|
|
17245
17784
|
const canShare = Boolean(hasHTML || job_id) && (generationMode !== "template" || reviewStatus !== "pending_review");
|
|
17246
17785
|
const handleApprove = async () => {
|
|
17247
17786
|
if (!job_id || approving) return;
|
|
@@ -17274,6 +17813,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17274
17813
|
word_count: wordCount,
|
|
17275
17814
|
executive_summary: summary,
|
|
17276
17815
|
html_url: htmlUrl,
|
|
17816
|
+
pdf_url: pdfUrl,
|
|
17277
17817
|
generation_mode: generationMode,
|
|
17278
17818
|
template_id: data.template_id || templateId,
|
|
17279
17819
|
template_version_id: data.template_version_id || templateVersionId,
|
|
@@ -17306,7 +17846,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17306
17846
|
}
|
|
17307
17847
|
const data = await res.json();
|
|
17308
17848
|
if (typeof data.job_id === "string" && data.job_id !== job_id) {
|
|
17309
|
-
setRegenPollUrl(
|
|
17849
|
+
setRegenPollUrl(deriveJobStatusUrl2(data.job_id, pollUrl, regenerateUrl));
|
|
17310
17850
|
return;
|
|
17311
17851
|
}
|
|
17312
17852
|
applyRegeneratedOutput(data.output || data);
|
|
@@ -17316,7 +17856,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17316
17856
|
setRegenerating(false);
|
|
17317
17857
|
}
|
|
17318
17858
|
};
|
|
17319
|
-
const templateLabel =
|
|
17859
|
+
const templateLabel = formatTemplateLabel2(templateId);
|
|
17320
17860
|
const isApproved = reviewStatus === "approved";
|
|
17321
17861
|
return /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)(import_jsx_runtime148.Fragment, { children: [
|
|
17322
17862
|
/* @__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: [
|
|
@@ -17365,7 +17905,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17365
17905
|
title: "Try another report template with the same data",
|
|
17366
17906
|
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",
|
|
17367
17907
|
children: [
|
|
17368
|
-
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
|
|
17908
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(RefreshIcon2, {}),
|
|
17369
17909
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("span", { children: regenerating ? "Switching..." : "Regenerate" })
|
|
17370
17910
|
]
|
|
17371
17911
|
}
|
|
@@ -17411,6 +17951,18 @@ var ResearchReportJobCard = (props) => {
|
|
|
17411
17951
|
]
|
|
17412
17952
|
}
|
|
17413
17953
|
),
|
|
17954
|
+
pdfUrl && /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)(
|
|
17955
|
+
"button",
|
|
17956
|
+
{
|
|
17957
|
+
onClick: handleDownloadPdf,
|
|
17958
|
+
title: "Download PDF",
|
|
17959
|
+
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-400 hover:text-zinc-200 text-xs font-medium transition-colors",
|
|
17960
|
+
children: [
|
|
17961
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(DownloadIcon2, {}),
|
|
17962
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("span", { children: "PDF" })
|
|
17963
|
+
]
|
|
17964
|
+
}
|
|
17965
|
+
),
|
|
17414
17966
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsxs)(
|
|
17415
17967
|
"button",
|
|
17416
17968
|
{
|