사용자 원래 요구였던 Codex non-interactive OAuth를 실제 동작까지 검증해 통합. ChatGPT 구독으로 API 키 없이 AI 변환. - CodexChatClient: codex exec --skip-git-repo-check --ephemeral -o <file> - (stdin 프롬프트). 실제 한글→영어 번역 검증 완료 - ExternalProcessRunner: stdin 지원 + UTF-8 인코딩(한글 깨짐 해결) - ExternalToolDetector.IsCodexAvailable() + public 승격 - LlmProvider: codex 백엔드 + auto 폴백(키 없으면 Codex) - SettingsWindow: OpenAI/Anthropic 키(2단계 Verify) + Codex(OAuth) + 외부도구 상태/다운로드 CTA + 모델 선택 - App.Settings를 LlmProvider와 공유(키 저장 즉시 반영) - MainWindow 네비바 ⚙ 설정 진입점 - 디자인: Cursor/Zed/Raycast 설정 UX + Radix 상태 토큰 영감
146 lines
5.1 KiB
C#
146 lines
5.1 KiB
C#
using Microsoft.Win32;
|
|
|
|
namespace Everything2Everything.Core.Converters;
|
|
|
|
public static class ExternalToolDetector
|
|
{
|
|
public static bool TryFindLibreOfficeSoffice(out string sofficePath)
|
|
{
|
|
sofficePath = "";
|
|
var candidates = new List<string>();
|
|
|
|
var pf = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
|
var pfx86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
|
|
foreach (var root in new[] { pf, pfx86 })
|
|
{
|
|
if (string.IsNullOrEmpty(root)) continue;
|
|
candidates.Add(Path.Combine(root, "LibreOffice", "program", "soffice.com"));
|
|
candidates.Add(Path.Combine(root, "LibreOffice", "program", "soffice.exe"));
|
|
}
|
|
|
|
try
|
|
{
|
|
using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\LibreOffice\UNO\InstallPath");
|
|
if (key?.GetValue(null) is string installPath)
|
|
{
|
|
candidates.Add(Path.Combine(installPath, "soffice.com"));
|
|
candidates.Add(Path.Combine(installPath, "soffice.exe"));
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
foreach (var path in candidates.Distinct())
|
|
{
|
|
if (File.Exists(path)) { sofficePath = path; return true; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static bool IsWordComAvailable()
|
|
{
|
|
try
|
|
{
|
|
using var key = Registry.ClassesRoot.OpenSubKey("Word.Application");
|
|
return key is not null;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// FFmpeg/ffprobe 바이너리 폴더를 찾는다. (1) 앱 전용 폴더(%LOCALAPPDATA%\Everything2Everything\ffmpeg),
|
|
/// (2) 시스템 PATH 순. ffmpeg.exe와 ffprobe.exe가 모두 있는 폴더만 유효.
|
|
/// </summary>
|
|
public static bool TryFindFfmpeg(out string ffmpegDirectory)
|
|
{
|
|
ffmpegDirectory = "";
|
|
var candidates = new List<string>();
|
|
|
|
var local = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
|
if (!string.IsNullOrEmpty(local))
|
|
candidates.Add(Path.Combine(local, "Everything2Everything", "ffmpeg"));
|
|
|
|
var pathEnv = Environment.GetEnvironmentVariable("PATH") ?? "";
|
|
foreach (var dir in pathEnv.Split(Path.PathSeparator))
|
|
if (!string.IsNullOrWhiteSpace(dir))
|
|
candidates.Add(dir.Trim());
|
|
|
|
foreach (var dir in candidates.Distinct())
|
|
{
|
|
try
|
|
{
|
|
if (File.Exists(Path.Combine(dir, "ffmpeg.exe")) && File.Exists(Path.Combine(dir, "ffprobe.exe")))
|
|
{
|
|
ffmpegDirectory = dir;
|
|
return true;
|
|
}
|
|
}
|
|
catch { /* 잘못된 경로 무시 */ }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// codex CLI(OpenAI Codex, ChatGPT 구독 OAuth 재사용) 설치 여부. npm 글로벌 + PATH에서
|
|
/// codex.cmd/codex.exe/codex.ps1을 탐지한다.
|
|
/// </summary>
|
|
public static bool IsCodexAvailable()
|
|
{
|
|
var names = new[] { "codex.cmd", "codex.exe", "codex.ps1" };
|
|
var dirs = new List<string>();
|
|
|
|
var appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
|
if (!string.IsNullOrEmpty(appdata)) dirs.Add(Path.Combine(appdata, "npm"));
|
|
|
|
var pathEnv = Environment.GetEnvironmentVariable("PATH") ?? "";
|
|
foreach (var d in pathEnv.Split(Path.PathSeparator))
|
|
if (!string.IsNullOrWhiteSpace(d)) dirs.Add(d.Trim());
|
|
|
|
foreach (var dir in dirs.Distinct())
|
|
{
|
|
try
|
|
{
|
|
foreach (var n in names)
|
|
if (File.Exists(Path.Combine(dir, n))) return true;
|
|
}
|
|
catch { /* 잘못된 경로 무시 */ }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static bool IsH2OrestartInstalled()
|
|
{
|
|
try
|
|
{
|
|
var roots = new[]
|
|
{
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
};
|
|
foreach (var root in roots)
|
|
{
|
|
if (string.IsNullOrEmpty(root)) continue;
|
|
var loDir = Path.Combine(root, "LibreOffice", "4", "user", "uno_packages", "cache", "uno_packages");
|
|
if (Directory.Exists(loDir))
|
|
{
|
|
foreach (var dir in Directory.EnumerateDirectories(loDir, "*H2Orestart*", SearchOption.AllDirectories))
|
|
{
|
|
if (Directory.Exists(dir)) return true;
|
|
}
|
|
}
|
|
var extDir = Path.Combine(root, "LibreOffice", "4", "user", "extensions", "bundled");
|
|
if (Directory.Exists(extDir))
|
|
{
|
|
foreach (var dir in Directory.EnumerateDirectories(extDir, "*H2O*", SearchOption.AllDirectories))
|
|
{
|
|
if (Directory.Exists(dir)) return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
return false;
|
|
}
|
|
}
|