diff --git a/src/AxCopilot/Models/QuickActionChip.cs b/src/AxCopilot/Models/QuickActionChip.cs
new file mode 100644
index 0000000..0c3cac9
--- /dev/null
+++ b/src/AxCopilot/Models/QuickActionChip.cs
@@ -0,0 +1,14 @@
+using System.Windows.Media;
+
+namespace AxCopilot.Models;
+
+///
+/// 퀵 액션 바에 표시되는 최근 실행 항목 칩 모델.
+/// 입력창 아래에 가로 칩 형태로 표시되며 클릭 시 즉시 실행됩니다.
+///
+public record QuickActionChip(
+ string Title,
+ string Symbol,
+ string Path,
+ Brush Background
+);
diff --git a/src/AxCopilot/Services/UsageRankingService.cs b/src/AxCopilot/Services/UsageRankingService.cs
index 5bdaffe..3965d3f 100644
--- a/src/AxCopilot/Services/UsageRankingService.cs
+++ b/src/AxCopilot/Services/UsageRankingService.cs
@@ -46,6 +46,21 @@ internal static class UsageRankingService
}
}
+ ///
+ /// 실행 횟수 기준 상위 N개의 (경로, 횟수) 목록을 반환합니다.
+ /// 퀵 액션 바 표시에 활용됩니다.
+ ///
+ public static IReadOnlyList<(string Path, int Count)> GetTopItems(int n)
+ {
+ EnsureLoaded();
+ lock (_lock)
+ return _counts
+ .OrderByDescending(kv => kv.Value)
+ .Take(n)
+ .Select(kv => (kv.Key, kv.Value))
+ .ToList();
+ }
+
///
/// 실행 횟수 기준으로 내림차순 정렬하는 컴파러를 반환합니다.
/// 동점이면 원래 순서 유지 (stable sort).
diff --git a/src/AxCopilot/ViewModels/LauncherViewModel.cs b/src/AxCopilot/ViewModels/LauncherViewModel.cs
index 553f3f8..42e5e48 100644
--- a/src/AxCopilot/ViewModels/LauncherViewModel.cs
+++ b/src/AxCopilot/ViewModels/LauncherViewModel.cs
@@ -48,6 +48,15 @@ public partial class LauncherViewModel : INotifyPropertyChanged
///
public ICollectionView GroupedResults { get; }
+ ///
+ /// 퀵 액션 바 — 가장 많이 실행한 항목을 입력창 아래 칩으로 표시합니다.
+ /// 입력창이 비어 있을 때만 표시됩니다.
+ ///
+ public ObservableCollection QuickActionItems { get; } = new();
+
+ /// 퀵 액션 칩 표시 조건: 입력이 없고 칩이 하나 이상 있을 때
+ public bool ShowQuickActions => string.IsNullOrEmpty(_inputText) && QuickActionItems.Count > 0;
+
// ─── 기본 프로퍼티 ────────────────────────────────────────────────────────
public string InputText
@@ -66,6 +75,8 @@ public partial class LauncherViewModel : INotifyPropertyChanged
OnPropertyChanged(nameof(ShowMergeHint));
OnPropertyChanged(nameof(MergeHintText));
+ OnPropertyChanged(nameof(ShowQuickActions));
+
// 연속 입력 시 이전 검색 즉시 취소 + 50ms 디바운스 후 실제 검색 시작
_searchCts?.Cancel();
_debounceTimer?.Dispose();
@@ -266,6 +277,47 @@ public partial class LauncherViewModel : INotifyPropertyChanged
Results.Clear();
_lastSearchQuery = "";
ClearMerge();
+ LoadQuickActions();
+ }
+
+ ///
+ /// UsageRankingService 상위 항목에서 퀵 액션 칩을 생성합니다.
+ /// 실제로 존재하는 파일/폴더만 표시하며 최대 8개로 제한합니다.
+ ///
+ public void LoadQuickActions()
+ {
+ QuickActionItems.Clear();
+ var topItems = UsageRankingService.GetTopItems(16); // 여유분 확보 (일부 경로가 없을 수 있음)
+ var added = 0;
+ foreach (var (path, _) in topItems)
+ {
+ if (added >= 8) break;
+ var expanded = Environment.ExpandEnvironmentVariables(path);
+ var isFolder = Directory.Exists(expanded);
+ var isFile = !isFolder && File.Exists(expanded);
+ if (!isFolder && !isFile) continue; // 삭제된 항목 건너뜀
+
+ var ext = Path.GetExtension(expanded).ToLowerInvariant();
+ var title = Path.GetFileNameWithoutExtension(expanded);
+ if (string.IsNullOrEmpty(title)) title = Path.GetFileName(expanded);
+
+ var symbol = isFolder ? Symbols.Folder
+ : ext == ".exe" ? Symbols.App
+ : ext is ".lnk"
+ or ".url" ? Symbols.App
+ : Symbols.File;
+
+ var color = isFolder ? Color.FromRgb(0x10, 0x7C, 0x10)
+ : ext == ".exe" ? Color.FromRgb(0x4B, 0x5E, 0xFC)
+ : ext is ".lnk"
+ or ".url" ? Color.FromRgb(0x4B, 0x5E, 0xFC)
+ : Color.FromRgb(0x5B, 0x4E, 0x7E);
+
+ var bg = new SolidColorBrush(Color.FromArgb(0x26, color.R, color.G, color.B));
+ QuickActionItems.Add(new QuickActionChip(title, symbol, path, bg));
+ added++;
+ }
+ OnPropertyChanged(nameof(ShowQuickActions));
}
// ─── 검색 ────────────────────────────────────────────────────────────────
diff --git a/src/AxCopilot/Views/LauncherWindow.Shell.cs b/src/AxCopilot/Views/LauncherWindow.Shell.cs
index 08dbc80..358f63d 100644
--- a/src/AxCopilot/Views/LauncherWindow.Shell.cs
+++ b/src/AxCopilot/Views/LauncherWindow.Shell.cs
@@ -163,6 +163,53 @@ public partial class LauncherWindow
_ = _vm.ExecuteSelectedAsync();
}
+ // ─── F3 QuickLook 토글 ────────────────────────────────────────────────
+
+ /// 현재 열린 QuickLook 창 참조 (토글/닫기 추적용)
+ private QuickLookWindow? _quickLookWindow;
+
+ ///
+ /// F3 빠른 미리보기 토글.
+ /// 이미 열려 있으면 닫고, 없으면 선택된 파일/폴더로 미리보기 창을 엽니다.
+ ///
+ internal void ToggleQuickLook()
+ {
+ // 이미 열린 창이 있으면 닫기 (토글)
+ if (_quickLookWindow != null)
+ {
+ _quickLookWindow.Close();
+ _quickLookWindow = null;
+ return;
+ }
+
+ var selected = _vm.SelectedItem;
+ string? path = null;
+
+ if (selected?.Data is Services.IndexEntry indexEntry)
+ path = Environment.ExpandEnvironmentVariables(indexEntry.Path);
+
+ if (string.IsNullOrEmpty(path)) return;
+ if (!System.IO.File.Exists(path) && !System.IO.Directory.Exists(path)) return;
+
+ // 런처 창 오른쪽에 배치 (화면 경계 고려)
+ var qlLeft = Left + ActualWidth + 8;
+ var qlTop = Top;
+
+ // 화면 오른쪽 여백 확인 (화면 밖으로 나가면 왼쪽에 배치)
+ var screen = System.Windows.Forms.Screen.FromHandle(
+ new System.Windows.Interop.WindowInteropHelper(this).Handle);
+ if (qlLeft + 400 > screen.WorkingArea.Right)
+ qlLeft = Left - 408;
+
+ _quickLookWindow = new QuickLookWindow(path, this)
+ {
+ Left = qlLeft,
+ Top = qlTop
+ };
+ _quickLookWindow.Closed += (_, _) => _quickLookWindow = null;
+ _quickLookWindow.Show();
+ }
+
// ─── 창 이벤트 / 스크롤 / 알림 ─────────────────────────────────────────
private void Window_Deactivated(object sender, EventArgs e)
@@ -171,6 +218,16 @@ public partial class LauncherWindow
if (_vm.CloseOnFocusLost) Hide();
}
+ private void Window_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
+ {
+ // 런처 숨김 시 QuickLook도 함께 닫기
+ if (!(bool)e.NewValue)
+ {
+ _quickLookWindow?.Close();
+ _quickLookWindow = null;
+ }
+ }
+
private void ScrollToSelected()
{
if (_vm.SelectedItem != null)
@@ -182,4 +239,33 @@ public partial class LauncherWindow
// 시스템 트레이 토스트 알림 표시
// App.xaml.cs의 TrayIcon을 통해 처리
}
+
+ // ─── 퀵 액션 칩 클릭 ──────────────────────────────────────────────────────
+
+ ///
+ /// 입력창 아래 최근 실행 칩 클릭 시 — 경로를 즉시 실행하고 런처를 닫습니다.
+ ///
+ private async void QuickActionChip_Click(object sender, MouseButtonEventArgs e)
+ {
+ if (((System.Windows.FrameworkElement)sender).DataContext
+ is not Models.QuickActionChip chip) return;
+
+ var expanded = Environment.ExpandEnvironmentVariables(chip.Path);
+ Hide(); // 창 먼저 숨겨 체감 속도 확보
+
+ try
+ {
+ await Task.Run(() =>
+ System.Diagnostics.Process.Start(
+ new System.Diagnostics.ProcessStartInfo(expanded)
+ { UseShellExecute = true }));
+ }
+ catch (Exception ex)
+ {
+ Services.LogService.Error($"퀵 액션 실행 실패: {expanded} - {ex.Message}");
+ }
+
+ // 사용 통계 기록 (비동기)
+ _ = Task.Run(() => Services.UsageRankingService.RecordExecution(chip.Path));
+ }
}
diff --git a/src/AxCopilot/Views/LauncherWindow.xaml b/src/AxCopilot/Views/LauncherWindow.xaml
index 70e31c1..1b3ea24 100644
--- a/src/AxCopilot/Views/LauncherWindow.xaml
+++ b/src/AxCopilot/Views/LauncherWindow.xaml
@@ -207,6 +207,10 @@
+
+
+
+
@@ -417,6 +421,55 @@
VerticalAlignment="Center"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+