diff --git a/PlotLine/Controllers/ScenesController.cs b/PlotLine/Controllers/ScenesController.cs index 7469411..f3bb399 100644 --- a/PlotLine/Controllers/ScenesController.cs +++ b/PlotLine/Controllers/ScenesController.cs @@ -151,6 +151,15 @@ public sealed class ScenesController( : PartialView("~/Views/Shared/SceneInspectorV2/_SceneCharacterAttributesEditor.cshtml", model); } + [HttpGet] + public async Task SceneSuggestionsEditor(int sceneId) + { + var model = await scenes.GetEditAsync(sceneId); + return model is null + ? NotFound() + : PartialView("~/Views/Shared/SceneInspectorV2/_SceneSuggestionsEditor.cshtml", model); + } + [HttpGet] public async Task SceneLocationsEditor(int sceneId) { @@ -967,9 +976,18 @@ public sealed class ScenesController( try { await characters.AcceptSceneCharacterSuggestionAsync(id); + if (IsAjaxRequest()) + { + return await SceneSuggestionsRefreshJsonAsync(sceneId, refreshPeople: true, refreshWorld: false); + } } catch (InvalidOperationException ex) { + if (IsAjaxRequest()) + { + return BadRequest(new { success = false, message = ex.Message }); + } + TempData["SceneCharacterSuggestionError"] = ex.Message; } @@ -983,9 +1001,18 @@ public sealed class ScenesController( try { await characters.RejectSceneCharacterSuggestionAsync(id); + if (IsAjaxRequest()) + { + return await SceneSuggestionsRefreshJsonAsync(sceneId, refreshPeople: true, refreshWorld: false); + } } catch (InvalidOperationException ex) { + if (IsAjaxRequest()) + { + return BadRequest(new { success = false, message = ex.Message }); + } + TempData["SceneCharacterSuggestionError"] = ex.Message; } @@ -999,9 +1026,18 @@ public sealed class ScenesController( try { await assets.AcceptSceneAssetSuggestionAsync(id); + if (IsAjaxRequest()) + { + return await SceneSuggestionsRefreshJsonAsync(sceneId, refreshPeople: true, refreshWorld: true); + } } catch (InvalidOperationException ex) { + if (IsAjaxRequest()) + { + return BadRequest(new { success = false, message = ex.Message }); + } + TempData["SceneAssetSuggestionError"] = ex.Message; } @@ -1015,9 +1051,18 @@ public sealed class ScenesController( try { await assets.RejectSceneAssetSuggestionAsync(id); + if (IsAjaxRequest()) + { + return await SceneSuggestionsRefreshJsonAsync(sceneId, refreshPeople: true, refreshWorld: true); + } } catch (InvalidOperationException ex) { + if (IsAjaxRequest()) + { + return BadRequest(new { success = false, message = ex.Message }); + } + TempData["SceneAssetSuggestionError"] = ex.Message; } @@ -1031,9 +1076,18 @@ public sealed class ScenesController( try { await locations.AcceptSceneLocationSuggestionAsync(id); + if (IsAjaxRequest()) + { + return await SceneSuggestionsRefreshJsonAsync(sceneId, refreshPeople: true, refreshWorld: true); + } } catch (InvalidOperationException ex) { + if (IsAjaxRequest()) + { + return BadRequest(new { success = false, message = ex.Message }); + } + TempData["SceneLocationSuggestionError"] = ex.Message; } @@ -1047,9 +1101,18 @@ public sealed class ScenesController( try { await locations.RejectSceneLocationSuggestionAsync(id); + if (IsAjaxRequest()) + { + return await SceneSuggestionsRefreshJsonAsync(sceneId, refreshPeople: true, refreshWorld: true); + } } catch (InvalidOperationException ex) { + if (IsAjaxRequest()) + { + return BadRequest(new { success = false, message = ex.Message }); + } + TempData["SceneLocationSuggestionError"] = ex.Message; } @@ -1528,6 +1591,44 @@ public sealed class ScenesController( }); } + private async Task SceneSuggestionsRefreshJsonAsync(int sceneId, bool refreshPeople, bool refreshWorld) + { + var updated = await scenes.GetEditAsync(sceneId); + if (updated is null) + { + return NotFound(); + } + + var html = new Dictionary + { + ["health"] = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneInspectorHealthStrip.cshtml", updated) + }; + + if (refreshPeople) + { + html["people"] = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_ScenePeopleCard.cshtml", updated); + } + + if (refreshWorld) + { + html["world"] = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneWorldCard.cshtml", updated); + } + + var primaryLocationName = updated.LocationOptions.FirstOrDefault(x => x.Value == updated.PrimaryLocationID?.ToString())?.Text?.Trim(); + + return Json(new + { + success = true, + html, + editorHtml = await RenderPartialViewToStringAsync("~/Views/Shared/SceneInspectorV2/_SceneSuggestionsEditor.cshtml", updated), + scene = new + { + updated.SceneID, + PrimaryLocationName = primaryLocationName ?? "No location" + } + }); + } + private async Task ScenePeopleContinuityRefreshJsonAsync(int sceneId) { var updated = await scenes.GetEditAsync(sceneId); diff --git a/PlotLine/Views/Shared/SceneInspectorV2/_ScenePeopleCard.cshtml b/PlotLine/Views/Shared/SceneInspectorV2/_ScenePeopleCard.cshtml index cf764ba..b095c83 100644 --- a/PlotLine/Views/Shared/SceneInspectorV2/_ScenePeopleCard.cshtml +++ b/PlotLine/Views/Shared/SceneInspectorV2/_ScenePeopleCard.cshtml @@ -2,6 +2,7 @@ @{ var povCharacterId = Model.POVCharacterID ?? Model.SceneCharacters.FirstOrDefault(x => x.RoleInSceneTypeName?.Contains("POV", StringComparison.OrdinalIgnoreCase) == true)?.CharacterID; + var suggestionCount = Model.CharacterSuggestions.Count + Model.AssetSuggestions.Count + Model.LocationSuggestions.Count; }
@@ -53,11 +54,16 @@ Attribute changes @Model.CharacterAttributeEvents.Count +
+ Suggestions + @suggestionCount +
+
diff --git a/PlotLine/Views/Shared/SceneInspectorV2/_SceneSuggestionsEditor.cshtml b/PlotLine/Views/Shared/SceneInspectorV2/_SceneSuggestionsEditor.cshtml new file mode 100644 index 0000000..578c72a --- /dev/null +++ b/PlotLine/Views/Shared/SceneInspectorV2/_SceneSuggestionsEditor.cshtml @@ -0,0 +1,192 @@ +@model SceneEditViewModel +@{ + var totalSuggestions = Model.CharacterSuggestions.Count + Model.AssetSuggestions.Count + Model.LocationSuggestions.Count; +} + +
+
+
+
+ Word Companion suggestions + @totalSuggestions +
+ + @if (totalSuggestions == 0) + { +

No pending Word Companion suggestions for this scene.

+ } +
+ +
+
+ Character suggestions + @Model.CharacterSuggestions.Count +
+ + @if (!Model.CharacterSuggestions.Any()) + { +

No pending character suggestions.

+ } + else + { +
+ @foreach (var suggestion in Model.CharacterSuggestions) + { +
+
+
+

@suggestion.CharacterName

+

@suggestion.Confidence confidence

+
+ Character +
+ + @if (!string.IsNullOrWhiteSpace(suggestion.Reason)) + { +

@suggestion.Reason

+ } +

Detected @suggestion.DetectedUtc.ToLocalTime().ToString("d MMM yyyy HH:mm")

+ +
+
+ @Html.AntiForgeryToken() + + + + + + +
+
+ @Html.AntiForgeryToken() + + + + + + +
+
+
+ } +
+ } +
+ +
+
+ Asset suggestions + @Model.AssetSuggestions.Count +
+ + @if (!Model.AssetSuggestions.Any()) + { +

No pending asset suggestions.

+ } + else + { +
+ @foreach (var suggestion in Model.AssetSuggestions) + { +
+
+
+

@suggestion.AssetName

+

@suggestion.Confidence confidence

+
+ Asset +
+ + @if (!string.IsNullOrWhiteSpace(suggestion.Reason)) + { +

@suggestion.Reason

+ } +

Detected @suggestion.DetectedUtc.ToLocalTime().ToString("d MMM yyyy HH:mm")

+ +
+
+ @Html.AntiForgeryToken() + + + + + + +
+
+ @Html.AntiForgeryToken() + + + + + + +
+
+
+ } +
+ } +
+ +
+
+ Location suggestions + @Model.LocationSuggestions.Count +
+ + @if (!Model.LocationSuggestions.Any()) + { +

No pending location suggestions.

+ } + else + { +
+ @foreach (var suggestion in Model.LocationSuggestions) + { +
+
+
+

@(!string.IsNullOrWhiteSpace(suggestion.LocationPath) ? suggestion.LocationPath : suggestion.LocationName)

+

@suggestion.Confidence confidence

+
+ Location +
+ + @if (!string.IsNullOrWhiteSpace(suggestion.Reason)) + { +

@suggestion.Reason

+ } +

Detected @suggestion.DetectedUtc.ToLocalTime().ToString("d MMM yyyy HH:mm")

+ +
+
+ @Html.AntiForgeryToken() + + + + + + +
+
+ @Html.AntiForgeryToken() + + + + + + +
+
+
+ } +
+ } +
+
+ + +
diff --git a/PlotLine/wwwroot/js/scene-inspector-v2.js b/PlotLine/wwwroot/js/scene-inspector-v2.js index 7ce4941..c915fbf 100644 --- a/PlotLine/wwwroot/js/scene-inspector-v2.js +++ b/PlotLine/wwwroot/js/scene-inspector-v2.js @@ -7,6 +7,7 @@ dependencies: "Manage dependencies", characters: "Manage characters", attributes: "Manage attributes", + suggestions: "Manage suggestions", knowledge: "Manage knowledge", relationships: "Manage relationships", locations: "Manage locations",