Initial commit to new repository
This commit is contained in:
333
.decompiledproj/AxCopilot/Services/Agent/PptxSkill.cs
Normal file
333
.decompiledproj/AxCopilot/Services/Agent/PptxSkill.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DocumentFormat.OpenXml;
|
||||
using DocumentFormat.OpenXml.Drawing;
|
||||
using DocumentFormat.OpenXml.Packaging;
|
||||
using DocumentFormat.OpenXml.Presentation;
|
||||
|
||||
namespace AxCopilot.Services.Agent;
|
||||
|
||||
public class PptxSkill : IAgentTool
|
||||
{
|
||||
private static readonly Dictionary<string, (string Primary, string Accent, string TextDark, string TextLight, string Bg)> Themes = new Dictionary<string, (string, string, string, string, string)>
|
||||
{
|
||||
["professional"] = ("2B579A", "4B5EFC", "1A1A2E", "FFFFFF", "FFFFFF"),
|
||||
["modern"] = ("0D9488", "06B6D4", "1A1A2E", "FFFFFF", "FFFFFF"),
|
||||
["dark"] = ("374151", "6366F1", "F9FAFB", "FFFFFF", "1F2937"),
|
||||
["minimal"] = ("6B7280", "3B82F6", "111827", "FFFFFF", "FAFAFA")
|
||||
};
|
||||
|
||||
public string Name => "pptx_create";
|
||||
|
||||
public string Description => "Create a PowerPoint (.pptx) presentation. Supports slide layouts: title (title+subtitle), content (title+body text), two_column (title+left+right), table (title+headers+rows), blank. No external runtime required (native OpenXML).";
|
||||
|
||||
public ToolParameterSchema Parameters
|
||||
{
|
||||
get
|
||||
{
|
||||
ToolParameterSchema toolParameterSchema = new ToolParameterSchema();
|
||||
Dictionary<string, ToolProperty> obj = new Dictionary<string, ToolProperty>
|
||||
{
|
||||
["path"] = new ToolProperty
|
||||
{
|
||||
Type = "string",
|
||||
Description = "Output file path (.pptx). Relative to work folder."
|
||||
},
|
||||
["title"] = new ToolProperty
|
||||
{
|
||||
Type = "string",
|
||||
Description = "Presentation title (used on first slide if no explicit title slide)."
|
||||
},
|
||||
["slides"] = new ToolProperty
|
||||
{
|
||||
Type = "array",
|
||||
Description = "Array of slide objects. Each slide: {\"layout\": \"title|content|two_column|table|blank\", \"title\": \"Slide Title\", \"subtitle\": \"...\", \"body\": \"...\", \"left\": \"...\", \"right\": \"...\", \"headers\": [...], \"rows\": [[...]], \"notes\": \"Speaker notes\"}",
|
||||
Items = new ToolProperty
|
||||
{
|
||||
Type = "object"
|
||||
}
|
||||
}
|
||||
};
|
||||
ToolProperty obj2 = new ToolProperty
|
||||
{
|
||||
Type = "string",
|
||||
Description = "Color theme: professional (blue), modern (teal), dark (dark gray), minimal (light). Default: professional"
|
||||
};
|
||||
int num = 4;
|
||||
List<string> list = new List<string>(num);
|
||||
CollectionsMarshal.SetCount(list, num);
|
||||
Span<string> span = CollectionsMarshal.AsSpan(list);
|
||||
span[0] = "professional";
|
||||
span[1] = "modern";
|
||||
span[2] = "dark";
|
||||
span[3] = "minimal";
|
||||
obj2.Enum = list;
|
||||
obj["theme"] = obj2;
|
||||
toolParameterSchema.Properties = obj;
|
||||
num = 2;
|
||||
List<string> list2 = new List<string>(num);
|
||||
CollectionsMarshal.SetCount(list2, num);
|
||||
Span<string> span2 = CollectionsMarshal.AsSpan(list2);
|
||||
span2[0] = "path";
|
||||
span2[1] = "slides";
|
||||
toolParameterSchema.Required = list2;
|
||||
return toolParameterSchema;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct)
|
||||
{
|
||||
string path = args.GetProperty("path").GetString() ?? "";
|
||||
if (!args.TryGetProperty("title", out var tt))
|
||||
{
|
||||
}
|
||||
else if (tt.GetString() == null)
|
||||
{
|
||||
}
|
||||
JsonElement th;
|
||||
string theme = (args.TryGetProperty("theme", out th) ? (th.GetString() ?? "professional") : "professional");
|
||||
if (!args.TryGetProperty("slides", out var slidesEl) || slidesEl.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return ToolResult.Fail("slides 배열이 필요합니다.");
|
||||
}
|
||||
string fullPath = FileReadTool.ResolvePath(path, context.WorkFolder);
|
||||
if (context.ActiveTab == "Cowork")
|
||||
{
|
||||
fullPath = AgentContext.EnsureTimestampedPath(fullPath);
|
||||
}
|
||||
if (!fullPath.EndsWith(".pptx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
fullPath += ".pptx";
|
||||
}
|
||||
if (!context.IsPathAllowed(fullPath))
|
||||
{
|
||||
return ToolResult.Fail("경로 접근 차단: " + fullPath);
|
||||
}
|
||||
if (!(await context.CheckWritePermissionAsync(Name, fullPath)))
|
||||
{
|
||||
return ToolResult.Fail("쓰기 권한 거부: " + fullPath);
|
||||
}
|
||||
string dir = System.IO.Path.GetDirectoryName(fullPath);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
if (!Themes.TryGetValue(theme, out var colors))
|
||||
{
|
||||
colors = Themes["professional"];
|
||||
}
|
||||
try
|
||||
{
|
||||
using PresentationDocument pres = PresentationDocument.Create(fullPath, PresentationDocumentType.Presentation);
|
||||
PresentationPart presPart = pres.AddPresentationPart();
|
||||
presPart.Presentation = new Presentation();
|
||||
presPart.Presentation.SlideIdList = new SlideIdList();
|
||||
presPart.Presentation.SlideSize = new SlideSize
|
||||
{
|
||||
Cx = 12192000,
|
||||
Cy = 6858000
|
||||
};
|
||||
presPart.Presentation.NotesSize = new NotesSize
|
||||
{
|
||||
Cx = 6858000L,
|
||||
Cy = 9144000L
|
||||
};
|
||||
uint slideId = 256u;
|
||||
int slideCount = 0;
|
||||
foreach (JsonElement slideEl in slidesEl.EnumerateArray())
|
||||
{
|
||||
JsonElement lay;
|
||||
string layout = (slideEl.TryGetProperty("layout", out lay) ? (lay.GetString() ?? "content") : "content");
|
||||
JsonElement st;
|
||||
string slideTitle = (slideEl.TryGetProperty("title", out st) ? (st.GetString() ?? "") : "");
|
||||
JsonElement sub;
|
||||
string subtitle = (slideEl.TryGetProperty("subtitle", out sub) ? (sub.GetString() ?? "") : "");
|
||||
JsonElement bd;
|
||||
string body = (slideEl.TryGetProperty("body", out bd) ? (bd.GetString() ?? "") : "");
|
||||
JsonElement lf;
|
||||
string left = (slideEl.TryGetProperty("left", out lf) ? (lf.GetString() ?? "") : "");
|
||||
JsonElement rt;
|
||||
string right = (slideEl.TryGetProperty("right", out rt) ? (rt.GetString() ?? "") : "");
|
||||
if (!slideEl.TryGetProperty("notes", out var nt))
|
||||
{
|
||||
}
|
||||
else if (nt.GetString() == null)
|
||||
{
|
||||
}
|
||||
SlidePart slidePart = presPart.AddNewPart<SlidePart>();
|
||||
slidePart.Slide = new Slide(new CommonSlideData(new ShapeTree(new DocumentFormat.OpenXml.Presentation.NonVisualGroupShapeProperties(new DocumentFormat.OpenXml.Presentation.NonVisualDrawingProperties
|
||||
{
|
||||
Id = 1u,
|
||||
Name = ""
|
||||
}, new DocumentFormat.OpenXml.Presentation.NonVisualGroupShapeDrawingProperties(), new ApplicationNonVisualDrawingProperties()), new GroupShapeProperties(new TransformGroup()))));
|
||||
ShapeTree shapeTree = slidePart.Slide.CommonSlideData.ShapeTree;
|
||||
uint shapeId = 2u;
|
||||
switch (layout)
|
||||
{
|
||||
case "title":
|
||||
AddRectangle(shapeTree, ref shapeId, 0L, 0L, 12192000L, 6858000L, colors.Primary);
|
||||
AddTextBox(shapeTree, ref shapeId, 600000L, 2000000L, 10992000L, 1400000L, slideTitle, 3600, colors.TextLight, bold: true);
|
||||
if (!string.IsNullOrEmpty(subtitle))
|
||||
{
|
||||
AddTextBox(shapeTree, ref shapeId, 600000L, 3600000L, 10992000L, 800000L, subtitle, 2000, colors.TextLight, bold: false);
|
||||
}
|
||||
break;
|
||||
case "two_column":
|
||||
AddTextBox(shapeTree, ref shapeId, 600000L, 300000L, 10992000L, 800000L, slideTitle, 2800, colors.Primary, bold: true);
|
||||
AddTextBox(shapeTree, ref shapeId, 600000L, 1300000L, 5200000L, 5000000L, left, 1600, colors.TextDark, bold: false);
|
||||
AddTextBox(shapeTree, ref shapeId, 6400000L, 1300000L, 5200000L, 5000000L, right, 1600, colors.TextDark, bold: false);
|
||||
break;
|
||||
case "table":
|
||||
{
|
||||
AddTextBox(shapeTree, ref shapeId, 600000L, 300000L, 10992000L, 800000L, slideTitle, 2800, colors.Primary, bold: true);
|
||||
string tableText = FormatTableAsText(slideEl, colors);
|
||||
AddTextBox(shapeTree, ref shapeId, 600000L, 1300000L, 10992000L, 5000000L, tableText, 1400, colors.TextDark, bold: false);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
AddTextBox(shapeTree, ref shapeId, 600000L, 300000L, 10992000L, 800000L, slideTitle, 2800, colors.Primary, bold: true);
|
||||
AddTextBox(shapeTree, ref shapeId, 600000L, 1300000L, 10992000L, 5000000L, body, 1600, colors.TextDark, bold: false);
|
||||
break;
|
||||
case "blank":
|
||||
break;
|
||||
}
|
||||
presPart.Presentation.SlideIdList.AppendChild(new SlideId
|
||||
{
|
||||
Id = slideId++,
|
||||
RelationshipId = presPart.GetIdOfPart(slidePart)
|
||||
});
|
||||
slideCount++;
|
||||
lay = default(JsonElement);
|
||||
st = default(JsonElement);
|
||||
sub = default(JsonElement);
|
||||
bd = default(JsonElement);
|
||||
lf = default(JsonElement);
|
||||
rt = default(JsonElement);
|
||||
nt = default(JsonElement);
|
||||
}
|
||||
presPart.Presentation.Save();
|
||||
return ToolResult.Ok($"✅ PPTX 생성 완료: {System.IO.Path.GetFileName(fullPath)} ({slideCount}슬라이드, 테마: {theme})", fullPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ToolResult.Fail("PPTX 생성 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddTextBox(ShapeTree tree, ref uint id, long x, long y, long cx, long cy, string text, int fontSize, string color, bool bold)
|
||||
{
|
||||
DocumentFormat.OpenXml.Presentation.Shape shape = new DocumentFormat.OpenXml.Presentation.Shape();
|
||||
shape.NonVisualShapeProperties = new DocumentFormat.OpenXml.Presentation.NonVisualShapeProperties(new DocumentFormat.OpenXml.Presentation.NonVisualDrawingProperties
|
||||
{
|
||||
Id = id++,
|
||||
Name = $"TextBox{id}"
|
||||
}, new DocumentFormat.OpenXml.Presentation.NonVisualShapeDrawingProperties(new ShapeLocks
|
||||
{
|
||||
NoGrouping = true
|
||||
}), new ApplicationNonVisualDrawingProperties());
|
||||
shape.ShapeProperties = new DocumentFormat.OpenXml.Presentation.ShapeProperties(new Transform2D(new Offset
|
||||
{
|
||||
X = x,
|
||||
Y = y
|
||||
}, new Extents
|
||||
{
|
||||
Cx = cx,
|
||||
Cy = cy
|
||||
}), new PresetGeometry(new AdjustValueList())
|
||||
{
|
||||
Preset = ShapeTypeValues.Rectangle
|
||||
});
|
||||
DocumentFormat.OpenXml.Presentation.TextBody textBody = new DocumentFormat.OpenXml.Presentation.TextBody(new BodyProperties
|
||||
{
|
||||
Wrap = TextWrappingValues.Square
|
||||
}, new ListStyle());
|
||||
string[] array = text.Split('\n');
|
||||
string[] array2 = array;
|
||||
foreach (string text2 in array2)
|
||||
{
|
||||
Paragraph paragraph = new Paragraph();
|
||||
Run run = new Run();
|
||||
RunProperties runProperties = new RunProperties
|
||||
{
|
||||
Language = "ko-KR",
|
||||
FontSize = fontSize,
|
||||
Dirty = false
|
||||
};
|
||||
runProperties.AppendChild(new SolidFill(new RgbColorModelHex
|
||||
{
|
||||
Val = color
|
||||
}));
|
||||
if (bold)
|
||||
{
|
||||
runProperties.Bold = true;
|
||||
}
|
||||
run.AppendChild(runProperties);
|
||||
run.AppendChild(new DocumentFormat.OpenXml.Drawing.Text(text2));
|
||||
paragraph.AppendChild(run);
|
||||
textBody.AppendChild(paragraph);
|
||||
}
|
||||
shape.TextBody = textBody;
|
||||
tree.AppendChild(shape);
|
||||
}
|
||||
|
||||
private static void AddRectangle(ShapeTree tree, ref uint id, long x, long y, long cx, long cy, string fillColor)
|
||||
{
|
||||
DocumentFormat.OpenXml.Presentation.Shape shape = new DocumentFormat.OpenXml.Presentation.Shape();
|
||||
shape.NonVisualShapeProperties = new DocumentFormat.OpenXml.Presentation.NonVisualShapeProperties(new DocumentFormat.OpenXml.Presentation.NonVisualDrawingProperties
|
||||
{
|
||||
Id = id++,
|
||||
Name = $"Rect{id}"
|
||||
}, new DocumentFormat.OpenXml.Presentation.NonVisualShapeDrawingProperties(), new ApplicationNonVisualDrawingProperties());
|
||||
shape.ShapeProperties = new DocumentFormat.OpenXml.Presentation.ShapeProperties(new Transform2D(new Offset
|
||||
{
|
||||
X = x,
|
||||
Y = y
|
||||
}, new Extents
|
||||
{
|
||||
Cx = cx,
|
||||
Cy = cy
|
||||
}), new PresetGeometry(new AdjustValueList())
|
||||
{
|
||||
Preset = ShapeTypeValues.Rectangle
|
||||
}, new SolidFill(new RgbColorModelHex
|
||||
{
|
||||
Val = fillColor
|
||||
}));
|
||||
tree.AppendChild(shape);
|
||||
}
|
||||
|
||||
private static string FormatTableAsText(JsonElement slideEl, (string Primary, string Accent, string TextDark, string TextLight, string Bg) colors)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (slideEl.TryGetProperty("headers", out var value))
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
foreach (JsonElement item in value.EnumerateArray())
|
||||
{
|
||||
list.Add(item.GetString() ?? "");
|
||||
}
|
||||
stringBuilder.AppendLine(string.Join(" | ", list));
|
||||
stringBuilder.AppendLine(new string('─', list.Sum((string h) => h.Length + 5)));
|
||||
}
|
||||
if (slideEl.TryGetProperty("rows", out var value2))
|
||||
{
|
||||
foreach (JsonElement item2 in value2.EnumerateArray())
|
||||
{
|
||||
List<string> list2 = new List<string>();
|
||||
foreach (JsonElement item3 in item2.EnumerateArray())
|
||||
{
|
||||
list2.Add(item3.GetString() ?? item3.ToString());
|
||||
}
|
||||
stringBuilder.AppendLine(string.Join(" | ", list2));
|
||||
}
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user