144 lines
6.7 KiB
C#
144 lines
6.7 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using AxCopilot.Models;
|
|
|
|
namespace AxCopilot.Services;
|
|
|
|
public static class PdfExportService
|
|
{
|
|
public static string ExportToPrintableHtml(ChatConversation conversation, string outputPath)
|
|
{
|
|
string contents = BuildHtml(conversation);
|
|
File.WriteAllText(outputPath, contents, Encoding.UTF8);
|
|
return outputPath;
|
|
}
|
|
|
|
public static string BuildHtml(ChatConversation conversation)
|
|
{
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
stringBuilder.AppendLine("<!DOCTYPE html>");
|
|
stringBuilder.AppendLine("<html lang=\"ko\">");
|
|
stringBuilder.AppendLine("<head>");
|
|
stringBuilder.AppendLine("<meta charset=\"utf-8\">");
|
|
StringBuilder stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder3 = stringBuilder2;
|
|
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(28, 1, stringBuilder2);
|
|
handler.AppendLiteral("<title>AX Copilot — ");
|
|
handler.AppendFormatted(EscapeHtml(conversation.Title));
|
|
handler.AppendLiteral("</title>");
|
|
stringBuilder3.AppendLine(ref handler);
|
|
stringBuilder.AppendLine("<style>");
|
|
stringBuilder.AppendLine(GetPrintStyles());
|
|
stringBuilder.AppendLine("</style>");
|
|
stringBuilder.AppendLine("</head>");
|
|
stringBuilder.AppendLine("<body>");
|
|
stringBuilder.AppendLine("<div class=\"header\">");
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder4 = stringBuilder2;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
|
|
handler.AppendLiteral("<h1>");
|
|
handler.AppendFormatted(EscapeHtml(conversation.Title));
|
|
handler.AppendLiteral("</h1>");
|
|
stringBuilder4.AppendLine(ref handler);
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder5 = stringBuilder2;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(32, 2, stringBuilder2);
|
|
handler.AppendLiteral("<div class=\"meta\">");
|
|
handler.AppendFormatted(conversation.CreatedAt, "yyyy-MM-dd HH:mm");
|
|
handler.AppendLiteral(" · ");
|
|
handler.AppendFormatted(conversation.Messages.Count);
|
|
handler.AppendLiteral("개 메시지</div>");
|
|
stringBuilder5.AppendLine(ref handler);
|
|
stringBuilder.AppendLine("</div>");
|
|
foreach (ChatMessage message in conversation.Messages)
|
|
{
|
|
string value = ((message.Role == "user") ? "user" : "assistant");
|
|
string value2 = ((message.Role == "user") ? "사용자" : "AI");
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder6 = stringBuilder2;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(22, 1, stringBuilder2);
|
|
handler.AppendLiteral("<div class=\"message ");
|
|
handler.AppendFormatted(value);
|
|
handler.AppendLiteral("\">");
|
|
stringBuilder6.AppendLine(ref handler);
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder7 = stringBuilder2;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(24, 1, stringBuilder2);
|
|
handler.AppendLiteral("<div class=\"role\">");
|
|
handler.AppendFormatted(value2);
|
|
handler.AppendLiteral("</div>");
|
|
stringBuilder7.AppendLine(ref handler);
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder8 = stringBuilder2;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(27, 1, stringBuilder2);
|
|
handler.AppendLiteral("<div class=\"content\">");
|
|
handler.AppendFormatted(FormatContent(message.Content));
|
|
handler.AppendLiteral("</div>");
|
|
stringBuilder8.AppendLine(ref handler);
|
|
if (message.Timestamp != default(DateTime))
|
|
{
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder9 = stringBuilder2;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(24, 1, stringBuilder2);
|
|
handler.AppendLiteral("<div class=\"time\">");
|
|
handler.AppendFormatted(message.Timestamp, "HH:mm");
|
|
handler.AppendLiteral("</div>");
|
|
stringBuilder9.AppendLine(ref handler);
|
|
}
|
|
stringBuilder.AppendLine("</div>");
|
|
}
|
|
stringBuilder.AppendLine("<div class=\"footer\">");
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder10 = stringBuilder2;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(22, 1, stringBuilder2);
|
|
handler.AppendLiteral("AX Copilot · 내보내기 일시: ");
|
|
handler.AppendFormatted(DateTime.Now, "yyyy-MM-dd HH:mm");
|
|
stringBuilder10.AppendLine(ref handler);
|
|
stringBuilder.AppendLine("</div>");
|
|
stringBuilder.AppendLine("<script>window.onload = function() { window.print(); };</script>");
|
|
stringBuilder.AppendLine("</body></html>");
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
private static string GetPrintStyles()
|
|
{
|
|
return "\n* { box-sizing: border-box; margin: 0; padding: 0; }\nbody { font-family: 'Malgun Gothic', 'Segoe UI', sans-serif; font-size: 13px; color: #222; background: #fff; padding: 20px; }\n.header { border-bottom: 2px solid #4B5EFC; padding-bottom: 12px; margin-bottom: 20px; }\n.header h1 { font-size: 18px; font-weight: 700; color: #1a1b2e; }\n.header .meta { font-size: 11px; color: #888; margin-top: 4px; }\n.message { margin-bottom: 16px; padding: 12px 16px; border-radius: 8px; page-break-inside: avoid; }\n.message.user { background: #f0f4ff; border-left: 3px solid #4B5EFC; }\n.message.assistant { background: #fafafa; border-left: 3px solid #10B981; }\n.role { font-size: 10px; font-weight: 700; color: #888; text-transform: uppercase; margin-bottom: 6px; }\n.content { line-height: 1.7; white-space: pre-wrap; word-break: break-word; }\n.content code { background: #f0f0f5; padding: 1px 4px; border-radius: 3px; font-family: Consolas, monospace; font-size: 12px; }\n.content pre { background: #f5f5fa; padding: 10px; border-radius: 6px; overflow-x: auto; margin: 8px 0; }\n.time { font-size: 10px; color: #aaa; text-align: right; margin-top: 4px; }\n.footer { margin-top: 30px; padding-top: 12px; border-top: 1px solid #ddd; font-size: 10px; color: #aaa; text-align: center; }\n@media print { body { padding: 0; } .message { break-inside: avoid; } }\n@page { margin: 15mm; }\n";
|
|
}
|
|
|
|
private static string FormatContent(string content)
|
|
{
|
|
if (string.IsNullOrEmpty(content))
|
|
{
|
|
return "";
|
|
}
|
|
string input = EscapeHtml(content);
|
|
input = Regex.Replace(input, "```(\\w*)\\n([\\s\\S]*?)```", (Match m) => "<pre><code>" + m.Groups[2].Value + "</code></pre>");
|
|
input = Regex.Replace(input, "`([^`]+)`", "<code>$1</code>");
|
|
return input.Replace("\n", "<br>");
|
|
}
|
|
|
|
private static string EscapeHtml(string text)
|
|
{
|
|
return text.Replace("&", "&").Replace("<", "<").Replace(">", ">")
|
|
.Replace("\"", """);
|
|
}
|
|
|
|
public static void OpenInBrowser(string htmlPath)
|
|
{
|
|
try
|
|
{
|
|
Process.Start(new ProcessStartInfo(htmlPath)
|
|
{
|
|
UseShellExecute = true
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogService.Warn("PDF 내보내기 브라우저 열기 실패: " + ex.Message);
|
|
}
|
|
}
|
|
}
|