Files
AX-Copilot-Codex/src/AxCopilot/Handlers/LoremHandler.cs
lacvet 0336904258 AX Commander 비교본 런처 기능 대량 이식
변경 목적: Agent Compare 아래 비교본의 개발 문서와 런처 소스를 기준으로 현재 AX Commander에 빠져 있던 신규 런처 기능을 동일한 흐름으로 옮겨, 비교본 수준의 기능 폭을 현재 제품에 반영했습니다.

핵심 수정사항: 비교본의 신규 런처 핸들러 다수를 src/AxCopilot/Handlers로 이식하고 App.xaml.cs 등록 흐름에 연결했습니다. 빠른 링크, 파일 태그, 알림 센터, 포모도로, 파일 브라우저, 핫키 관리, OCR, 세션/스케줄/매크로, Git/정규식/네트워크/압축/해시/UUID/JWT/QR 등 AX Commander 기능을 추가했습니다.

핵심 수정사항: 신규 기능이 실제 동작하도록 AppSettings 확장, SchedulerService/FileTagService/NotificationCenterService/IconCacheService/UrlTemplateEngine/PomodoroService 추가, 배치 이름변경/세션/스케줄/매크로 편집 창 추가, NotificationService와 Symbols 보강, QR/OCR용 csproj 의존성과 Windows 타겟 프레임워크를 반영했습니다.

문서 반영: README.md와 docs/DEVELOPMENT.md에 비교본 기반 런처 기능 이식 이력과 검증 결과를 업데이트했습니다.

검증 결과: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 실행 기준 경고 0개, 오류 0개를 확인했습니다.
2026-04-05 00:59:45 +09:00

285 lines
13 KiB
C#

