코워크 PPT 생성 경로를 공통 품질 루프로 고도화하고 자동 보정 단계를 추가
- Cowork 프롬프트와 direct-creation 탐색 정책에서 PPT 요청은 document_plan을 무조건 선행하지 않고 pptx_create를 우선하도록 조정했습니다. - DeckPlanningService에 RefineForQuality 루프를 추가해 executive summary, comparison, roadmap, chart, KPI dashboard 슬라이드의 headline, takeaway, verdict, timeline/owner, KPI narrative를 자동 보강하도록 했습니다. - PptxSkill이 초기 deck review가 약할 때 한 번 더 refined deck을 만들고 실제로 점수와 경고가 개선된 경우에만 최종 렌더링에 반영하도록 수정했습니다. - DeckPlanningServiceTests와 PptxSkillAutoRepairTests를 확장해 generic PPT 품질 보정 회귀를 고정했습니다. - 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_ppt_generic_quality\\ -p:IntermediateOutputPath=obj\\verify_ppt_generic_quality\\ (경고 0 / 오류 0) - 검증: deck planning, pptx auto repair, golden deck, deck quality review 테스트 통과 14
This commit is contained in:
@@ -7,6 +7,14 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
|
||||
개발 참고: Claw Code 동등성 작업 추적 문서
|
||||
`docs/claw-code-parity-plan.md`
|
||||
|
||||
- 업데이트: 2026-04-15 15:45 (KST)
|
||||
- Cowork의 PPT 생성 경로를 더 일반적인 고품질 흐름으로 보강했습니다. [ChatWindow.SystemPromptBuilder.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.SystemPromptBuilder.cs)와 [AgentLoopExplorationPolicy.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopExplorationPolicy.cs)는 이제 프레젠테이션/슬라이드 덱 요청에서 `document_plan`을 무조건 먼저 타지 않고, 명시적으로 계획을 요구한 경우가 아니면 `pptx_create`를 우선하도록 안내합니다.
|
||||
- [DeckPlanningService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/DeckPlanningService.cs)에 generic `RefineForQuality(...)` 루프를 추가했습니다. executive summary, comparison, roadmap, chart, KPI dashboard 같은 특화 슬라이드에서 headline, takeaway, verdict, owner/timeline, KPI trend/note를 자동 보강하고, 필요하면 appendix/evidence 슬라이드까지 추가합니다.
|
||||
- [PptxSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PptxSkill.cs)는 초기 deck review 결과가 약할 때 한 번 더 자동 보정을 수행한 뒤 점수와 이슈가 실제로 개선된 경우에만 refined deck을 최종 렌더링에 사용합니다. 특정 업종 전용이 아니라 어떤 PPT 요청에도 적용되는 공통 품질 루프입니다.
|
||||
- 테스트: [DeckPlanningServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DeckPlanningServiceTests.cs), [PptxSkillAutoRepairTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillAutoRepairTests.cs)
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_ppt_generic_quality\\ -p:IntermediateOutputPath=obj\\verify_ppt_generic_quality\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "DeckPlanningServiceTests|PptxSkillAutoRepairTests|PptxSkillGoldenDeckTests|DeckQualityReviewServiceTests" -p:OutputPath=bin\\verify_ppt_generic_quality_tests\\ -p:IntermediateOutputPath=obj\\verify_ppt_generic_quality_tests\\` 통과 14
|
||||
|
||||
- 업데이트: 2026-04-15 15:18 (KST)
|
||||
- 코워크/코드 내부설정의 최대 컨텍스트 토큰 기본값을 32K로 올렸습니다. [AppSettings.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Models/AppSettings.cs)의 `MaxContextTokens` 기본값을 `32_768`로 변경해 신규 설정/신규 세션이 바로 32K 기준으로 시작됩니다.
|
||||
- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml)과 [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs)에는 `32K` 선택 카드를 추가해 메인 설정에서도 기본 선택과 표시가 어긋나지 않게 맞췄습니다.
|
||||
|
||||
@@ -49,6 +49,13 @@
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentStatusNarrativeCatalogTests|AgentLoopIterationPreparationServiceTests|AgentToolResultBudgetTests|ChatStorageServiceTests|AgentMessageInvariantHelperTests" -p:OutputPath=bin\\verify_status_narrative_tests\\ -p:IntermediateOutputPath=obj\\verify_status_narrative_tests\\` 통과 15
|
||||
|
||||
업데이트: 2026-04-14 19:50 (KST)
|
||||
업데이트: 2026-04-15 15:45 (KST)
|
||||
- Cowork PPT 생성 경로를 특정 업종 전용 archetype이 아니라 공통 품질 루프로 강화했습니다. `src/AxCopilot/Views/ChatWindow.SystemPromptBuilder.cs`와 `src/AxCopilot/Services/Agent/AgentLoopExplorationPolicy.cs`는 presentation/deck 요청에서 `document_plan`을 무조건 선행하지 않고, 계획 요청이 명시되지 않으면 `pptx_create`를 우선하도록 안내합니다.
|
||||
- `src/AxCopilot/Services/Agent/DeckPlanningService.cs`에 `RefineForQuality(...)`를 추가했습니다. 이 루프는 executive summary, recommendation, comparison, roadmap, chart, KPI dashboard 슬라이드를 다시 점검해 summary takeaways, verdict/trade-off, timeline/owner/detail, KPI trend/note, chart takeaway, appendix evidence를 자동으로 보강합니다.
|
||||
- `src/AxCopilot/Services/Agent/PptxSkill.cs`는 초기 `DeckQualityReviewService.ReviewDeck(...)` 결과가 약할 때 한 번 더 보정한 deck을 만들고, 실제로 점수/경고가 개선된 경우에만 refined deck을 최종 렌더링에 사용합니다. 결과적으로 weak deck은 한 번 더 자동으로 다듬어진 뒤 export됩니다.
|
||||
- 테스트: `src/AxCopilot.Tests/Services/DeckPlanningServiceTests.cs`, `src/AxCopilot.Tests/Services/PptxSkillAutoRepairTests.cs`
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_ppt_generic_quality\\ -p:IntermediateOutputPath=obj\\verify_ppt_generic_quality\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "DeckPlanningServiceTests|PptxSkillAutoRepairTests|PptxSkillGoldenDeckTests|DeckQualityReviewServiceTests" -p:OutputPath=bin\\verify_ppt_generic_quality_tests\\ -p:IntermediateOutputPath=obj\\verify_ppt_generic_quality_tests\\` 통과 14
|
||||
- Agent loop/queue/context 품질을 보강했습니다. `src/AxCopilot/Services/Agent/AgentCommandQueue.cs`로 실행 중 추가 입력을 우선순위와 interrupt 여부까지 포함해 관리하고, `AgentLoopService`는 이를 안전하게 반영합니다.
|
||||
- `AgentToolResultBudget`, `AgentQueryContextBuilder`, `ChatModels`는 tool result preview를 메시지에 캐시해 긴 세션과 재질문에서도 같은 축약 결과를 재사용하도록 정리했습니다.
|
||||
- 코드 탭의 내장 언어 지원을 `src/AxCopilot/Services/CodeLanguageCatalog.cs`로 통합했고, 설정의 코드 탭에 지원 언어(LSP)와 코드 탭 기본 지원 언어를 명시적으로 표시합니다.
|
||||
|
||||
@@ -98,4 +98,97 @@ public class DeckPlanningServiceTests
|
||||
layouts.Should().Contain("comparison");
|
||||
layouts.Should().Contain("roadmap");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RefineForQuality_ShouldStrengthenWeakSpecializedSlides()
|
||||
{
|
||||
using var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"title": "Executive Operating Review",
|
||||
"objective": "improve execution quality",
|
||||
"decision_ask": "approve the first delivery wave",
|
||||
"slides": [
|
||||
{
|
||||
"layout": "executive_summary",
|
||||
"title": "Executive Summary",
|
||||
"body": [
|
||||
"Improve execution quality",
|
||||
"Approve the first delivery wave"
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "comparison",
|
||||
"title": "Execution Options",
|
||||
"options": [
|
||||
{ "name": "Pilot" },
|
||||
{ "name": "Wave rollout" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "roadmap",
|
||||
"title": "Roadmap",
|
||||
"phases": [
|
||||
{ "title": "Kickoff", "owner": "담당 미정" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "chart",
|
||||
"title": "Trend",
|
||||
"chart_labels": ["Q1", "Q2", "Q3"],
|
||||
"chart_values": [100, 120, 138]
|
||||
},
|
||||
{
|
||||
"layout": "kpi_dashboard",
|
||||
"title": "KPI Snapshot",
|
||||
"kpis": [
|
||||
{ "label": "Revenue", "value": "+12%" },
|
||||
{ "label": "Cycle Time", "value": "-8%" },
|
||||
{ "label": "Quality", "value": "97%" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
using var prepared = DeckPlanningService.Prepare(args.RootElement, args.RootElement.GetProperty("slides"), "Executive Operating Review");
|
||||
var review = DeckQualityReviewService.ReviewDeck(
|
||||
"Executive Operating Review",
|
||||
prepared.Slides,
|
||||
hasTemplate: false,
|
||||
prepared.AutoRepairCount,
|
||||
prepared.Storyline);
|
||||
|
||||
using var refined = DeckPlanningService.RefineForQuality(prepared, review, "Executive Operating Review");
|
||||
var slides = refined.Slides.EnumerateArray().ToList();
|
||||
|
||||
var executive = slides.First(slide => slide.GetProperty("layout").GetString() == "executive_summary");
|
||||
executive.TryGetProperty("summary_points", out var executivePoints).Should().BeTrue();
|
||||
executivePoints.GetArrayLength().Should().BeGreaterThan(0);
|
||||
executive.TryGetProperty("kpis", out var executiveKpis).Should().BeTrue();
|
||||
executiveKpis.GetArrayLength().Should().BeGreaterThan(0);
|
||||
|
||||
var comparison = slides.First(slide => slide.GetProperty("layout").GetString() == "comparison");
|
||||
comparison.TryGetProperty("options", out var options).Should().BeTrue();
|
||||
options[0].GetProperty("verdict").GetString().Should().NotBeNullOrWhiteSpace();
|
||||
options[0].GetProperty("summary").GetString().Should().NotBeNullOrWhiteSpace();
|
||||
|
||||
var roadmap = slides.First(slide => slide.GetProperty("layout").GetString() == "roadmap");
|
||||
var firstPhase = roadmap.GetProperty("phases")[0];
|
||||
firstPhase.GetProperty("owner").GetString().Should().NotContain("미정");
|
||||
firstPhase.GetProperty("timeline").GetString().Should().NotBeNullOrWhiteSpace();
|
||||
firstPhase.GetProperty("detail").GetString().Should().NotBeNullOrWhiteSpace();
|
||||
|
||||
var chart = slides.First(slide => slide.GetProperty("layout").GetString() == "chart");
|
||||
chart.TryGetProperty("summary_points", out var chartPoints).Should().BeTrue();
|
||||
chartPoints.GetArrayLength().Should().BeGreaterThan(0);
|
||||
|
||||
var kpi = slides.First(slide => slide.GetProperty("layout").GetString() == "kpi_dashboard");
|
||||
kpi.TryGetProperty("summary_points", out var kpiPoints).Should().BeTrue();
|
||||
kpiPoints.GetArrayLength().Should().BeGreaterThan(0);
|
||||
kpi.GetProperty("kpis")[0].GetProperty("trend").GetString().Should().NotBeNullOrWhiteSpace();
|
||||
kpi.GetProperty("kpis")[0].GetProperty("note").GetString().Should().NotBeNullOrWhiteSpace();
|
||||
|
||||
refined.AutoRepairCount.Should().BeGreaterThan(prepared.AutoRepairCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,5 +75,91 @@ public class PptxSkillAutoRepairTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldRefineWeakDeckBeforeExport()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-pptx-refine-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workDir);
|
||||
|
||||
try
|
||||
{
|
||||
var context = new AgentContext
|
||||
{
|
||||
WorkFolder = workDir,
|
||||
Permission = "Auto",
|
||||
OperationMode = "external",
|
||||
};
|
||||
|
||||
var tool = new PptxSkill();
|
||||
var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"path": "refined-deck.pptx",
|
||||
"title": "Quarterly Steering Deck",
|
||||
"objective": "approve the next delivery wave",
|
||||
"slides": [
|
||||
{
|
||||
"layout": "executive_summary",
|
||||
"title": "Executive Summary",
|
||||
"body": [
|
||||
"Approve the next delivery wave",
|
||||
"The delivery plan should move forward"
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "comparison",
|
||||
"title": "Options",
|
||||
"options": [
|
||||
{ "name": "Conservative" },
|
||||
{ "name": "Balanced" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "roadmap",
|
||||
"title": "Roadmap",
|
||||
"phases": [
|
||||
{ "title": "Kickoff", "owner": "담당 미정" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "chart",
|
||||
"title": "Trend",
|
||||
"chart_labels": ["Jan", "Feb", "Mar"],
|
||||
"chart_values": [10, 13, 16]
|
||||
},
|
||||
{
|
||||
"layout": "kpi_dashboard",
|
||||
"title": "KPI Snapshot",
|
||||
"kpis": [
|
||||
{ "label": "Revenue", "value": "+9%" },
|
||||
{ "label": "Cycle Time", "value": "-6%" },
|
||||
{ "label": "Stability", "value": "96%" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
result.Output.Should().Contain("PPT quality");
|
||||
result.Output.Should().Contain("Needs work: none");
|
||||
result.Output.Should().Contain("Auto-repair:");
|
||||
File.Exists(Path.Combine(workDir, "refined-deck.pptx")).Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ public partial class AgentLoopService
|
||||
if (string.Equals(activeTab, "Code", StringComparison.OrdinalIgnoreCase))
|
||||
return "file_write -> file_edit -> build_run/test_loop as needed";
|
||||
|
||||
return "document_plan -> docx_create/html_create/excel_create -> self-review";
|
||||
return "pptx_create for presentations/decks, otherwise document_plan -> creation tool -> self-review";
|
||||
}
|
||||
|
||||
if (HasExplicitFolderIntent(userQuery))
|
||||
@@ -336,7 +336,8 @@ public partial class AgentLoopService
|
||||
"Exploration scope = direct-creation. The user wants to CREATE a new file or document. " +
|
||||
"Do NOT search for existing files with glob/grep/folder_map unless the user explicitly asked to inspect the workspace. " +
|
||||
"If you are in the Code tab, call file_write immediately with a relative path inside the current work folder, then use file_edit/build_run/test_loop only if needed. " +
|
||||
"If you are in Cowork, call document_plan first and then the appropriate creation tool. " +
|
||||
"If you are in Cowork and the user asked for a presentation, deck, slide pack, or PPT, call pptx_create directly unless the user explicitly asked for a plan or outline. " +
|
||||
"Use document_plan first only when you are drafting a multi-section written document or when structure planning materially improves the result. " +
|
||||
"The output MUST be a real file on disk, not a text response.";
|
||||
}
|
||||
|
||||
|
||||
@@ -147,6 +147,118 @@ public static class DeckPlanningService
|
||||
};
|
||||
}
|
||||
|
||||
public static DeckPreparationResult RefineForQuality(
|
||||
DeckPreparationResult prepared,
|
||||
DeckQualityReport review,
|
||||
string presentationTitle)
|
||||
{
|
||||
var slideNodes = ParseSlides(prepared.Slides);
|
||||
var repairs = 0;
|
||||
var injectedAppendix = prepared.InjectedAppendix;
|
||||
|
||||
for (var i = 0; i < slideNodes.Count; i++)
|
||||
{
|
||||
repairs += RefineSlideForQuality(
|
||||
slideNodes[i],
|
||||
i,
|
||||
presentationTitle,
|
||||
prepared.Objective,
|
||||
prepared.DecisionAsk);
|
||||
repairs += NormalizeSlide(
|
||||
slideNodes[i],
|
||||
i,
|
||||
presentationTitle,
|
||||
prepared.Objective,
|
||||
prepared.DecisionAsk);
|
||||
}
|
||||
|
||||
if (ShouldAddEvidenceAppendix(review) && !HasAppendixSlide(slideNodes))
|
||||
{
|
||||
slideNodes.Add(BuildAppendixSlide(slideNodes));
|
||||
injectedAppendix = true;
|
||||
repairs++;
|
||||
}
|
||||
|
||||
var storyline = BuildStoryline(slideNodes, prepared.Storyline);
|
||||
var json = JsonSerializer.SerializeToUtf8Bytes(ToJsonArray(slideNodes));
|
||||
var document = JsonDocument.Parse(json);
|
||||
|
||||
return new DeckPreparationResult
|
||||
{
|
||||
SlidesDocument = document,
|
||||
Storyline = storyline,
|
||||
AutoRepairCount = prepared.AutoRepairCount + repairs,
|
||||
Audience = prepared.Audience,
|
||||
Objective = prepared.Objective,
|
||||
DecisionAsk = prepared.DecisionAsk,
|
||||
SuggestedTheme = prepared.SuggestedTheme,
|
||||
InjectedTitleSlide = prepared.InjectedTitleSlide,
|
||||
InjectedExecutiveSummary = prepared.InjectedExecutiveSummary,
|
||||
InjectedRecommendation = prepared.InjectedRecommendation,
|
||||
InjectedRoadmap = prepared.InjectedRoadmap,
|
||||
InjectedAppendix = injectedAppendix,
|
||||
};
|
||||
}
|
||||
|
||||
private static int RefineSlideForQuality(
|
||||
JsonObject slide,
|
||||
int slideIndex,
|
||||
string presentationTitle,
|
||||
string? objective,
|
||||
string? decisionAsk)
|
||||
{
|
||||
var repairs = 0;
|
||||
var layout = ReadString(slide, "layout").Trim().ToLowerInvariant();
|
||||
|
||||
switch (layout)
|
||||
{
|
||||
case "executive_summary":
|
||||
repairs += EnsureExecutiveSummaryContent(slide, objective, decisionAsk);
|
||||
repairs += EnsureKpiContent(slide);
|
||||
repairs += EnsureKpiNarrative(slide);
|
||||
repairs += EnsureSummaryTakeaways(slide, objective, decisionAsk);
|
||||
repairs += RemoveRedundantBody(slide, objective, decisionAsk);
|
||||
break;
|
||||
|
||||
case "recommendation":
|
||||
repairs += EnsureRecommendationSupport(slide, objective, decisionAsk);
|
||||
repairs += RemoveRedundantBody(slide, objective, decisionAsk);
|
||||
break;
|
||||
|
||||
case "comparison":
|
||||
repairs += EnsureComparisonRationale(slide);
|
||||
repairs += RemoveRedundantBody(slide, objective, decisionAsk);
|
||||
break;
|
||||
|
||||
case "roadmap":
|
||||
repairs += EnsureRoadmapExecutionLabels(slide);
|
||||
repairs += RemoveRedundantBody(slide, objective, decisionAsk);
|
||||
break;
|
||||
|
||||
case "chart":
|
||||
repairs += EnsureChartTakeaway(slide, objective, decisionAsk);
|
||||
break;
|
||||
|
||||
case "kpi_dashboard":
|
||||
repairs += EnsureKpiContent(slide);
|
||||
repairs += EnsureKpiNarrative(slide);
|
||||
repairs += EnsureSummaryTakeaways(slide, objective, decisionAsk);
|
||||
repairs += RemoveRedundantBody(slide, objective, decisionAsk);
|
||||
break;
|
||||
}
|
||||
|
||||
if (layout is "executive_summary" or "comparison" or "recommendation" or "roadmap" or "kpi_dashboard" or "chart")
|
||||
{
|
||||
if (EnsureDistinctiveHeadline(slide, layout, slideIndex, presentationTitle, objective, decisionAsk))
|
||||
repairs++;
|
||||
}
|
||||
|
||||
if (TrimLongHeadline(slide))
|
||||
repairs++;
|
||||
|
||||
return repairs;
|
||||
}
|
||||
|
||||
private static int NormalizeSlide(
|
||||
JsonObject slide,
|
||||
int slideIndex,
|
||||
@@ -608,6 +720,396 @@ public static class DeckPlanningService
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int EnsureKpiNarrative(JsonObject slide)
|
||||
{
|
||||
if (!slide.TryGetPropertyValue("kpis", out var kpisNode) || kpisNode is not JsonArray kpis || kpis.Count == 0)
|
||||
return 0;
|
||||
|
||||
var repairs = 0;
|
||||
foreach (var (item, index) in kpis.OfType<JsonObject>().Select((value, index) => (value, index)))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ReadString(item, "trend")))
|
||||
{
|
||||
item["trend"] = index switch
|
||||
{
|
||||
0 => "Current trend",
|
||||
1 => "Execution watch",
|
||||
_ => "Next review",
|
||||
};
|
||||
repairs++;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ReadString(item, "note")))
|
||||
{
|
||||
item["note"] = index switch
|
||||
{
|
||||
0 => "Primary takeaway",
|
||||
1 => "Operational context",
|
||||
_ => "Decision context",
|
||||
};
|
||||
repairs++;
|
||||
}
|
||||
}
|
||||
|
||||
return repairs;
|
||||
}
|
||||
|
||||
private static int EnsureSummaryTakeaways(JsonObject slide, string? objective, string? decisionAsk)
|
||||
{
|
||||
if (ReadTextLines(slide, "summary_points").Count > 0)
|
||||
return 0;
|
||||
|
||||
var takeaways = BuildTakeawaysFromMetrics(slide);
|
||||
if (takeaways.Count == 0)
|
||||
{
|
||||
takeaways = new List<string>
|
||||
{
|
||||
Coalesce(decisionAsk, objective, "Translate the slide into one decision-ready takeaway."),
|
||||
"Separate the most urgent action from the supporting evidence.",
|
||||
"Keep the message anchored to impact, risk, and execution timing."
|
||||
};
|
||||
}
|
||||
|
||||
slide["summary_points"] = ToJsonArray(takeaways.Take(3));
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int EnsureRecommendationSupport(JsonObject slide, string? objective, string? decisionAsk)
|
||||
{
|
||||
var repairs = 0;
|
||||
if (string.IsNullOrWhiteSpace(ReadString(slide, "recommendation")))
|
||||
{
|
||||
slide["recommendation"] = Coalesce(decisionAsk, objective, "Confirm the preferred path and move into execution.");
|
||||
repairs++;
|
||||
}
|
||||
|
||||
if (ReadTextLines(slide, "summary_points").Count == 0)
|
||||
{
|
||||
slide["summary_points"] = new JsonArray(
|
||||
"The recommendation balances impact, delivery risk, and implementation effort.",
|
||||
"The preferred option can start quickly without blocking later scale-up.",
|
||||
"The recommendation links directly to the requested decision and operating next step.");
|
||||
repairs++;
|
||||
}
|
||||
|
||||
if (ReadTextLines(slide, "next_steps").Count == 0)
|
||||
{
|
||||
slide["next_steps"] = new JsonArray(
|
||||
"Confirm scope, timing, and accountable owner.",
|
||||
"Launch the first execution milestone with explicit success metrics.",
|
||||
"Review early results and expand only after the first checkpoint.");
|
||||
repairs++;
|
||||
}
|
||||
|
||||
return repairs;
|
||||
}
|
||||
|
||||
private static int EnsureComparisonRationale(JsonObject slide)
|
||||
{
|
||||
var repairs = EnsureMinimumOptions(slide) ? 1 : 0;
|
||||
if (!slide.TryGetPropertyValue("options", out var optionsNode) || optionsNode is not JsonArray options)
|
||||
return repairs;
|
||||
|
||||
var hasRecommendedVerdict = options.OfType<JsonObject>()
|
||||
.Any(option => ReadString(option, "verdict").Contains("recommended", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
foreach (var (option, index) in options.OfType<JsonObject>().Select((value, index) => (value, index)))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ReadString(option, "pros")))
|
||||
{
|
||||
option["pros"] = index == 0 ? "Fastest route to an initial result" : "Balanced delivery profile";
|
||||
repairs++;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ReadString(option, "cons")))
|
||||
{
|
||||
option["cons"] = index == 0 ? "May leave key follow-up work for later" : "Needs more alignment up front";
|
||||
repairs++;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ReadString(option, "summary")))
|
||||
{
|
||||
option["summary"] = Coalesce(
|
||||
ReadString(option, "pros"),
|
||||
"Summarize why this option matters and what trade-off it brings.");
|
||||
repairs++;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ReadString(option, "verdict")))
|
||||
{
|
||||
option["verdict"] = !hasRecommendedVerdict && index == 0
|
||||
? "Recommended"
|
||||
: "Consider";
|
||||
hasRecommendedVerdict = true;
|
||||
repairs++;
|
||||
}
|
||||
}
|
||||
|
||||
return repairs;
|
||||
}
|
||||
|
||||
private static int EnsureRoadmapExecutionLabels(JsonObject slide)
|
||||
{
|
||||
var repairs = EnsureRoadmapPhases(slide) ? 1 : 0;
|
||||
if (!slide.TryGetPropertyValue("phases", out var phasesNode) || phasesNode is not JsonArray phases)
|
||||
return repairs;
|
||||
|
||||
while (phases.Count < 3)
|
||||
{
|
||||
var nextIndex = phases.Count;
|
||||
phases.Add(new JsonObject
|
||||
{
|
||||
["title"] = $"Phase {nextIndex + 1}",
|
||||
["detail"] = nextIndex switch
|
||||
{
|
||||
1 => "Execute the first delivery wave and validate performance",
|
||||
_ => "Scale the operating rhythm and lock the next checkpoint",
|
||||
},
|
||||
["timeline"] = nextIndex switch
|
||||
{
|
||||
1 => "30-60 days",
|
||||
_ => "60-90 days",
|
||||
},
|
||||
["owner"] = nextIndex switch
|
||||
{
|
||||
1 => "Delivery lead",
|
||||
_ => "Business owner",
|
||||
}
|
||||
});
|
||||
repairs++;
|
||||
}
|
||||
|
||||
foreach (var (phase, index) in phases.OfType<JsonObject>().Select((value, index) => (value, index)))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ReadString(phase, "title")))
|
||||
{
|
||||
phase["title"] = $"Phase {index + 1}";
|
||||
repairs++;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ReadString(phase, "detail")))
|
||||
{
|
||||
phase["detail"] = index switch
|
||||
{
|
||||
0 => "Lock the scope, decision, and success criteria",
|
||||
1 => "Execute the first delivery wave and validate results",
|
||||
_ => "Scale the operating rhythm and stabilize outcomes",
|
||||
};
|
||||
repairs++;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ReadString(phase, "timeline")))
|
||||
{
|
||||
phase["timeline"] = index switch
|
||||
{
|
||||
0 => "0-30 days",
|
||||
1 => "30-60 days",
|
||||
_ => "60-90 days",
|
||||
};
|
||||
repairs++;
|
||||
}
|
||||
|
||||
var owner = ReadString(phase, "owner");
|
||||
if (string.IsNullOrWhiteSpace(owner) || IsPlaceholderOwner(owner))
|
||||
{
|
||||
phase["owner"] = index switch
|
||||
{
|
||||
0 => "Program sponsor",
|
||||
1 => "Delivery lead",
|
||||
_ => "Business owner",
|
||||
};
|
||||
repairs++;
|
||||
}
|
||||
}
|
||||
|
||||
return repairs;
|
||||
}
|
||||
|
||||
private static bool EnsureDistinctiveHeadline(
|
||||
JsonObject slide,
|
||||
string layout,
|
||||
int slideIndex,
|
||||
string presentationTitle,
|
||||
string? objective,
|
||||
string? decisionAsk)
|
||||
{
|
||||
var headline = ReadString(slide, "headline");
|
||||
var shouldReplace =
|
||||
string.IsNullOrWhiteSpace(headline) ||
|
||||
string.Equals(headline, objective, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(headline, decisionAsk, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!shouldReplace)
|
||||
return false;
|
||||
|
||||
var replacement = layout switch
|
||||
{
|
||||
"executive_summary" => Coalesce(
|
||||
decisionAsk is { Length: > 0 } ? $"Decision: {decisionAsk}" : null,
|
||||
objective is { Length: > 0 } ? $"Executive takeaway: {objective}" : null,
|
||||
"Executive takeaway: align the decision, evidence, and next step."),
|
||||
"comparison" => "Preferred option balances impact, delivery speed, and implementation risk.",
|
||||
"recommendation" => Coalesce(
|
||||
decisionAsk is { Length: > 0 } ? $"Recommendation: {decisionAsk}" : null,
|
||||
"Recommendation: move with the preferred path and confirm the first milestone."),
|
||||
"roadmap" => "Execution can move in phased milestones with clear owners and timing.",
|
||||
"kpi_dashboard" => "Core KPIs show the operating trend and the next action focus.",
|
||||
"chart" => Coalesce(BuildChartHeadline(slide), "The chart translates trend movement into a decision takeaway."),
|
||||
_ => BuildHeadline(slide, slideIndex, presentationTitle, objective, decisionAsk)
|
||||
};
|
||||
|
||||
slide["headline"] = replacement;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int EnsureChartTakeaway(JsonObject slide, string? objective, string? decisionAsk)
|
||||
{
|
||||
var repairs = 0;
|
||||
if (!HasChartData(slide))
|
||||
return repairs;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ReadString(slide, "headline")))
|
||||
{
|
||||
slide["headline"] = Coalesce(
|
||||
decisionAsk,
|
||||
objective,
|
||||
BuildChartHeadline(slide),
|
||||
"Translate the chart into a single decision-ready takeaway.");
|
||||
repairs++;
|
||||
}
|
||||
|
||||
if (ReadTextLines(slide, "summary_points").Count == 0)
|
||||
{
|
||||
var takeaway = BuildChartTakeaways(slide);
|
||||
if (takeaway.Count > 0)
|
||||
{
|
||||
slide["summary_points"] = ToJsonArray(takeaway);
|
||||
repairs++;
|
||||
}
|
||||
}
|
||||
|
||||
return repairs;
|
||||
}
|
||||
|
||||
private static int RemoveRedundantBody(JsonObject slide, string? objective, string? decisionAsk)
|
||||
{
|
||||
var bodyLines = ReadTextLines(slide, "body");
|
||||
if (bodyLines.Count == 0)
|
||||
return 0;
|
||||
|
||||
var normalizedBody = string.Join(" ", bodyLines).Trim();
|
||||
var objectiveText = objective?.Trim() ?? string.Empty;
|
||||
var askText = decisionAsk?.Trim() ?? string.Empty;
|
||||
|
||||
if ((!string.IsNullOrWhiteSpace(objectiveText) && normalizedBody.Contains(objectiveText, StringComparison.OrdinalIgnoreCase)) ||
|
||||
(!string.IsNullOrWhiteSpace(askText) && normalizedBody.Contains(askText, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
slide.Remove("body");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool HasAppendixSlide(IReadOnlyList<JsonObject> slides)
|
||||
{
|
||||
return slides.Any(slide =>
|
||||
{
|
||||
var title = ReadString(slide, "title");
|
||||
return title.Contains("appendix", StringComparison.OrdinalIgnoreCase) ||
|
||||
title.Contains("\uBD80\uB85D", StringComparison.OrdinalIgnoreCase) ||
|
||||
title.Contains("evidence", StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
|
||||
private static bool ShouldAddEvidenceAppendix(DeckQualityReport review)
|
||||
{
|
||||
return review.Issues.Any(issue =>
|
||||
issue.Message.Contains("Appendix or evidence slide", StringComparison.OrdinalIgnoreCase) ||
|
||||
issue.Message.Contains("appendix or evidence support", StringComparison.OrdinalIgnoreCase) ||
|
||||
issue.Message.Contains("Evidence slides", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static bool IsPlaceholderOwner(string owner)
|
||||
{
|
||||
return owner.Contains("담당 미정", StringComparison.OrdinalIgnoreCase) ||
|
||||
owner.Contains("미정", StringComparison.OrdinalIgnoreCase) ||
|
||||
owner.Contains("tbd", StringComparison.OrdinalIgnoreCase) ||
|
||||
owner.Contains("unknown", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static List<string> BuildTakeawaysFromMetrics(JsonObject slide)
|
||||
{
|
||||
var takeaways = new List<string>();
|
||||
if (!slide.TryGetPropertyValue("kpis", out var kpisNode) || kpisNode is not JsonArray kpis)
|
||||
return takeaways;
|
||||
|
||||
foreach (var item in kpis.OfType<JsonObject>().Take(3))
|
||||
{
|
||||
var label = Coalesce(ReadString(item, "label"), ReadString(item, "title"), "Metric");
|
||||
var value = Coalesce(ReadString(item, "value"), "tracked");
|
||||
var trend = ReadString(item, "trend");
|
||||
var note = ReadString(item, "note");
|
||||
takeaways.Add($"{label} is {value}{FormatContextSuffix(trend, note)}.");
|
||||
}
|
||||
|
||||
return takeaways;
|
||||
}
|
||||
|
||||
private static string BuildChartHeadline(JsonObject slide)
|
||||
{
|
||||
if (!slide.TryGetPropertyValue("chart_labels", out var labelsNode) ||
|
||||
!slide.TryGetPropertyValue("chart_values", out var valuesNode) ||
|
||||
labelsNode is not JsonArray labels ||
|
||||
valuesNode is not JsonArray values ||
|
||||
labels.Count == 0 ||
|
||||
values.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var firstLabel = UnquoteJson(labels[0]?.ToJsonString() ?? string.Empty);
|
||||
var firstValue = UnquoteJson(values[0]?.ToJsonString() ?? string.Empty);
|
||||
if (string.IsNullOrWhiteSpace(firstLabel) || string.IsNullOrWhiteSpace(firstValue))
|
||||
return string.Empty;
|
||||
|
||||
return $"{firstLabel} anchors the chart story at {firstValue}.";
|
||||
}
|
||||
|
||||
private static List<string> BuildChartTakeaways(JsonObject slide)
|
||||
{
|
||||
var takeaways = new List<string>();
|
||||
if (!slide.TryGetPropertyValue("chart_labels", out var labelsNode) ||
|
||||
!slide.TryGetPropertyValue("chart_values", out var valuesNode) ||
|
||||
labelsNode is not JsonArray labels ||
|
||||
valuesNode is not JsonArray values)
|
||||
{
|
||||
return takeaways;
|
||||
}
|
||||
|
||||
for (var i = 0; i < Math.Min(3, Math.Min(labels.Count, values.Count)); i++)
|
||||
{
|
||||
var label = UnquoteJson(labels[i]?.ToJsonString() ?? string.Empty);
|
||||
var value = UnquoteJson(values[i]?.ToJsonString() ?? string.Empty);
|
||||
if (string.IsNullOrWhiteSpace(label) || string.IsNullOrWhiteSpace(value))
|
||||
continue;
|
||||
|
||||
takeaways.Add($"{label} is tracked at {value} and should inform the decision message.");
|
||||
}
|
||||
|
||||
return takeaways;
|
||||
}
|
||||
|
||||
private static string FormatContextSuffix(string trend, string note)
|
||||
{
|
||||
var context = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(trend))
|
||||
context.Add(trend);
|
||||
if (!string.IsNullOrWhiteSpace(note))
|
||||
context.Add(note);
|
||||
return context.Count == 0 ? string.Empty : $" ({string.Join(", ", context)})";
|
||||
}
|
||||
|
||||
private static bool TrimLongHeadline(JsonObject slide)
|
||||
{
|
||||
var headline = ReadString(slide, "headline");
|
||||
|
||||
@@ -564,28 +564,69 @@ public class PptxSkill : IAgentTool
|
||||
if (!hasSlidesArray && !cloneAll)
|
||||
return ToolResult.Fail("slides 배열이 필요합니다.");
|
||||
|
||||
using var preparedDeck = hasSlidesArray
|
||||
DeckPreparationResult? preparedDeck = hasSlidesArray
|
||||
? DeckPlanningService.Prepare(args, slidesEl, presTitle)
|
||||
: null;
|
||||
DeckPreparationResult? refinedDeck = null;
|
||||
DeckPreparationResult? renderDeck = preparedDeck;
|
||||
DeckQualityReport? deckReview = null;
|
||||
|
||||
if (preparedDeck != null)
|
||||
if (renderDeck != null)
|
||||
{
|
||||
slidesEl = preparedDeck.Slides;
|
||||
slidesEl = renderDeck.Slides;
|
||||
if (!hasExplicitTheme && !hasExplicitTemplate && !hasThemeFile &&
|
||||
!string.IsNullOrWhiteSpace(preparedDeck.SuggestedTheme))
|
||||
!string.IsNullOrWhiteSpace(renderDeck.SuggestedTheme))
|
||||
{
|
||||
theme = preparedDeck.SuggestedTheme!;
|
||||
theme = renderDeck.SuggestedTheme!;
|
||||
}
|
||||
}
|
||||
|
||||
var templatePack = !string.IsNullOrWhiteSpace(requestedTemplatePack)
|
||||
? PptxTemplatePackRegistry.Resolve(requestedTemplatePack)
|
||||
: (!hasExplicitTheme && !hasExplicitTemplate && !hasThemeFile
|
||||
? PptxTemplatePackRegistry.Suggest(preparedDeck?.Objective ?? objective, preparedDeck?.Audience ?? audience)
|
||||
? PptxTemplatePackRegistry.Suggest(renderDeck?.Objective ?? objective, renderDeck?.Audience ?? audience)
|
||||
: null);
|
||||
var templatePackName = templatePack?.Name;
|
||||
var packTemplateName = templatePack?.PreferredTemplate;
|
||||
|
||||
if (renderDeck != null)
|
||||
{
|
||||
deckReview = DeckQualityReviewService.ReviewDeck(
|
||||
presTitle,
|
||||
renderDeck.Slides,
|
||||
hasExplicitTemplate || hasThemeFile || !string.IsNullOrWhiteSpace(templatePackName),
|
||||
renderDeck.AutoRepairCount,
|
||||
renderDeck.Storyline);
|
||||
|
||||
if (ShouldAutoRefineDeck(deckReview))
|
||||
{
|
||||
refinedDeck = DeckPlanningService.RefineForQuality(renderDeck, deckReview, presTitle);
|
||||
var refinedReview = DeckQualityReviewService.ReviewDeck(
|
||||
presTitle,
|
||||
refinedDeck.Slides,
|
||||
hasExplicitTemplate || hasThemeFile || !string.IsNullOrWhiteSpace(templatePackName),
|
||||
refinedDeck.AutoRepairCount,
|
||||
refinedDeck.Storyline);
|
||||
|
||||
if (IsImprovedDeckReview(deckReview, refinedReview))
|
||||
{
|
||||
renderDeck = refinedDeck;
|
||||
slidesEl = renderDeck.Slides;
|
||||
deckReview = refinedReview;
|
||||
if (!hasExplicitTheme && !hasExplicitTemplate && !hasThemeFile &&
|
||||
!string.IsNullOrWhiteSpace(renderDeck.SuggestedTheme))
|
||||
{
|
||||
theme = renderDeck.SuggestedTheme!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
refinedDeck.Dispose();
|
||||
refinedDeck = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fullPath = FileReadTool.ResolvePath(path, context.WorkFolder);
|
||||
if (context.ActiveTab == "Cowork") fullPath = AgentContext.EnsureTimestampedPath(fullPath);
|
||||
if (!fullPath.EndsWith(".pptx", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -1012,22 +1053,23 @@ public class PptxSkill : IAgentTool
|
||||
: templatePptxPath != null
|
||||
? $"theme_file:{cloneInfo_srcLabel} (master cloned{(cloneAll ? " + slides cloned" : "")})"
|
||||
: theme;
|
||||
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 (renderDeck != null)
|
||||
outputParts.Add(renderDeck.ToToolSummary());
|
||||
if (!string.IsNullOrWhiteSpace(templatePackName))
|
||||
outputParts.Add($"Template pack: {templatePackName}");
|
||||
if (deckReview == null && hasSlidesArray)
|
||||
{
|
||||
deckReview = DeckQualityReviewService.ReviewDeck(
|
||||
presTitle,
|
||||
slidesEl,
|
||||
!string.IsNullOrWhiteSpace(templateName) || templatePptxPath != null || !string.IsNullOrWhiteSpace(templatePackName),
|
||||
renderDeck?.AutoRepairCount ?? 0,
|
||||
renderDeck?.Storyline);
|
||||
}
|
||||
if (deckReview != null)
|
||||
outputParts.AddRange(ArtifactQualityOutputFormatter.BuildLines(deckReview));
|
||||
return ToolResult.Ok(string.Join("\n", outputParts), fullPath);
|
||||
@@ -1037,6 +1079,37 @@ public class PptxSkill : IAgentTool
|
||||
if (File.Exists(fullPath)) try { File.Delete(fullPath); } catch { }
|
||||
return ToolResult.Fail($"PPTX 생성 실패: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
refinedDeck?.Dispose();
|
||||
preparedDeck?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ShouldAutoRefineDeck(DeckQualityReport review)
|
||||
{
|
||||
return review.Score < 80 ||
|
||||
review.Issues.Any(issue => issue.Severity is DeckReviewSeverity.Warning or DeckReviewSeverity.Critical);
|
||||
}
|
||||
|
||||
private static bool IsImprovedDeckReview(DeckQualityReport baseline, DeckQualityReport refined)
|
||||
{
|
||||
if (refined.Score > baseline.Score)
|
||||
return true;
|
||||
|
||||
var baselineCritical = baseline.Issues.Count(issue => issue.Severity == DeckReviewSeverity.Critical);
|
||||
var refinedCritical = refined.Issues.Count(issue => issue.Severity == DeckReviewSeverity.Critical);
|
||||
if (refinedCritical < baselineCritical)
|
||||
return true;
|
||||
|
||||
var baselineWarning = baseline.Issues.Count(issue => issue.Severity == DeckReviewSeverity.Warning);
|
||||
var refinedWarning = refined.Issues.Count(issue => issue.Severity == DeckReviewSeverity.Warning);
|
||||
if (refinedWarning < baselineWarning)
|
||||
return true;
|
||||
|
||||
var baselineAlerts = baseline.Issues.Count(issue => issue.Message.StartsWith("Slide ", StringComparison.OrdinalIgnoreCase));
|
||||
var refinedAlerts = refined.Issues.Count(issue => issue.Message.StartsWith("Slide ", StringComparison.OrdinalIgnoreCase));
|
||||
return refinedAlerts < baselineAlerts;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -68,6 +68,7 @@ public partial class ChatWindow
|
||||
sb.AppendLine("For ordinary Cowork requests where no plan is requested, proceed directly with the work and focus on producing the requested result.");
|
||||
sb.AppendLine("If the user asks for a brand-new report, proposal, analysis, manual, or other document and does not explicitly ask to reference workspace files, do NOT start with glob, grep, document_read, or folder_map.");
|
||||
sb.AppendLine("In that case, go straight to the creation tool. Use document_plan only when multi-section structure work clearly improves the result or when the user explicitly requests a plan.");
|
||||
sb.AppendLine("If the user asks for a presentation, slide deck, or PPT, prefer calling pptx_create directly. Use document_plan first only when the user explicitly asks for an outline or when the deck structure is genuinely unclear.");
|
||||
sb.AppendLine("When writing a new document, avoid repetitive same-shape sections. Tailor the structure to the purpose and use summaries, findings, comparison tables, timelines, recommendations, appendices, or action items when they improve clarity.");
|
||||
sb.AppendLine("Prefer concrete and useful content over filler. If a section benefits from bullets, tables, or structured comparison, use them instead of flat generic paragraphs.");
|
||||
sb.AppendLine("After creating files, summarize what was created and include the actual output path.");
|
||||
@@ -85,7 +86,7 @@ public partial class ChatWindow
|
||||
sb.AppendLine("IMPORTANT: For reports, proposals, analyses, and manuals with multiple sections:");
|
||||
sb.AppendLine(" 1. Decide the document structure internally first.");
|
||||
sb.AppendLine(" 2. Use document_plan only when multi-section structure work clearly improves the result or when the user explicitly asks for a plan. When the user requests a plan, present it for approval before proceeding.");
|
||||
sb.AppendLine(" 3. Then immediately create the real output file with html_create, docx_create, markdown_create, or file_write.");
|
||||
sb.AppendLine(" 3. Then immediately create the real output file with html_create, docx_create, markdown_create, pptx_create, or file_write.");
|
||||
sb.AppendLine(" 4. Fill every section with real content. Do not stop at an outline or plan only.");
|
||||
sb.AppendLine(" 4-1. Use richer section patterns when they help: summary box, key findings bullets, comparison table, timeline, checklist, action items, appendix.");
|
||||
sb.AppendLine(" 5. The task is complete only after the actual document file has been created or updated.");
|
||||
|
||||
Reference in New Issue
Block a user