1
0
Fork 0
Everything2Everything/src/Everything2Everything.App/Views/SettingsWindow.xaml
Yun Chan 6a41c4a0dc feat(tools): add first-run external tool installer wizard and catalog
첫 실행 시 외부 도구 설치 마법사를 띄우고 7종(FFmpeg·LibreOffice·Pandoc·ImageMagick·H2Orestart·agy·Codex)을 기본 전체 선택 상태로 순차 설치한다.
- Core: ExternalToolCatalog(정적 카탈로그, winget ID 실검증), ExternalToolInstaller(winget 무인 실행·재감지 판정, IExternalCommandRunner 시드로 테스트 가능)
- Core: ExternalToolDetector에 winget FFmpeg 스캔 + Pandoc/ImageMagick 감지 추가
- App: ToolSetupWindow + ToolSetupViewModel, 첫 실행 플래그(setup.tools.done), 설정 창 '외부 도구 설치/검증' 재진입 버튼
- Tests: ExternalToolInstallerTests 5건
- 기존 미커밋 작업(광고 Ads 지원: AdBannerControl/AdLargeCardControl/AdService, AI 백엔드 agy·switchboard·codex 채팅 클라이언트, 관련 테스트) 포함
2026-09-07 21:16:49 +09:00

212 lines
16 KiB
XML

