using System.Diagnostics;
using System.IO;
using System.Text;
using AxCopilot.Models;
namespace AxCopilot.Services;
///
/// 대화 내역을 PDF로 내보내는 서비스.
/// HTML을 생성한 후 시스템 브라우저의 인쇄 기능을 활용합니다.
///
public static class PdfExportService
{
/// 대화를 HTML 파일로 내보내고, 브라우저에서 PDF로 인쇄할 수 있도록 엽니다.
public static string ExportToPrintableHtml(ChatConversation conversation, string outputPath)
{
var html = BuildHtml(conversation);
File.WriteAllText(outputPath, html, Encoding.UTF8);
return outputPath;
}
/// 대화를 HTML로 변환합니다 (인쇄 최적화 스타일 포함).
public static string BuildHtml(ChatConversation conversation)
{
var sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine("");
sb.AppendLine("
");
sb.AppendLine("");
sb.AppendLine($"AX Copilot — {EscapeHtml(conversation.Title)}");
sb.AppendLine("");
sb.AppendLine("");
sb.AppendLine("");
// 헤더
sb.AppendLine("");
// 메시지
foreach (var msg in conversation.Messages)
{
var roleClass = msg.Role == "user" ? "user" : "assistant";
var roleLabel = msg.Role == "user" ? "사용자" : "AI";
sb.AppendLine($"");
sb.AppendLine($"
{roleLabel}
");
sb.AppendLine($"
{FormatContent(msg.Content)}
");
if (msg.Timestamp != default)
sb.AppendLine($"
{msg.Timestamp:HH:mm}
");
sb.AppendLine("
");
}
// 푸터
sb.AppendLine("");
sb.AppendLine("");
sb.AppendLine("");
return sb.ToString();
}
private static string GetPrintStyles() => @"
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Malgun Gothic', 'Segoe UI', sans-serif; font-size: 13px; color: #222; background: #fff; padding: 20px; }
.header { border-bottom: 2px solid #4B5EFC; padding-bottom: 12px; margin-bottom: 20px; }
.header h1 { font-size: 18px; font-weight: 700; color: #1a1b2e; }
.header .meta { font-size: 11px; color: #888; margin-top: 4px; }
.message { margin-bottom: 16px; padding: 12px 16px; border-radius: 8px; page-break-inside: avoid; }
.message.user { background: #f0f4ff; border-left: 3px solid #4B5EFC; }
.message.assistant { background: #fafafa; border-left: 3px solid #10B981; }
.role { font-size: 10px; font-weight: 700; color: #888; text-transform: uppercase; margin-bottom: 6px; }
.content { line-height: 1.7; white-space: pre-wrap; word-break: break-word; }
.content code { background: #f0f0f5; padding: 1px 4px; border-radius: 3px; font-family: Consolas, monospace; font-size: 12px; }
.content pre { background: #f5f5fa; padding: 10px; border-radius: 6px; overflow-x: auto; margin: 8px 0; }
.time { font-size: 10px; color: #aaa; text-align: right; margin-top: 4px; }
.footer { margin-top: 30px; padding-top: 12px; border-top: 1px solid #ddd; font-size: 10px; color: #aaa; text-align: center; }
@media print { body { padding: 0; } .message { break-inside: avoid; } }
@page { margin: 15mm; }
";
private static string FormatContent(string content)
{
if (string.IsNullOrEmpty(content)) return "";
var escaped = EscapeHtml(content);
// 코드 블록
escaped = System.Text.RegularExpressions.Regex.Replace(escaped,
@"```(\w*)\n([\s\S]*?)```",
m => $"{m.Groups[2].Value}
");
// 인라인 코드
escaped = System.Text.RegularExpressions.Regex.Replace(escaped,
@"`([^`]+)`", "$1");
// 줄바꿈
escaped = escaped.Replace("\n", "
");
return escaped;
}
private static string EscapeHtml(string text) =>
text.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """);
/// HTML 파일을 기본 브라우저에서 엽니다 (인쇄 대화상자 자동 표시).
public static void OpenInBrowser(string htmlPath)
{
try
{
Process.Start(new ProcessStartInfo(htmlPath) { UseShellExecute = true });
}
catch (Exception ex)
{
LogService.Warn($"PDF 내보내기 브라우저 열기 실패: {ex.Message}");
}
}
}