PPT 생성 고도화 3차를 반영하고 deck planning·quality gate를 추가

- DeckPlanningService와 DeckQualityReviewService를 추가해 deck brief 정규화, consulting storyline 보강, 누락된 Executive Summary/Recommendation/Roadmap/Appendix 자동 보강, deck-level 품질 점수와 경고 계산을 지원합니다.

- PptxSkill에 audience/objective/decision_ask/storyline 파라미터를 추가하고, issue_tree/before_after/decision_matrix/risk_heatmap/benefit_waterfall/operating_model/appendix_evidence 레이아웃을 네이티브 슬라이드 타입으로 정규화한 뒤 planning summary와 quality summary를 함께 반환하도록 보강했습니다.

- pptx-creator 및 strategy-deck/board-update/pmo-steering/sales-review-deck/operating-model-deck 번들 스킬을 추가·정리하고, DeckPlanningServiceTests/DeckQualityReviewServiceTests/PptxSkillAutoRepairTests로 회귀 검증을 보강했습니다.

- README.md와 docs/DEVELOPMENT.md에 2026-04-14 21:50, 22:00 (KST) 기준 변경 이력과 검증 결과를 반영했습니다.

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_ppt_phase3\ -p:IntermediateOutputPath=obj\verify_ppt_phase3\ (경고 0 / 오류 0)

- 검증: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter 'DeckPlanningServiceTests|DeckQualityReviewServiceTests|PptxSkillAutoRepairTests|PptxSkillConsultingDeckTests' -p:OutputPath=bin\verify_ppt_phase3_tests\ -p:IntermediateOutputPath=obj\verify_ppt_phase3_tests\ (통과 5)
This commit is contained in:
2026-04-14 22:01:41 +09:00
parent 6c7fba9dff
commit 8571a83ed0
14 changed files with 1450 additions and 85 deletions

View File

@@ -1,4 +1,4 @@
using System.IO;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
@@ -155,6 +155,27 @@ public class PptxSkill : IAgentTool
Description = "Slide aspect ratio. Default: widescreen (16:9)",
Enum = ["widescreen", "standard"]
},
["audience"] = new()
{
Type = "string",
Description = "Target audience such as executives, steering committee, PMO, sales leadership, or team leads.",
},
["objective"] = new()
{
Type = "string",
Description = "Presentation objective such as strategy recommendation, board update, PMO steering, operating model proposal, or KPI review.",
},
["decision_ask"] = new()
{
Type = "string",
Description = "The exact decision or approval being requested from the audience.",
},
["storyline"] = new()
{
Type = "array",
Description = "Optional storyline or ordered section hints.",
Items = new() { Type = "string" }
},
},
Required = ["slides"]
};
@@ -506,9 +527,15 @@ public class PptxSkill : IAgentTool
if (safe.Length > 60) safe = safe[..60].TrimEnd();
path = (string.IsNullOrWhiteSpace(safe) ? "presentation" : safe) + ".pptx";
}
var theme = args.SafeTryGetProperty("theme", out var th) ? th.SafeGetString() ?? "basic100" : "basic100";
var aspect = args.SafeTryGetProperty("aspect", out var asp) ? asp.SafeGetString() ?? "widescreen" : "widescreen";
var hasExplicitTheme = args.SafeTryGetProperty("theme", out var th) &&
!string.IsNullOrWhiteSpace(th.SafeGetString());
var theme = hasExplicitTheme ? th.SafeGetString() ?? "basic100" : "basic100";
var aspect = args.SafeTryGetProperty("aspect", out var asp) ? asp.SafeGetString() ?? "widescreen" : "widescreen";
var cloneAll = args.SafeTryGetProperty("clone_slides", out var cloneEl) && cloneEl.ValueKind == JsonValueKind.True;
var hasThemeFile = args.SafeTryGetProperty("theme_file", out var themeFileEl) &&
!string.IsNullOrWhiteSpace(themeFileEl.SafeGetString());
var hasExplicitTemplate = args.SafeTryGetProperty("template", out var templateArgEl) &&
!string.IsNullOrWhiteSpace(templateArgEl.SafeGetString());
// 전체 복제용 텍스트 교체 맵
Dictionary<string, string>? globalReplacements = null;
@@ -526,6 +553,20 @@ public class PptxSkill : IAgentTool
if (!hasSlidesArray && !cloneAll)
return ToolResult.Fail("slides 배열이 필요합니다.");
using var preparedDeck = hasSlidesArray
? DeckPlanningService.Prepare(args, slidesEl, presTitle)
: null;
if (preparedDeck != null)
{
slidesEl = preparedDeck.Slides;
if (!hasExplicitTheme && !hasExplicitTemplate && !hasThemeFile &&
!string.IsNullOrWhiteSpace(preparedDeck.SuggestedTheme))
{
theme = preparedDeck.SuggestedTheme!;
}
}
var fullPath = FileReadTool.ResolvePath(path, context.WorkFolder);
if (context.ActiveTab == "Cowork") fullPath = AgentContext.EnsureTimestampedPath(fullPath);
if (!fullPath.EndsWith(".pptx", StringComparison.OrdinalIgnoreCase))
@@ -923,9 +964,23 @@ public class PptxSkill : IAgentTool
: templatePptxPath != null
? $"theme_file:{cloneInfo_srcLabel} (master cloned{(cloneAll ? " + slides cloned" : "")})"
: theme;
return ToolResult.Ok(
$"✅ PPTX 생성 완료: {fullPath} ({slideCount_final}슬라이드, {themeLabel}, {(isWide ? "16:9" : "4:3")})",
fullPath);
var deckReview = hasSlidesArray
? DeckQualityReviewService.ReviewDeck(
presTitle,
slidesEl,
!string.IsNullOrWhiteSpace(templateName) || templatePptxPath != null,
preparedDeck?.AutoRepairCount ?? 0,
preparedDeck?.Storyline)
: null;
var outputParts = new List<string>
{
$"PPTX created: {fullPath} ({slideCount_final} slides, {themeLabel}, {(isWide ? "16:9" : "4:3")})"
};
if (preparedDeck != null)
outputParts.Add(preparedDeck.ToToolSummary());
if (deckReview != null)
outputParts.Add(deckReview.ToToolSummary());
return ToolResult.Ok(string.Join("\n", outputParts), fullPath);
}
catch (Exception ex)
{