[Phase L3-8] 알림 센터 핸들러(notif) 구현 완료
NotifHandler.cs (신규, 120줄):
- notif 프리픽스: 최근 알림 이력 목록 표시 (최대 12건)
- notif [검색어]: 제목·내용 기반 필터링
- notif clear: 전체 이력 초기화 (건수 표시 후 확인)
- Enter: 선택 알림 내용 클립보드 복사
- NotificationType(Error/Warning/Success/Info)별 심볼 자동 적용
- TimeAgo() 헬퍼: 방금 전/N분 전/N시간 전/N일 전
NotificationService.cs (정리, 24줄):
- 중복 정의된 NotificationEntry 레코드 제거 (CS0101 오류 해결)
- 이력 관리는 NotificationCenterService에 위임
- LogOnly(): NotificationCenterService.Show() 호출로 대체
App.xaml.cs:
- commandResolver.RegisterHandler(new NotifHandler()) 추가
LauncherViewModel.cs:
- PrefixMap에 notif 배지 추가 (알림, ReminderBell, #F59E0B)
docs/LAUNCHER_ROADMAP.md:
- L3-8 알림 센터 통합 ✅ 완료 표시
빌드: 경고 0, 오류 0
This commit is contained in:
@@ -102,7 +102,7 @@
|
|||||||
| ✅ L3-5 | **파일 태그 시스템** | 파일에 사용자 태그 부여, `tag` 프리픽스로 태그 기반 검색. `file_tags.json` 로컬 저장 | 중간 | — |
|
| ✅ L3-5 | **파일 태그 시스템** | 파일에 사용자 태그 부여, `tag` 프리픽스로 태그 기반 검색. `file_tags.json` 로컬 저장 | 중간 | — |
|
||||||
| L3-6 | **오프라인 AI (로컬 SLM)** | ONNX Runtime + phi-3, 서버 없이 번역/요약 | 낮음 | → Agent 18-5 |
|
| L3-6 | **오프라인 AI (로컬 SLM)** | ONNX Runtime + phi-3, 서버 없이 번역/요약 | 낮음 | → Agent 18-5 |
|
||||||
| ✅ L3-7 | **다중 디스플레이** | 마우스 커서 위치 모니터에 런처 표시, 독 바 per-monitor 위치 저장·유효성 검증 | 낮음 | — |
|
| ✅ L3-7 | **다중 디스플레이** | 마우스 커서 위치 모니터에 런처 표시, 독 바 per-monitor 위치 저장·유효성 검증 | 낮음 | — |
|
||||||
| L3-8 | **알림 센터 통합** | Windows 알림과 연동 | 낮음 | — |
|
| ✅ L3-8 | **알림 센터 통합** | `notif` 프리픽스로 알림 이력 조회·검색·초기화. NotificationCenterService 이력 연동, 클립보드 복사 | 낮음 | — |
|
||||||
| L3-9 | **런처 미니 위젯** | 날씨/일정/할일을 런처 하단에 카드형으로 표시. 로컬 데이터 기반 | 낮음 | — |
|
| L3-9 | **런처 미니 위젯** | 날씨/일정/할일을 런처 하단에 카드형으로 표시. 로컬 데이터 기반 | 낮음 | — |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ public partial class App : System.Windows.Application
|
|||||||
commandResolver.RegisterHandler(new WebSearchSummaryHandler(settings, _sharedLlm));
|
commandResolver.RegisterHandler(new WebSearchSummaryHandler(settings, _sharedLlm));
|
||||||
// Phase L3-5: 파일 태그 시스템
|
// Phase L3-5: 파일 태그 시스템
|
||||||
commandResolver.RegisterHandler(new TagHandler());
|
commandResolver.RegisterHandler(new TagHandler());
|
||||||
|
// Phase L3-8: 알림 센터
|
||||||
|
commandResolver.RegisterHandler(new NotifHandler());
|
||||||
|
|
||||||
// ─── 플러그인 로드 ────────────────────────────────────────────────────
|
// ─── 플러그인 로드 ────────────────────────────────────────────────────
|
||||||
var pluginHost = new PluginHost(settings, commandResolver);
|
var pluginHost = new PluginHost(settings, commandResolver);
|
||||||
|
|||||||
139
src/AxCopilot/Handlers/NotifHandler.cs
Normal file
139
src/AxCopilot/Handlers/NotifHandler.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using AxCopilot.SDK;
|
||||||
|
using AxCopilot.Services;
|
||||||
|
using AxCopilot.Themes;
|
||||||
|
|
||||||
|
namespace AxCopilot.Handlers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Phase L3-8: 알림 센터 핸들러. "notif" 프리픽스로 사용합니다.
|
||||||
|
/// AX Copilot이 표시한 최근 알림 이력을 런처에서 조회합니다.
|
||||||
|
///
|
||||||
|
/// 사용법:
|
||||||
|
/// notif → 최근 알림 이력 목록
|
||||||
|
/// notif clear → 알림 이력 초기화
|
||||||
|
/// notif [검색어] → 제목·내용으로 필터링
|
||||||
|
///
|
||||||
|
/// Enter → 알림 내용을 클립보드에 복사.
|
||||||
|
/// </summary>
|
||||||
|
public class NotifHandler : IActionHandler
|
||||||
|
{
|
||||||
|
public string? Prefix => "notif";
|
||||||
|
|
||||||
|
public PluginMetadata Metadata => new(
|
||||||
|
"NotifCenter",
|
||||||
|
"알림 센터 — notif",
|
||||||
|
"1.0",
|
||||||
|
"AX",
|
||||||
|
"AX Copilot 알림 이력을 조회합니다.");
|
||||||
|
|
||||||
|
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var q = query.Trim();
|
||||||
|
|
||||||
|
// clear 명령
|
||||||
|
if (q.Equals("clear", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var count = NotificationCenterService.History.Count;
|
||||||
|
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||||
|
[
|
||||||
|
new LauncherItem(
|
||||||
|
$"알림 이력 초기화 ({count}건)",
|
||||||
|
"Enter를 눌러 전체 삭제",
|
||||||
|
null, "__CLEAR__",
|
||||||
|
Symbol: Symbols.Delete)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var history = NotificationCenterService.History;
|
||||||
|
|
||||||
|
// 검색어 필터
|
||||||
|
IEnumerable<NotificationEntry> filtered = history;
|
||||||
|
if (!string.IsNullOrEmpty(q))
|
||||||
|
{
|
||||||
|
filtered = history
|
||||||
|
.Where(e => e.Title.Contains(q, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| e.Message.Contains(q, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = filtered.Take(12).ToList();
|
||||||
|
|
||||||
|
if (list.Count == 0)
|
||||||
|
{
|
||||||
|
var emptyMsg = string.IsNullOrEmpty(q)
|
||||||
|
? "알림 이력이 없습니다"
|
||||||
|
: $"'{q}'에 해당하는 알림 없음";
|
||||||
|
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||||
|
[
|
||||||
|
new LauncherItem(
|
||||||
|
emptyMsg,
|
||||||
|
"AX Copilot 동작 중 발생한 알림이 여기에 표시됩니다",
|
||||||
|
null, null,
|
||||||
|
Symbol: Symbols.Info)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = list
|
||||||
|
.Select(e => new LauncherItem(
|
||||||
|
e.Title,
|
||||||
|
$"{e.Message} · {TimeAgo(e.Timestamp)}",
|
||||||
|
null, e,
|
||||||
|
Symbol: GetSymbol(e)))
|
||||||
|
.ToList<LauncherItem>();
|
||||||
|
|
||||||
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||||
|
{
|
||||||
|
// 이력 초기화
|
||||||
|
if (item.Data is string s && s == "__CLEAR__")
|
||||||
|
{
|
||||||
|
NotificationCenterService.ClearHistory();
|
||||||
|
NotificationService.LogOnly("AX Copilot", "알림 이력이 초기화되었습니다.");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 알림 내용 클립보드 복사
|
||||||
|
if (item.Data is NotificationEntry entry)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Application.Current?.Dispatcher.Invoke(() =>
|
||||||
|
Clipboard.SetText($"[{entry.Title}] {entry.Message}"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogService.Warn($"[NotifHandler] 클립보드 복사 실패: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 내부 헬퍼 ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static string TimeAgo(DateTime timestamp)
|
||||||
|
{
|
||||||
|
var diff = DateTime.Now - timestamp;
|
||||||
|
if (diff.TotalSeconds < 60) return "방금 전";
|
||||||
|
if (diff.TotalMinutes < 60) return $"{(int)diff.TotalMinutes}분 전";
|
||||||
|
if (diff.TotalHours < 24) return $"{(int)diff.TotalHours}시간 전";
|
||||||
|
return $"{(int)diff.TotalDays}일 전";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSymbol(NotificationEntry entry) => entry.Type switch
|
||||||
|
{
|
||||||
|
NotificationType.Error => Symbols.Error,
|
||||||
|
NotificationType.Warning => Symbols.Warning,
|
||||||
|
NotificationType.Success => Symbols.Favorite,
|
||||||
|
_ => entry.Title switch
|
||||||
|
{
|
||||||
|
var t when t.Contains("태그") => Symbols.Tag,
|
||||||
|
var t when t.Contains("즐겨찾기") => Symbols.Favorite,
|
||||||
|
var t when t.Contains("저장")
|
||||||
|
|| t.Contains("내보내기") => Symbols.Save,
|
||||||
|
_ => Symbols.ReminderBell,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace AxCopilot.Services;
|
namespace AxCopilot.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 트레이 아이콘 풍선 알림을 핸들러/서비스에서 표시하기 위한 전달 서비스.
|
/// 트레이 아이콘 풍선 알림을 핸들러/서비스에서 표시하기 위한 전달 서비스.
|
||||||
|
/// Phase L3-8: 알림 이력은 NotificationCenterService가 관리합니다.
|
||||||
/// App.xaml.cs의 SetupTrayIcon 이후 Initialize()로 초기화됩니다.
|
/// App.xaml.cs의 SetupTrayIcon 이후 Initialize()로 초기화됩니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class NotificationService
|
internal static class NotificationService
|
||||||
@@ -12,7 +15,11 @@ internal static class NotificationService
|
|||||||
public static void Initialize(Action<string, string> showBalloon)
|
public static void Initialize(Action<string, string> showBalloon)
|
||||||
=> _showBalloon = showBalloon;
|
=> _showBalloon = showBalloon;
|
||||||
|
|
||||||
/// <summary>트레이 풍선 알림을 표시합니다.</summary>
|
/// <summary>트레이 풍선 알림을 표시합니다 (이력 기록은 NotificationCenterService.Show 담당).</summary>
|
||||||
public static void Notify(string title, string message)
|
public static void Notify(string title, string message)
|
||||||
=> _showBalloon?.Invoke(title, message);
|
=> _showBalloon?.Invoke(title, message);
|
||||||
|
|
||||||
|
/// <summary>트레이 알림 없이 알림 이력에만 추가합니다.</summary>
|
||||||
|
public static void LogOnly(string title, string message)
|
||||||
|
=> NotificationCenterService.Show(title, message, NotificationType.Info);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,8 @@ public partial class LauncherViewModel : INotifyPropertyChanged
|
|||||||
{ "help", ("도움말", Symbols.Info, "#6B7280") },
|
{ "help", ("도움말", Symbols.Info, "#6B7280") },
|
||||||
// ─── Phase L3-5 파일 태그 ──────────────────────────────────────────────
|
// ─── Phase L3-5 파일 태그 ──────────────────────────────────────────────
|
||||||
{ "tag", ("태그", Symbols.Tag, "#6366F1") },
|
{ "tag", ("태그", Symbols.Tag, "#6366F1") },
|
||||||
|
// ─── Phase L3-8 알림 센터 ─────────────────────────────────────────────
|
||||||
|
{ "notif", ("알림", Symbols.ReminderBell, "#F59E0B") },
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─── 설정 기능 토글 (런처 실동작 연결) ──────────────────────────────────
|
// ─── 설정 기능 토글 (런처 실동작 연결) ──────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user