Phase 10L.1: Apply Structure Sync.
This commit is contained in:
parent
cb1be6b6ba
commit
69da86fbd6
@ -189,7 +189,10 @@
|
||||
<section class="word-companion-section word-companion-structure-preview" aria-label="Structure Sync Preview">
|
||||
<div class="word-companion-section-header">
|
||||
<span>Structure Sync Preview</span>
|
||||
<button type="button" data-analyse-manuscript-structure disabled>Analyse Manuscript Structure</button>
|
||||
<div class="word-companion-header-actions">
|
||||
<button type="button" data-analyse-manuscript-structure disabled>Analyse Manuscript Structure</button>
|
||||
<button type="button" data-apply-structure-sync disabled>Apply Structure Sync</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="word-companion-message" data-structure-preview-status>Select a project and book to analyse manuscript structure.</p>
|
||||
<div class="word-companion-preview-groups" data-structure-preview-results>
|
||||
@ -373,6 +376,21 @@
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<dialog class="word-companion-dialog" data-apply-structure-sync-dialog>
|
||||
<form method="dialog">
|
||||
<div class="word-companion-section-header">
|
||||
<span>Apply Structure Sync?</span>
|
||||
</div>
|
||||
<p class="word-companion-dialog-copy">This will:</p>
|
||||
<div class="word-companion-apply-summary" data-apply-structure-sync-summary></div>
|
||||
<p class="word-companion-dialog-copy">Manual review items will be skipped.</p>
|
||||
<div class="word-companion-dialog-actions">
|
||||
<button type="button" data-apply-structure-sync-confirm>Apply</button>
|
||||
<button type="button" data-apply-structure-sync-cancel>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
</main>
|
||||
<script src="~/js/word-companion-host.js" asp-append-version="true"></script>
|
||||
</body>
|
||||
|
||||
@ -129,6 +129,13 @@ body {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.word-companion-header-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.word-companion-section-header button {
|
||||
min-height: 32px;
|
||||
border: 1px solid var(--companion-accent);
|
||||
@ -391,6 +398,18 @@ body {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.word-companion-apply-summary ul {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.word-companion-apply-summary li {
|
||||
color: var(--companion-ink);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.word-companion-dialog {
|
||||
width: min(360px, calc(100vw - 24px));
|
||||
border: 1px solid var(--companion-line);
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
const syncNote = document.querySelector("[data-sync-note]");
|
||||
const documentOutline = document.querySelector("[data-document-outline]");
|
||||
const analyseManuscriptStructureButton = document.querySelector("[data-analyse-manuscript-structure]");
|
||||
const applyStructureSyncButton = document.querySelector("[data-apply-structure-sync]");
|
||||
const structurePreviewStatus = document.querySelector("[data-structure-preview-status]");
|
||||
const structurePreviewResults = document.querySelector("[data-structure-preview-results]");
|
||||
const refreshPlotDirectorSceneButton = document.querySelector("[data-refresh-plotdirector-scene]");
|
||||
@ -87,6 +88,10 @@
|
||||
const createChapterDialogBook = document.querySelector("[data-create-chapter-dialog-book]");
|
||||
const createChapterDialogConfirm = document.querySelector("[data-create-chapter-dialog-confirm]");
|
||||
const createChapterDialogCancel = document.querySelector("[data-create-chapter-dialog-cancel]");
|
||||
const applyStructureSyncDialog = document.querySelector("[data-apply-structure-sync-dialog]");
|
||||
const applyStructureSyncSummary = document.querySelector("[data-apply-structure-sync-summary]");
|
||||
const applyStructureSyncConfirm = document.querySelector("[data-apply-structure-sync-confirm]");
|
||||
const applyStructureSyncCancel = document.querySelector("[data-apply-structure-sync-cancel]");
|
||||
const resolveProjectId = document.querySelector("[data-resolve-project-id]");
|
||||
const resolveProjectTitle = document.querySelector("[data-resolve-project-title]");
|
||||
const resolveBookId = document.querySelector("[data-resolve-book-id]");
|
||||
@ -137,6 +142,8 @@
|
||||
let selectedLinkChapterId = null;
|
||||
let pendingCreateChapterConfirmation = null;
|
||||
let structurePreviewModel = null;
|
||||
let isApplyingStructureSync = false;
|
||||
let pendingApplyStructureSyncConfirmation = null;
|
||||
let currentSceneLinks = {
|
||||
characters: [],
|
||||
assets: [],
|
||||
@ -257,10 +264,35 @@
|
||||
setText(structurePreviewStatus, message);
|
||||
};
|
||||
|
||||
const resetStructurePreview = (message = "Select a project and book to analyse manuscript structure.") => {
|
||||
structurePreviewModel = null;
|
||||
if (structurePreviewResults) {
|
||||
structurePreviewResults.innerHTML = "";
|
||||
const empty = document.createElement("p");
|
||||
empty.textContent = "No preview generated.";
|
||||
structurePreviewResults.append(empty);
|
||||
}
|
||||
setStructurePreviewStatus(message);
|
||||
updateApplyStructureSyncButton();
|
||||
};
|
||||
|
||||
const previewItems = (model = structurePreviewModel) => previewGroups.flatMap((group) =>
|
||||
Array.isArray(model?.[group.key]) ? model[group.key] : []);
|
||||
|
||||
const actionablePreviewItems = (model = structurePreviewModel) => previewItems(model)
|
||||
.filter((item) => item.category === "couldLink" || item.category === "wouldCreateChapters" || item.category === "wouldCreateScenes");
|
||||
|
||||
const updateApplyStructureSyncButton = () => {
|
||||
if (applyStructureSyncButton) {
|
||||
applyStructureSyncButton.disabled = isApplyingStructureSync || actionablePreviewItems().length === 0;
|
||||
}
|
||||
};
|
||||
|
||||
const setAnalyseButtonEnabled = () => {
|
||||
if (analyseManuscriptStructureButton) {
|
||||
analyseManuscriptStructureButton.disabled = !selectedProject() || !selectedBook();
|
||||
analyseManuscriptStructureButton.disabled = isApplyingStructureSync || !selectedProject() || !selectedBook();
|
||||
}
|
||||
updateApplyStructureSyncButton();
|
||||
};
|
||||
|
||||
const setResolvedScene = (sceneId, chapterId = null, resolutionMethod = "") => {
|
||||
@ -978,7 +1010,21 @@
|
||||
|
||||
const titleKey = (title, type) => normalizeTitleForResolve(title, type).toLocaleLowerCase();
|
||||
|
||||
const createPreviewItem = ({ category, action, type, wordTitle, match = "", anchorStatus = "None", matches = [], pdChapter = null }) => ({
|
||||
const createPreviewItem = ({
|
||||
category,
|
||||
action,
|
||||
type,
|
||||
wordTitle,
|
||||
match = "",
|
||||
anchorStatus = "None",
|
||||
matches = [],
|
||||
pdChapter = null,
|
||||
pdScene = null,
|
||||
wordChapter = null,
|
||||
wordScene = null,
|
||||
parentItem = null
|
||||
}) => ({
|
||||
id: `${type}-${wordChapter?.index || 0}-${wordScene?.index || 0}-${wordChapter?.paragraphIndex ?? wordScene?.paragraphIndex ?? 0}`,
|
||||
category,
|
||||
action,
|
||||
type,
|
||||
@ -986,7 +1032,13 @@
|
||||
match,
|
||||
anchorStatus,
|
||||
matches,
|
||||
pdChapter
|
||||
pdChapter,
|
||||
pdScene,
|
||||
wordChapter,
|
||||
wordScene,
|
||||
parentItem,
|
||||
result: "Pending",
|
||||
error: ""
|
||||
});
|
||||
|
||||
const addPreviewItem = (model, item) => {
|
||||
@ -1020,7 +1072,8 @@
|
||||
wordTitle: wordChapter.title,
|
||||
match: `Chapter ${anchoredChapter.chapterId}: ${anchoredChapter.title}`,
|
||||
anchorStatus,
|
||||
pdChapter: anchoredChapter
|
||||
pdChapter: anchoredChapter,
|
||||
wordChapter
|
||||
});
|
||||
addPreviewItem(model, item);
|
||||
return item;
|
||||
@ -1033,7 +1086,8 @@
|
||||
wordTitle: wordChapter.title,
|
||||
anchorStatus: `${anchorStatus} not found`,
|
||||
matches: [],
|
||||
pdChapter: null
|
||||
pdChapter: null,
|
||||
wordChapter
|
||||
});
|
||||
addPreviewItem(model, item);
|
||||
return item;
|
||||
@ -1048,7 +1102,8 @@
|
||||
wordTitle: wordChapter.title,
|
||||
match: `Chapter ${matches[0].chapterId}: ${matches[0].title}`,
|
||||
anchorStatus,
|
||||
pdChapter: matches[0]
|
||||
pdChapter: matches[0],
|
||||
wordChapter
|
||||
});
|
||||
addPreviewItem(model, item);
|
||||
return item;
|
||||
@ -1062,7 +1117,8 @@
|
||||
wordTitle: wordChapter.title,
|
||||
anchorStatus,
|
||||
matches: matches.map((chapter) => `Chapter ${chapter.chapterId}: ${chapter.title}`),
|
||||
pdChapter: null
|
||||
pdChapter: null,
|
||||
wordChapter
|
||||
});
|
||||
addPreviewItem(model, item);
|
||||
return item;
|
||||
@ -1074,7 +1130,8 @@
|
||||
type: "Chapter",
|
||||
wordTitle: wordChapter.title,
|
||||
anchorStatus,
|
||||
pdChapter: null
|
||||
pdChapter: null,
|
||||
wordChapter
|
||||
});
|
||||
addPreviewItem(model, item);
|
||||
return item;
|
||||
@ -1091,7 +1148,12 @@
|
||||
type: "Scene",
|
||||
wordTitle: wordScene.title,
|
||||
match: `Scene ${anchored.scene.sceneId}: ${anchored.scene.title}`,
|
||||
anchorStatus
|
||||
anchorStatus,
|
||||
pdChapter: anchored.chapter,
|
||||
pdScene: anchored.scene,
|
||||
wordChapter: chapterPreview?.wordChapter,
|
||||
wordScene,
|
||||
parentItem: chapterPreview
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@ -1101,7 +1163,10 @@
|
||||
action: "manualReview",
|
||||
type: "Scene",
|
||||
wordTitle: wordScene.title,
|
||||
anchorStatus: `${anchorStatus} not found`
|
||||
anchorStatus: `${anchorStatus} not found`,
|
||||
wordChapter: chapterPreview?.wordChapter,
|
||||
wordScene,
|
||||
parentItem: chapterPreview
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@ -1113,7 +1178,10 @@
|
||||
type: "Scene",
|
||||
wordTitle: wordScene.title,
|
||||
anchorStatus,
|
||||
matches: ["Parent chapter requires manual review."]
|
||||
matches: ["Parent chapter requires manual review."],
|
||||
wordChapter: chapterPreview?.wordChapter,
|
||||
wordScene,
|
||||
parentItem: chapterPreview
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@ -1124,7 +1192,10 @@
|
||||
action: "wouldCreateScene",
|
||||
type: "Scene",
|
||||
wordTitle: wordScene.title,
|
||||
anchorStatus
|
||||
anchorStatus,
|
||||
wordChapter: chapterPreview?.wordChapter,
|
||||
wordScene,
|
||||
parentItem: chapterPreview
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@ -1138,7 +1209,12 @@
|
||||
type: "Scene",
|
||||
wordTitle: wordScene.title,
|
||||
match: `Scene ${matches[0].sceneId}: ${matches[0].title}`,
|
||||
anchorStatus
|
||||
anchorStatus,
|
||||
pdChapter: chapterPreview.pdChapter,
|
||||
pdScene: matches[0],
|
||||
wordChapter: chapterPreview.wordChapter,
|
||||
wordScene,
|
||||
parentItem: chapterPreview
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@ -1150,7 +1226,10 @@
|
||||
type: "Scene",
|
||||
wordTitle: wordScene.title,
|
||||
anchorStatus,
|
||||
matches: matches.map((scene) => `Scene ${scene.sceneId}: ${scene.title}`)
|
||||
matches: matches.map((scene) => `Scene ${scene.sceneId}: ${scene.title}`),
|
||||
wordChapter: chapterPreview.wordChapter,
|
||||
wordScene,
|
||||
parentItem: chapterPreview
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@ -1160,7 +1239,11 @@
|
||||
action: "wouldCreateScene",
|
||||
type: "Scene",
|
||||
wordTitle: wordScene.title,
|
||||
anchorStatus
|
||||
anchorStatus,
|
||||
pdChapter: chapterPreview.pdChapter,
|
||||
wordChapter: chapterPreview.wordChapter,
|
||||
wordScene,
|
||||
parentItem: chapterPreview
|
||||
}));
|
||||
};
|
||||
|
||||
@ -1221,6 +1304,12 @@
|
||||
const action = document.createElement("span");
|
||||
action.textContent = `Action: ${previewActionLabels[item.action] || item.action}`;
|
||||
row.append(action);
|
||||
|
||||
if (item.result && item.result !== "Pending") {
|
||||
const result = document.createElement("span");
|
||||
result.textContent = item.error ? `Result: ${item.result} - ${item.error}` : `Result: ${item.result}`;
|
||||
row.append(result);
|
||||
}
|
||||
return row;
|
||||
};
|
||||
|
||||
@ -1252,6 +1341,7 @@
|
||||
|
||||
structurePreviewResults.append(section);
|
||||
}
|
||||
updateApplyStructureSyncButton();
|
||||
};
|
||||
|
||||
const readDocumentStructure = async (context) => {
|
||||
@ -1387,6 +1477,259 @@
|
||||
}
|
||||
};
|
||||
|
||||
const applyCounts = (model = structurePreviewModel) => {
|
||||
const actionable = actionablePreviewItems(model);
|
||||
const linkChapters = actionable.filter((item) => item.category === "couldLink" && item.type === "Chapter").length;
|
||||
const linkScenes = actionable.filter((item) => item.category === "couldLink" && item.type === "Scene").length;
|
||||
const createChapters = actionable.filter((item) => item.category === "wouldCreateChapters").length;
|
||||
const createScenes = actionable.filter((item) => item.category === "wouldCreateScenes").length;
|
||||
const manualReview = Array.isArray(model?.manualReview) ? model.manualReview.length : 0;
|
||||
|
||||
return { linkChapters, linkScenes, createChapters, createScenes, manualReview };
|
||||
};
|
||||
|
||||
const renderApplySummary = (counts) => {
|
||||
if (!applyStructureSyncSummary) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyStructureSyncSummary.innerHTML = "";
|
||||
const lines = [
|
||||
`Link ${counts.linkChapters + counts.linkScenes} chapters/scenes`,
|
||||
`Create ${counts.createChapters} chapters`,
|
||||
`Create ${counts.createScenes} scenes`
|
||||
];
|
||||
const list = document.createElement("ul");
|
||||
for (const line of lines) {
|
||||
const item = document.createElement("li");
|
||||
item.textContent = line;
|
||||
list.append(item);
|
||||
}
|
||||
applyStructureSyncSummary.append(list);
|
||||
};
|
||||
|
||||
const closeApplyStructureSyncDialog = (confirmed) => {
|
||||
if (pendingApplyStructureSyncConfirmation) {
|
||||
pendingApplyStructureSyncConfirmation(confirmed);
|
||||
pendingApplyStructureSyncConfirmation = null;
|
||||
}
|
||||
|
||||
if (applyStructureSyncDialog?.close) {
|
||||
applyStructureSyncDialog.close();
|
||||
} else if (applyStructureSyncDialog) {
|
||||
applyStructureSyncDialog.hidden = true;
|
||||
}
|
||||
};
|
||||
|
||||
const confirmApplyStructureSync = () => {
|
||||
const counts = applyCounts();
|
||||
renderApplySummary(counts);
|
||||
|
||||
if (!applyStructureSyncDialog) {
|
||||
return Promise.resolve(window.confirm(
|
||||
`Apply Structure Sync?\n\nThis will:\n\n- Link ${counts.linkChapters + counts.linkScenes} chapters/scenes\n- Create ${counts.createChapters} chapters\n- Create ${counts.createScenes} scenes\n\nManual review items will be skipped.`
|
||||
));
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
pendingApplyStructureSyncConfirmation = resolve;
|
||||
if (applyStructureSyncDialog.showModal) {
|
||||
applyStructureSyncDialog.showModal();
|
||||
} else {
|
||||
applyStructureSyncDialog.hidden = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const markPreviewItem = (item, result, error = "") => {
|
||||
item.result = result;
|
||||
item.error = error;
|
||||
};
|
||||
|
||||
const previewChapterSortOrder = (item) => Number.isInteger(item?.wordChapter?.index) && item.wordChapter.index > 0
|
||||
? item.wordChapter.index * 10
|
||||
: null;
|
||||
|
||||
const previewSceneSortOrder = (item) => Number.isInteger(item?.wordScene?.index) && item.wordScene.index > 0
|
||||
? item.wordScene.index * 10
|
||||
: null;
|
||||
|
||||
const createChapterFromPreviewItem = async (bookId, item) => {
|
||||
const response = await postJson(`/api/word-companion/books/${bookId}/chapters`, {
|
||||
title: normalizeWhitespace(item.wordTitle),
|
||||
sortOrder: previewChapterSortOrder(item)
|
||||
});
|
||||
|
||||
if (!response?.chapterId) {
|
||||
throw new Error("Chapter creation did not return a chapter ID.");
|
||||
}
|
||||
|
||||
item.pdChapter = {
|
||||
chapterId: response.chapterId,
|
||||
title: response.title || item.wordTitle,
|
||||
sortOrder: response.sortOrder || previewChapterSortOrder(item) || 0,
|
||||
scenes: []
|
||||
};
|
||||
item.match = `Chapter ${item.pdChapter.chapterId}: ${item.pdChapter.title}`;
|
||||
};
|
||||
|
||||
const createSceneFromPreviewItem = async (bookId, item) => {
|
||||
const chapter = item.pdChapter || item.parentItem?.pdChapter;
|
||||
if (!chapter?.chapterId) {
|
||||
throw new Error("Scene parent chapter is not available.");
|
||||
}
|
||||
|
||||
const response = await postJson(`/api/word-companion/books/${bookId}/chapters/${chapter.chapterId}/scenes`, {
|
||||
sceneTitle: normalizeWhitespace(item.wordTitle),
|
||||
sortOrder: previewSceneSortOrder(item)
|
||||
});
|
||||
|
||||
if (!response?.sceneId) {
|
||||
throw new Error("Scene creation did not return a scene ID.");
|
||||
}
|
||||
|
||||
item.pdChapter = chapter;
|
||||
item.pdScene = {
|
||||
sceneId: response.sceneId,
|
||||
title: item.wordTitle,
|
||||
sortOrder: previewSceneSortOrder(item) || 0
|
||||
};
|
||||
item.match = `Scene ${item.pdScene.sceneId}: ${item.pdScene.title}`;
|
||||
};
|
||||
|
||||
const attachAnchorForPreviewItem = async (item) => {
|
||||
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
|
||||
throw new Error("Word document access is unavailable.");
|
||||
}
|
||||
|
||||
await window.Word.run(async (context) => {
|
||||
const structure = await readDocumentStructure(context);
|
||||
const source = item.type === "Chapter" ? item.wordChapter : item.wordScene;
|
||||
const paragraph = Number.isInteger(source?.paragraphIndex)
|
||||
? structure.bodyParagraphs[source.paragraphIndex]
|
||||
: null;
|
||||
if (!paragraph) {
|
||||
throw new Error("Unable to locate the Word heading.");
|
||||
}
|
||||
|
||||
if (item.type === "Chapter") {
|
||||
const chapterId = item.pdChapter?.chapterId;
|
||||
if (!chapterId) {
|
||||
throw new Error("Chapter ID is not available.");
|
||||
}
|
||||
ensureAnchorControl(paragraph, "PlotDirector Chapter", `PD-CHAPTER-${chapterId}`, "PD-CHAPTER");
|
||||
} else {
|
||||
const sceneId = item.pdScene?.sceneId;
|
||||
if (!sceneId) {
|
||||
throw new Error("Scene ID is not available.");
|
||||
}
|
||||
ensureAnchorControl(paragraph, "PlotDirector Scene", `PD-SCENE-${sceneId}`, "PD-SCENE");
|
||||
}
|
||||
|
||||
await context.sync();
|
||||
});
|
||||
};
|
||||
|
||||
const resultSummaryText = (summary) =>
|
||||
`Structure Sync Complete. Created: ${summary.createdChapters} Chapters, ${summary.createdScenes} Scenes. Linked: ${summary.linkedChapters} Chapters, ${summary.linkedScenes} Scenes. Skipped: ${summary.skipped} Manual Review items. Failed: ${summary.failed} Items.`;
|
||||
|
||||
const applyStructureSync = async () => {
|
||||
const book = selectedBook();
|
||||
if (!book || actionablePreviewItems().length === 0 || isApplyingStructureSync) {
|
||||
updateApplyStructureSyncButton();
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await confirmApplyStructureSync();
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
isApplyingStructureSync = true;
|
||||
setAnalyseButtonEnabled();
|
||||
setStructurePreviewStatus("Applying structure sync...");
|
||||
|
||||
const summary = {
|
||||
createdChapters: 0,
|
||||
createdScenes: 0,
|
||||
linkedChapters: 0,
|
||||
linkedScenes: 0,
|
||||
skipped: Array.isArray(structurePreviewModel?.manualReview) ? structurePreviewModel.manualReview.length : 0,
|
||||
failed: 0
|
||||
};
|
||||
|
||||
const linkChapters = actionablePreviewItems().filter((item) => item.category === "couldLink" && item.type === "Chapter");
|
||||
const createChapters = actionablePreviewItems().filter((item) => item.category === "wouldCreateChapters");
|
||||
const linkScenes = actionablePreviewItems().filter((item) => item.category === "couldLink" && item.type === "Scene");
|
||||
const createScenes = actionablePreviewItems().filter((item) => item.category === "wouldCreateScenes");
|
||||
const anchorQueue = [];
|
||||
let finalStatus = "";
|
||||
|
||||
const queueAnchor = (item, counterName) => {
|
||||
anchorQueue.push({ item, counterName });
|
||||
};
|
||||
|
||||
const attachQueuedItem = async (item, counterName) => {
|
||||
try {
|
||||
await attachAnchorForPreviewItem(item);
|
||||
markPreviewItem(item, "Success");
|
||||
summary[counterName] += 1;
|
||||
} catch (error) {
|
||||
markPreviewItem(item, "Failed", errorText(error));
|
||||
summary.failed += 1;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
for (const item of linkChapters) {
|
||||
queueAnchor(item, "linkedChapters");
|
||||
}
|
||||
|
||||
for (const item of createChapters) {
|
||||
try {
|
||||
await createChapterFromPreviewItem(book.bookId, item);
|
||||
queueAnchor(item, "createdChapters");
|
||||
} catch (error) {
|
||||
markPreviewItem(item, "Failed", errorText(error));
|
||||
summary.failed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of linkScenes) {
|
||||
queueAnchor(item, "linkedScenes");
|
||||
}
|
||||
|
||||
for (const item of createScenes) {
|
||||
try {
|
||||
await createSceneFromPreviewItem(book.bookId, item);
|
||||
queueAnchor(item, "createdScenes");
|
||||
} catch (error) {
|
||||
markPreviewItem(item, "Failed", errorText(error));
|
||||
summary.failed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (const queued of anchorQueue) {
|
||||
await attachQueuedItem(queued.item, queued.counterName);
|
||||
}
|
||||
|
||||
for (const item of structurePreviewModel.manualReview || []) {
|
||||
markPreviewItem(item, "Skipped");
|
||||
}
|
||||
|
||||
renderStructurePreview(structurePreviewModel);
|
||||
finalStatus = resultSummaryText(summary);
|
||||
setStructurePreviewStatus(finalStatus);
|
||||
} finally {
|
||||
isApplyingStructureSync = false;
|
||||
setAnalyseButtonEnabled();
|
||||
if (finalStatus) {
|
||||
await analyseManuscriptStructure();
|
||||
setStructurePreviewStatus(`${finalStatus} Preview refreshed.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const runDiagnostics = async () => {
|
||||
if (runDiagnosticsButton) {
|
||||
runDiagnosticsButton.disabled = true;
|
||||
@ -2429,12 +2772,14 @@
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
updateSummary();
|
||||
resetPlotDirectorScene("Not detected");
|
||||
resetStructurePreview();
|
||||
return;
|
||||
}
|
||||
|
||||
resetSelect(bookSelect, "Loading books...");
|
||||
setBookMessage("");
|
||||
resetPlotDirectorScene("Not detected");
|
||||
resetStructurePreview("Select a book to analyse manuscript structure.");
|
||||
|
||||
try {
|
||||
const data = await fetchJson(`/api/word-companion/projects/${projectId}/books`);
|
||||
@ -2458,6 +2803,9 @@
|
||||
}
|
||||
updateSummary();
|
||||
resetPlotDirectorScene(selectedBook() ? "Not detected" : "Not detected");
|
||||
resetStructurePreview(selectedBook()
|
||||
? "Ready to analyse manuscript structure."
|
||||
: "Select a book to analyse manuscript structure.");
|
||||
} catch {
|
||||
books = [];
|
||||
resetSelect(bookSelect, "Unable to load books.");
|
||||
@ -2465,6 +2813,7 @@
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
updateSummary();
|
||||
resetPlotDirectorScene("Not detected");
|
||||
resetStructurePreview("Unable to load books.");
|
||||
}
|
||||
};
|
||||
|
||||
@ -2517,6 +2866,7 @@
|
||||
writeStoredId(storageKeys.projectId, Number.isInteger(projectId) && projectId > 0 ? projectId : null);
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
resetPlotDirectorScene("Not detected");
|
||||
resetStructurePreview();
|
||||
await loadBooks(projectId, null);
|
||||
});
|
||||
|
||||
@ -2525,10 +2875,20 @@
|
||||
writeStoredId(storageKeys.bookId, Number.isInteger(bookId) && bookId > 0 ? bookId : null);
|
||||
updateSummary();
|
||||
resetPlotDirectorScene("Not detected");
|
||||
resetStructurePreview(selectedBook()
|
||||
? "Ready to analyse manuscript structure."
|
||||
: "Select a book to analyse manuscript structure.");
|
||||
});
|
||||
|
||||
refreshDocumentButton?.addEventListener("click", refreshDocumentStructure);
|
||||
analyseManuscriptStructureButton?.addEventListener("click", analyseManuscriptStructure);
|
||||
applyStructureSyncButton?.addEventListener("click", applyStructureSync);
|
||||
applyStructureSyncConfirm?.addEventListener("click", () => closeApplyStructureSyncDialog(true));
|
||||
applyStructureSyncCancel?.addEventListener("click", () => closeApplyStructureSyncDialog(false));
|
||||
applyStructureSyncDialog?.addEventListener("cancel", (event) => {
|
||||
event.preventDefault();
|
||||
closeApplyStructureSyncDialog(false);
|
||||
});
|
||||
refreshPlotDirectorSceneButton?.addEventListener("click", refreshPlotDirectorScene);
|
||||
refreshCurrentSceneButton?.addEventListener("click", refreshCurrentScene);
|
||||
syncSceneProgressButton?.addEventListener("click", syncSceneProgress);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user