using System.Text;
using System.Windows;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;
namespace AxCopilot.Handlers;
/// <summary>
/// L10-4: Lorem Ipsum / 더미 텍스트 생성기 핸들러. "lorem" 프리픽스로 사용합니다.
///
/// 예: lorem → 1단락 생성 (기본)
/// lorem 3 → 3단락 생성
/// lorem words 20 → 단어 20개
/// lorem sentences 5 → 문장 5개
/// lorem ko → 한국어 더미 텍스트 1단락
/// lorem ko 3 → 한국어 더미 텍스트 3단락
/// lorem email 5 → 더미 이메일 주소 5개
/// lorem name 5 → 더미 이름 5개 (한국어)
/// Enter → 결과를 클립보드에 복사.
/// </summary>
public class LoremHandler : IActionHandler
{
public string? Prefix => "lorem";
public PluginMetadata Metadata => new(
"Lorem",
"더미 텍스트 생성기 — Lorem Ipsum · 한국어 · 이메일 · 이름",
"1.0",
"AX");
// ── Lorem Ipsum 단어 풀 ─────────────────────────────────────────────────
private static readonly string[] LoremWords =
[
"lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit",
"sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore",
"magna", "aliqua", "enim", "ad", "minim", "veniam", "quis", "nostrud",
"exercitation", "ullamco", "laboris", "nisi", "aliquip", "ex", "ea", "commodo",
"consequat", "duis", "aute", "irure", "in", "reprehenderit", "voluptate",
"velit", "esse", "cillum", "eu", "fugiat", "nulla", "pariatur", "excepteur",
"sint", "occaecat", "cupidatat", "non", "proident", "sunt", "culpa", "qui",
"officia", "deserunt", "mollit", "anim", "id", "est", "laborum", "perspiciatis",
"unde", "omnis", "iste", "natus", "error", "voluptatem", "accusantium",
"doloremque", "laudantium", "totam", "rem", "aperiam", "eaque", "ipsa", "quae",
"ab", "illo", "inventore", "veritatis", "quasi", "architecto", "beatae", "vitae",
"dicta", "explicabo", "nemo", "ipsam", "quia", "voluptas", "aspernatur", "aut",
"odit", "fugit", "consequuntur", "magni", "dolores", "ratione", "sequi",
"nesciunt", "neque", "porro", "quisquam", "dolorem", "numquam", "eius", "modi",
"temporibus", "incidunt", "magnam", "aliquam", "quaerat", "minima", "nostrum",
"exercitationem", "ullam", "corporis", "suscipit", "laboriosam", "nisi",
"aliquid", "commodi", "consequatur", "quidem", "rerum", "facilis",
];
// ── 한국어 더미 단어 풀 ──────────────────────────────────────────────────
private static readonly string[] KorWords =
[
"가나다라", "마바사아", "자차카타", "파하", "데이터", "처리", "시스템", "네트워크",
"소프트웨어", "알고리즘", "데이터베이스", "인터페이스", "프레임워크", "모듈", "클래스",
"메서드", "함수", "변수", "구조체", "배열", "목록", "사전", "집합", "스택", "큐",
"트리", "그래프", "정렬", "탐색", "분석", "설계", "구현", "테스트", "배포", "운영",
"서비스", "플랫폼", "클라우드", "컨테이너", "가상화", "보안", "암호화", "인증", "권한",
"로그", "모니터링", "알림", "이벤트", "트랜잭션", "세션", "쿠키", "토큰", "키",
"값", "객체", "인스턴스", "프로세스", "스레드", "비동기", "병렬", "동기화", "잠금",
"버퍼", "캐시", "인덱스", "쿼리", "뷰", "프로시저", "스키마", "테이블", "컬럼",
"행", "열", "기본키", "외래키", "조인", "집계", "필터", "정렬", "그룹화", "분류",
];
private static readonly string[] KorSentenceStarters =
[
"이 시스템은", "해당 모듈은", "기능을 구현하면", "데이터를 처리하는",
"네트워크 연결이", "서비스가 시작되면", "사용자 인터페이스는", "알고리즘이",
"처리 과정에서", "설계 단계에서", "구현 방식은", "테스트 결과",
];
private static readonly string[] KorSentenceEnders =
[
"처리됩니다.", "구현되어 있습니다.", "필요합니다.", "중요한 역할을 합니다.",
"확인할 수 있습니다.", "설계되어 있습니다.", "활용됩니다.", "반환됩니다.",
"저장됩니다.", "업데이트됩니다.", "삭제됩니다.", "초기화됩니다.",
];
// ── 더미 이름/이메일 데이터 ───────────────────────────────────────────────
private static readonly string[] KorLastNames = ["김", "이", "박", "최", "정", "강", "조", "윤", "장", "임", "한", "오", "서", "신", "권", "황", "안", "송", "류", "전"];
private static readonly string[] KorFirstNames = ["민준", "서연", "예준", "서현", "도윤", "지우", "시우", "수아", "지호", "하은", "준서", "하린", "건우", "소연", "현우", "지민", "우진", "지유", "연우", "채원"];
private static readonly string[] EmailDomains = ["example.com", "test.co.kr", "dummy.net", "sample.org", "mock.io", "placeholder.dev"];
private static readonly Random Rng = new();
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
{
var q = query.Trim();
var items = new List<LauncherItem>();
if (string.IsNullOrWhiteSpace(q))
{
var para = GenerateParagraph(false);
items.Add(new LauncherItem(
"Lorem Ipsum 1단락",
para.Length > 80 ? para[..80] + "…" : para,
null,
("copy", para),
Symbol: "\uE8BD"));
items.Add(new LauncherItem("lorem 3", "3단락 생성", null, null, Symbol: "\uE8BD"));
items.Add(new LauncherItem("lorem words 20", "단어 20개", null, null, Symbol: "\uE8BD"));
items.Add(new LauncherItem("lorem sentences 3", "문장 3개", null, null, Symbol: "\uE8BD"));
items.Add(new LauncherItem("lorem ko", "한국어 더미 텍스트", null, null, Symbol: "\uE8BD"));
items.Add(new LauncherItem("lorem email 5", "더미 이메일 5개", null, null, Symbol: "\uE8BD"));
items.Add(new LauncherItem("lorem name 5", "더미 이름 5개", null, null, Symbol: "\uE8BD"));
return Task.FromResult<IEnumerable<LauncherItem>>(items);
}
var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var sub = parts[0].ToLowerInvariant();
switch (sub)
{
case "words":
case "word":
case "w":
{
var cnt = parts.Length > 1 && int.TryParse(parts[1], out var n) ? Math.Clamp(n, 1, 500) : 20;
var text = GenerateWords(cnt, false);
items.Add(new LauncherItem(
$"단어 {cnt}개",
text.Length > 80 ? text[..80] + "…" : text,
null, ("copy", text), Symbol: "\uE8BD"));
break;
}
case "sentences":
case "sentence":
case "s":
{
var cnt = parts.Length > 1 && int.TryParse(parts[1], out var n) ? Math.Clamp(n, 1, 50) : 5;
var text = GenerateSentences(cnt, false);
items.Add(new LauncherItem(
$"문장 {cnt}개",
text.Length > 80 ? text[..80] + "…" : text,
null, ("copy", text), Symbol: "\uE8BD"));
break;
}
case "ko":
case "kor":
case "korean":
{
var cnt = parts.Length > 1 && int.TryParse(parts[1], out var n) ? Math.Clamp(n, 1, 10) : 1;
var text = GenerateParagraphs(cnt, true);
items.Add(new LauncherItem(
$"한국어 더미 텍스트 {cnt}단락",
text.Length > 80 ? text[..80] + "…" : text,
null, ("copy", text), Symbol: "\uE8BD"));
break;
}
case "email":
{
var cnt = parts.Length > 1 && int.TryParse(parts[1], out var n) ? Math.Clamp(n, 1, 20) : 5;
var emails = Enumerable.Range(0, cnt).Select(_ => GenerateEmail()).ToList();
var all = string.Join("\n", emails);
items.Add(new LauncherItem(
$"더미 이메일 {cnt}개",
"전체 복사: Enter",
null, ("copy", all), Symbol: "\uE8BD"));
foreach (var email in emails)
items.Add(new LauncherItem(email, "Enter 복사", null, ("copy", email), Symbol: "\uE8BD"));
break;
}
case "name":
case "names":
{
var cnt = parts.Length > 1 && int.TryParse(parts[1], out var n) ? Math.Clamp(n, 1, 20) : 5;
var names = Enumerable.Range(0, cnt).Select(_ => GenerateKorName()).ToList();
var all = string.Join("\n", names);
items.Add(new LauncherItem(
$"더미 이름 {cnt}개",
"전체 복사: Enter",
null, ("copy", all), Symbol: "\uE8BD"));
foreach (var name in names)
items.Add(new LauncherItem(name, "한국어 이름 · Enter 복사", null, ("copy", name), Symbol: "\uE8BD"));
break;
}
default:
{
// 숫자 단독 → 단락 수
var cnt = int.TryParse(sub, out var n) ? Math.Clamp(n, 1, 10) : 1;
var text = GenerateParagraphs(cnt, false);
items.Add(new LauncherItem(
$"Lorem Ipsum {cnt}단락",
text.Length > 80 ? text[..80] + "…" : text,
null, ("copy", text), Symbol: "\uE8BD"));
break;
}
}
return Task.FromResult<IEnumerable<LauncherItem>>(items);
}
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
{
if (item.Data is ("copy", string text))
{
try
{
System.Windows.Application.Current.Dispatcher.Invoke(
() => Clipboard.SetText(text));
NotificationService.Notify("Lorem", "클립보드에 복사했습니다.");
}
catch { /* 비핵심 */ }
}
return Task.CompletedTask;
}
// ── 생성 헬퍼 ─────────────────────────────────────────────────────────────
private static string GenerateParagraphs(int count, bool korean)
{
var paras = Enumerable.Range(0, count).Select(_ => GenerateParagraph(korean));
return string.Join("\n\n", paras);
}
private static string GenerateParagraph(bool korean)
{
var sentenceCount = Rng.Next(4, 8);
return GenerateSentences(sentenceCount, korean);
}
private static string GenerateSentences(int count, bool korean)
{
var sb = new StringBuilder();
for (var i = 0; i < count; i++)
{
if (i > 0) sb.Append(' ');
sb.Append(GenerateSentence(korean));
}
return sb.ToString();
}
private static string GenerateSentence(bool korean)
{
if (korean)
{
var starter = KorSentenceStarters[Rng.Next(KorSentenceStarters.Length)];
var wordCnt = Rng.Next(3, 8);
var words = Enumerable.Range(0, wordCnt).Select(_ => KorWords[Rng.Next(KorWords.Length)]);
var ender = KorSentenceEnders[Rng.Next(KorSentenceEnders.Length)];
return $"{starter} {string.Join(" ", words)} {ender}";
}
else
{
var wordCnt = Rng.Next(6, 15);
var words = Enumerable.Range(0, wordCnt).Select((_, idx) =>
{
var w = LoremWords[Rng.Next(LoremWords.Length)];
return idx == 0 ? char.ToUpper(w[0]) + w[1..] : w;
});
return string.Join(" ", words) + ".";
}
}
private static string GenerateWords(int count, bool korean)
{
var pool = korean ? KorWords : LoremWords;
return string.Join(" ", Enumerable.Range(0, count).Select(_ => pool[Rng.Next(pool.Length)]));
}
private static string GenerateEmail()
{
var first = LoremWords[Rng.Next(LoremWords.Length)];
var second = LoremWords[Rng.Next(LoremWords.Length)];
var num = Rng.Next(10, 999);
var domain = EmailDomains[Rng.Next(EmailDomains.Length)];
return $"{first}.{second}{num}@{domain}";
}
private static string GenerateKorName()
{
var last = KorLastNames[Rng.Next(KorLastNames.Length)];
var first = KorFirstNames[Rng.Next(KorFirstNames.Length)];
return last + first;
}
}