<ui:FluentWindow x:Class="Everything2Everything.App.Views.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Title="설정" Width="560" Height="720"
WindowStartupLocation="CenterOwner"
ExtendsContentIntoTitleBar="True"
WindowBackdropType="Mica"
Background="{DynamicResource FsBgBase}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ControlsDictionary/>
<ResourceDictionary Source="FormatShiftTheme.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid Background="{StaticResource FsBgBase}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ui:TitleBar Grid.Row="0" Title="설정" ShowMaximize="False" ShowMinimize="False"/>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" Padding="24,8,24,16">
<StackPanel>
<!-- ===== AI 카드 ===== -->
<Border Style="{StaticResource FsDoubleBezelShellStyle}" Margin="0,0,0,16">
<Border Style="{StaticResource FsDoubleBezelCoreStyle}" Padding="20">
<StackPanel>
<TextBlock Text="AI 텍스트 변환" Style="{StaticResource FsBodyStyle}" FontWeight="SemiBold" FontSize="15"/>
<TextBlock Text="요약 · 번역 · 교정에 사용됩니다 (종량 과금 · 네트워크 필요). 키가 없으면 AI 변환만 비활성됩니다."
Style="{StaticResource FsCaptionStyle}" TextWrapping="Wrap" Margin="0,4,0,16"/>
<TextBlock Text="기본 백엔드" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,6"/>
<ComboBox x:Name="BackendCombo" Margin="0,0,0,16">
<ComboBoxItem Content="자동 (API 키 우선 · Switchboard · Antigravity · Codex)"/>
<ComboBoxItem Content="Switchboard 게이트웨이 (http://127.0.0.1:8787 · 로컬/LAN)"/>
<ComboBoxItem Content="Antigravity CLI (agy · Gemini OAuth · 무료/구독)"/>
<ComboBoxItem Content="OpenAI (API 키)"/>
<ComboBoxItem Content="Anthropic (API 키)"/>
<ComboBoxItem Content="Codex CLI (ChatGPT 구독 OAuth)"/>
</ComboBox>
<!-- Switchboard Gateway -->
<TextBlock Text="Switchboard 게이트웨이 (로컬/LAN 멀티에이전트 오케스트레이션)" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,6"/>
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="SwitchboardEndpointBox" Grid.Column="0"
Style="{StaticResource FsPathInputStyle}"
TextChanged="OnSwitchboardEndpointChanged"/>
<Button x:Name="SwitchboardVerifyBtn" Grid.Column="1" Content="테스트"
Style="{StaticResource FsSecondaryButtonStyle}" Margin="8,0,0,0"
Padding="12,6" Click="OnVerifySwitchboard"/>
<Button x:Name="SwitchboardStartBtn" Grid.Column="2" Content="게이트웨이 기동"
Style="{StaticResource FsSecondaryButtonStyle}" Margin="8,0,0,0"
Padding="12,6" Click="OnStartSwitchboardGateway"/>
</Grid>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,0,16">
<Ellipse x:Name="SwitchboardDot" Style="{StaticResource FsLossDotStyle}"
Fill="{StaticResource FsTextTertiary}" Margin="0,0,7,0" VerticalAlignment="Center"/>
<TextBlock x:Name="SwitchboardStatus" Text="확인 중…" Style="{StaticResource FsCaptionStyle}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
</StackPanel>
<!-- Antigravity CLI (OAuth) -->
<TextBlock Text="Antigravity CLI (agy · Google OAuth · API 키 불필요)" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,6"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,0,16">
<Ellipse x:Name="AgyDot" Style="{StaticResource FsLossDotStyle}"
Fill="{StaticResource FsTextTertiary}" Margin="0,0,7,0" VerticalAlignment="Center"/>
<TextBlock x:Name="AgyStatus" Text="확인 중…" Style="{StaticResource FsCaptionStyle}" VerticalAlignment="Center"/>
<Button x:Name="AgyVerifyBtn" Content="테스트" Style="{StaticResource FsSecondaryButtonStyle}"
Margin="12,0,0,0" Padding="10,4" IsEnabled="False" Click="OnVerifyAgy" VerticalAlignment="Center"/>
</StackPanel>
<!-- OpenAI -->
<TextBlock Text="OpenAI API Key" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,6"/>
<Grid Margin="0,0,0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<PasswordBox x:Name="OpenAiKeyBox" Grid.Column="0"
Style="{StaticResource FsPasswordInputStyle}"
PasswordChanged="OnOpenAiKeyChanged"/>
<Button x:Name="OpenAiVerifyBtn" Grid.Column="1" Content="확인"
Style="{StaticResource FsSecondaryButtonStyle}" Margin="8,0,0,0"
IsEnabled="False" Click="OnVerifyOpenAi"/>
</Grid>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,0,16">
<Ellipse x:Name="OpenAiDot" Style="{StaticResource FsLossDotStyle}"
Fill="{StaticResource FsTextTertiary}" Margin="0,0,7,0" VerticalAlignment="Center"/>
<TextBlock x:Name="OpenAiStatus" Text="키 미설정" Style="{StaticResource FsCaptionStyle}" VerticalAlignment="Center"/>
</StackPanel>
<!-- Anthropic -->
<TextBlock Text="Anthropic API Key" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,6"/>
<Grid Margin="0,0,0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<PasswordBox x:Name="AnthropicKeyBox" Grid.Column="0"
Style="{StaticResource FsPasswordInputStyle}"
PasswordChanged="OnAnthropicKeyChanged"/>
<Button x:Name="AnthropicVerifyBtn" Grid.Column="1" Content="확인"
Style="{StaticResource FsSecondaryButtonStyle}" Margin="8,0,0,0"
IsEnabled="False" Click="OnVerifyAnthropic"/>
</Grid>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,0,16">
<Ellipse x:Name="AnthropicDot" Style="{StaticResource FsLossDotStyle}"
Fill="{StaticResource FsTextTertiary}" Margin="0,0,7,0" VerticalAlignment="Center"/>
<TextBlock x:Name="AnthropicStatus" Text="키 미설정" Style="{StaticResource FsCaptionStyle}" VerticalAlignment="Center"/>
</StackPanel>
<!-- Codex CLI (OAuth) -->
<TextBlock Text="Codex CLI (OAuth · API 키 불필요)" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,6"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,0,16">
<Ellipse x:Name="CodexDot" Style="{StaticResource FsLossDotStyle}"
Fill="{StaticResource FsTextTertiary}" Margin="0,0,7,0" VerticalAlignment="Center"/>
<TextBlock x:Name="CodexStatus" Text="확인 중…" Style="{StaticResource FsCaptionStyle}" VerticalAlignment="Center"/>
<Button x:Name="CodexVerifyBtn" Content="테스트" Style="{StaticResource FsSecondaryButtonStyle}"
Margin="12,0,0,0" Padding="10,4" IsEnabled="False" Click="OnVerifyCodex" VerticalAlignment="Center"/>
</StackPanel>
<!-- 모델 -->
<TextBlock Text="모델 (선택)" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,6"/>
<TextBox x:Name="ModelBox" Style="{StaticResource FsPathInputStyle}"/>
<TextBlock Text="비워두면 기본 모델 (OpenAI: gpt-4o-mini, Anthropic: claude-3-5-sonnet-latest)"
Style="{StaticResource FsCaptionStyle}" Margin="0,6,0,0"/>
</StackPanel>
</Border>
</Border>
<!-- ===== 외부 도구 카드 ===== -->
<Border Style="{StaticResource FsDoubleBezelShellStyle}">
<Border Style="{StaticResource FsDoubleBezelCoreStyle}" Padding="20">
<StackPanel>
<TextBlock Text="외부 도구" Style="{StaticResource FsBodyStyle}" FontWeight="SemiBold" FontSize="15"/>
<TextBlock Text="설치하면 영상/오디오·한글/Word 변환이 자동 활성화됩니다."
Style="{StaticResource FsCaptionStyle}" TextWrapping="Wrap" Margin="0,4,0,12"/>
<Button Content="외부 도구 설치 / 검증" Style="{StaticResource FsSecondaryButtonStyle}"
Padding="14,6" HorizontalAlignment="Right" Margin="0,0,0,12"
Click="OnOpenToolInstaller"/>
<CheckBox x:Name="GpuToggle" Content="영상 변환 시 GPU 가속(NVENC) 시도 — 없으면 CPU 자동 전환"
Foreground="{StaticResource FsTextSecondary}" IsChecked="True" Margin="0,0,0,16"/>
<!-- FFmpeg -->
<Grid Margin="0,0,0,14">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Ellipse x:Name="FfmpegDot" Style="{StaticResource FsLossDotStyle}"
Fill="{StaticResource FsStatusWarn}" Margin="0,0,7,0" VerticalAlignment="Center"/>
<TextBlock Text="FFmpeg" Style="{StaticResource FsBodyStyle}" FontWeight="Medium" VerticalAlignment="Center"/>
<TextBlock x:Name="FfmpegStatus" Text="미설치" Style="{StaticResource FsCaptionStyle}" Margin="8,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Text="영상/오디오 변환 (mp4·webm·mp3·flac…)" Style="{StaticResource FsCaptionStyle}" Margin="15,3,0,0"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
<Button Content="다운로드" Style="{StaticResource FsSecondaryButtonStyle}" Click="OnDownloadFfmpeg"/>
<Button Content="폴더 열기" Style="{StaticResource FsSecondaryButtonStyle}" Margin="6,0,0,0" Click="OnOpenFfmpegFolder"/>
</StackPanel>
</Grid>
<!-- LibreOffice -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Ellipse x:Name="LibreDot" Style="{StaticResource FsLossDotStyle}"
Fill="{StaticResource FsStatusWarn}" Margin="0,0,7,0" VerticalAlignment="Center"/>
<TextBlock Text="LibreOffice" Style="{StaticResource FsBodyStyle}" FontWeight="Medium" VerticalAlignment="Center"/>
<TextBlock x:Name="LibreStatus" Text="미설치" Style="{StaticResource FsCaptionStyle}" Margin="8,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Text="한글/Word/문서 변환 (HWP·DOCX→PDF/이미지). 한글은 H2Orestart 확장 필요." Style="{StaticResource FsCaptionStyle}" Margin="15,3,0,0" TextWrapping="Wrap"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
<Button Content="다운로드" Style="{StaticResource FsSecondaryButtonStyle}" Click="OnDownloadLibre"/>
</StackPanel>
</Grid>
</StackPanel>
</Border>
</Border>
</StackPanel>
</ScrollViewer>
<!-- ===== 푸터 ===== -->
<Border Grid.Row="2" Background="{StaticResource FsBgPanel}"
BorderBrush="{StaticResource FsBorderSubtle}" BorderThickness="0,1,0,0" Padding="24,14">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button Content="닫기" Style="{StaticResource FsSecondaryButtonStyle}" Padding="20,8" Click="OnClose"/>
<Button Content="저장" Style="{StaticResource FsPrimaryButtonStyle}" Padding="20,8" Margin="8,0,0,0" Click="OnSave"/>
</StackPanel>
</Border>
</Grid>
</ui:FluentWindow>