재구성 AX Agent 설정과 채팅 UI를 Claude형 구조로
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
This commit is contained in:
@@ -94,18 +94,7 @@ public partial class App : System.Windows.Application
|
||||
LogService.Warn($"대화 보관 정리 중 오류: {ex.Message}");
|
||||
}
|
||||
|
||||
// 마이그레이션 알림 (UI가 준비된 후 표시)
|
||||
if (settings.MigrationSummary != null)
|
||||
{
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
CustomMessageBox.Show(
|
||||
settings.MigrationSummary,
|
||||
"AX Copilot — 설정 업데이트",
|
||||
System.Windows.MessageBoxButton.OK,
|
||||
System.Windows.MessageBoxImage.Information);
|
||||
}, System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}
|
||||
// 업데이트/마이그레이션 안내 메시지박스는 표시하지 않음.
|
||||
|
||||
// 언어 초기화
|
||||
L10n.SetLanguage(settings.Settings.Launcher.Language);
|
||||
@@ -482,7 +471,7 @@ public partial class App : System.Windows.Application
|
||||
.AddItem("\uE7C5", "AX Commander 호출하기", () =>
|
||||
Dispatcher.Invoke(() => _launcher?.Show()))
|
||||
.AddItem("\uE8BD", "AX Agent 대화하기", () =>
|
||||
Dispatcher.Invoke(() => OpenAiChat()), out var aiTrayItem)
|
||||
Dispatcher.Invoke(OpenAiChat), out var aiTrayItem)
|
||||
.AddItem("\uE8A7", "독 바 표시", () =>
|
||||
Dispatcher.Invoke(() => ToggleDockBar()))
|
||||
.AddSeparator()
|
||||
@@ -549,6 +538,16 @@ public partial class App : System.Windows.Application
|
||||
/// <summary>ChatWindow 등 외부에서 설정 창을 여는 공개 메서드.</summary>
|
||||
public void OpenSettingsFromChat() => Dispatcher.Invoke(OpenSettings);
|
||||
|
||||
/// <summary>AX Agent 창을 열고 전용 설정창을 바로 표시합니다.</summary>
|
||||
public void OpenAgentSettingsInChat()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
OpenAiChat();
|
||||
_chatWindow?.OpenAgentSettingsFromExternal();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>AX Agent 창 열기 (트레이 메뉴 등에서 호출).</summary>
|
||||
private Views.ChatWindow? _chatWindow;
|
||||
|
||||
@@ -610,20 +609,22 @@ public partial class App : System.Windows.Application
|
||||
var launcher = _settings?.Settings.Launcher;
|
||||
var dockItems = launcher?.DockBarItems ?? new() { "launcher", "clipboard", "capture", "agent", "clock", "cpu" };
|
||||
_dockBar.BuildFromSettings(dockItems);
|
||||
_dockBar.OnPositionChanged = (left, top) =>
|
||||
_dockBar.OnPositionChanged = (deviceName, left, top) =>
|
||||
{
|
||||
if (_settings != null)
|
||||
if (_settings != null && !string.IsNullOrWhiteSpace(deviceName))
|
||||
{
|
||||
_settings.Settings.Launcher.DockBarLeft = left;
|
||||
_settings.Settings.Launcher.DockBarTop = top;
|
||||
_settings.Settings.Launcher.MonitorDockPositions[deviceName] = new List<double> { left, top };
|
||||
_settings.Save();
|
||||
}
|
||||
};
|
||||
_dockBar.Show();
|
||||
var (dockLeft, dockTop) = ResolveDockBarPosition(launcher);
|
||||
_dockBar.ApplySettings(
|
||||
launcher?.DockBarOpacity ?? 0.92,
|
||||
launcher?.DockBarLeft ?? -1,
|
||||
launcher?.DockBarTop ?? -1,
|
||||
dockLeft,
|
||||
dockTop,
|
||||
launcher?.DockBarRainbowGlow ?? false);
|
||||
}
|
||||
|
||||
@@ -634,13 +635,59 @@ public partial class App : System.Windows.Application
|
||||
var launcher = _settings?.Settings.Launcher;
|
||||
var dockItems = launcher?.DockBarItems ?? new() { "launcher", "clipboard", "capture", "agent", "clock", "cpu" };
|
||||
_dockBar.BuildFromSettings(dockItems);
|
||||
var (dockLeft, dockTop) = ResolveDockBarPosition(launcher);
|
||||
_dockBar.ApplySettings(
|
||||
launcher?.DockBarOpacity ?? 0.92,
|
||||
launcher?.DockBarLeft ?? -1,
|
||||
launcher?.DockBarTop ?? -1,
|
||||
dockLeft,
|
||||
dockTop,
|
||||
launcher?.DockBarRainbowGlow ?? false);
|
||||
}
|
||||
|
||||
private (double Left, double Top) ResolveDockBarPosition(AxCopilot.Models.LauncherSettings? launcher)
|
||||
{
|
||||
if (launcher == null)
|
||||
return (-1, -1);
|
||||
|
||||
var currentScreen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
|
||||
var currentDeviceName = currentScreen.DeviceName;
|
||||
if (launcher.MonitorDockPositions.TryGetValue(currentDeviceName, out var stored)
|
||||
&& stored is { Count: >= 2 }
|
||||
&& IsDockPositionValid(currentScreen, stored[0], stored[1]))
|
||||
{
|
||||
launcher.DockBarLeft = stored[0];
|
||||
launcher.DockBarTop = stored[1];
|
||||
return (stored[0], stored[1]);
|
||||
}
|
||||
|
||||
var left = launcher.DockBarLeft;
|
||||
var top = launcher.DockBarTop;
|
||||
if (left >= 0 && top >= 0)
|
||||
{
|
||||
var matchedScreen = Screen.AllScreens.FirstOrDefault(screen => IsDockPositionValid(screen, left, top));
|
||||
if (matchedScreen != null)
|
||||
return (left, top);
|
||||
}
|
||||
|
||||
launcher.DockBarLeft = -1;
|
||||
launcher.DockBarTop = -1;
|
||||
try { _settings?.Save(); } catch { }
|
||||
return (-1, -1);
|
||||
}
|
||||
|
||||
private bool IsDockPositionValid(Screen screen, double left, double top)
|
||||
{
|
||||
if (left < 0 || top < 0)
|
||||
return false;
|
||||
|
||||
var width = _dockBar?.ActualWidth > 0 ? _dockBar.ActualWidth : 420;
|
||||
var height = _dockBar?.ActualHeight > 0 ? _dockBar.ActualHeight : 56;
|
||||
var area = screen.WorkingArea;
|
||||
return left >= area.Left
|
||||
&& top >= area.Top
|
||||
&& left + width <= area.Right
|
||||
&& top + height <= area.Bottom;
|
||||
}
|
||||
|
||||
private void OpenSettings()
|
||||
{
|
||||
if (_settingsWindow != null && _settingsWindow.IsVisible)
|
||||
|
||||
@@ -853,15 +853,6 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
<h2 id="sec-agent" class="section-title">🤖 AX Agent 상세 안내</h2>
|
||||
|
||||
<div style="background:#fff3f3; border:2px solid #e74c3c; border-radius:12px; padding:20px; margin:16px 0;">
|
||||
<p style="font-size:15px; font-weight:bold; color:#c0392b; margin:0 0 8px 0;">⚠ 당부의 말씀 드립니다.</p>
|
||||
<p style="font-size:14px; color:#333; margin:0; line-height:1.7;">
|
||||
AX Agent의 <strong style="color:#c0392b;">Cowork / Code 기능은 충분한 검증이 되지 않았습니다.</strong><br>
|
||||
데이터가 있는 폴더를 워크스페이스로 지정하는 경우
|
||||
<strong style="background:#ffe066; color:#333; padding:2px 6px; border-radius:4px;">반드시 백업본을 만들고 진행</strong> 부탁드립니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:20px;">
|
||||
<h3 style="color:#2563eb; border-bottom:2px solid #2563eb; padding-bottom:4px;">💬 Chat 탭 — 연속적인 질의 답변</h3>
|
||||
<ul style="font-size:13.5px; color:#333; line-height:2; margin:8px 0 16px 16px;">
|
||||
@@ -905,10 +896,26 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
<!-- 푸터 -->
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
|
||||
<!-- v1.7.2 -->
|
||||
<div id="sec-version" class="version-section">
|
||||
<h2>?? ?? ??</h2>
|
||||
<!-- v0.7.3 -->
|
||||
<div class="version-entry" style="border-left:3px solid #2563EB; background:linear-gradient(90deg,#2563EB10,transparent);">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#2563EB; color:#fff;">v0.7.3</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
<li><strong>AX Agent ?? ?? ??</strong> ? ??? ???? ?? ????? ?? ???, ?? ??? ??? ??? ??????</li>
|
||||
<li><strong>?? UX ??</strong> ? ? ?? ? ? ??? ?? ??? ??? ????, ?? ?? ?? ?? ??? ??????</li>
|
||||
<li><strong>??? ?? ??</strong> ? ???? ?? ???, <code>/compact</code>, <code>/status test</code> ?? ??? ??????</li>
|
||||
<li><strong>?? ?? ???</strong> ? vLLM SSL ?? ??, ??? ?? ??, ?? ??? ??????</li>
|
||||
<li><strong>?? ????? ??</strong> ? ??? ?? ?? ???? ???? ? ? ??? ????? ?????</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div><!-- v0.7.2 -->
|
||||
<div class="version-entry" style="border-left:3px solid #22C55E; background:linear-gradient(90deg,#22C55E10,transparent);">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#22C55E; color:#fff;">v1.7.2</div>
|
||||
<div class="version-badge" style="background:#22C55E; color:#fff;">v0.7.2</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -923,10 +930,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.7.1 -->
|
||||
<!-- v0.7.1 -->
|
||||
<div class="version-entry" style="border-left:3px solid #F59E0B; background:linear-gradient(90deg,#F59E0B10,transparent);">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#F59E0B; color:#fff;">v1.7.1</div>
|
||||
<div class="version-badge" style="background:#F59E0B; color:#fff;">v0.7.1</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -939,10 +946,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.7.0 -->
|
||||
<!-- v0.7.0 -->
|
||||
<div class="version-entry" style="border-left:3px solid #8B5CF6; background:linear-gradient(90deg,#8B5CF610,transparent);">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#8B5CF6; color:#fff;">v1.7.0</div>
|
||||
<div class="version-badge" style="background:#8B5CF6; color:#fff;">v0.7.0</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -956,10 +963,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.6.1 -->
|
||||
<!-- v0.6.1 -->
|
||||
<div class="version-entry" style="border-left:3px solid #38BDF8; background:linear-gradient(90deg,#38BDF810,transparent);">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#38BDF8; color:#fff;">v1.6.1</div>
|
||||
<div class="version-badge" style="background:#38BDF8; color:#fff;">v0.6.1</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -971,10 +978,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.6.0 -->
|
||||
<!-- v0.6.0 -->
|
||||
<div class="version-entry" style="border-left:3px solid #10B981; background:linear-gradient(90deg,#10B98110,transparent);">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#10B981; color:#fff;">v1.6.0</div>
|
||||
<div class="version-badge" style="background:#10B981; color:#fff;">v0.6.0</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -986,10 +993,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.5.2 -->
|
||||
<!-- v0.5.2 -->
|
||||
<div class="version-entry" style="border-left:3px solid #A78BFA; background:linear-gradient(90deg,#A78BFA10,transparent);">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#A78BFA; color:#fff;">v1.5.2</div>
|
||||
<div class="version-badge" style="background:#A78BFA; color:#fff;">v0.5.2</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1002,10 +1009,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.5.1 -->
|
||||
<!-- v0.5.1 -->
|
||||
<div class="version-entry" style="border-left:3px solid #4b5efc;">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#4b5efc; color:#fff;">v1.5.1</div>
|
||||
<div class="version-badge" style="background:#4b5efc; color:#fff;">v0.5.1</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1018,10 +1025,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.5.0 -->
|
||||
<!-- v0.5.0 -->
|
||||
<div class="version-entry" style="border-left:3px solid #059669;">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#059669; color:#fff;">v1.5.0</div>
|
||||
<div class="version-badge" style="background:#059669; color:#fff;">v0.5.0</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1038,12 +1045,12 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
|
||||
<details class="version-fold">
|
||||
<summary>이전 이력 (v1.4.0 이하)</summary>
|
||||
<summary>이전 이력 (v0.4.0 이하)</summary>
|
||||
|
||||
<!-- v1.4.0 -->
|
||||
<!-- v0.4.0 -->
|
||||
<div class="version-entry" style="border-left:3px solid #2563EB;">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#2563EB; color:#fff;">v1.4.0</div>
|
||||
<div class="version-badge" style="background:#2563EB; color:#fff;">v0.4.0</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1057,10 +1064,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.3.1 -->
|
||||
<!-- v0.3.1 -->
|
||||
<div class="version-entry" style="border-left:3px solid #059669;">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#059669; color:#fff;">v1.3.1</div>
|
||||
<div class="version-badge" style="background:#059669; color:#fff;">v0.3.1</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1075,10 +1082,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.3.0 -->
|
||||
<!-- v0.3.0 -->
|
||||
<div class="version-entry" style="border-left:3px solid #8b5cf6;">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#8b5cf6; color:#fff;">v1.3.0</div>
|
||||
<div class="version-badge" style="background:#8b5cf6; color:#fff;">v0.3.0</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1093,10 +1100,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.2.2 -->
|
||||
<!-- v0.2.2 -->
|
||||
<div class="version-entry" style="border-left:3px solid #0ea5e9;">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#0ea5e9; color:#fff;">v1.2.2</div>
|
||||
<div class="version-badge" style="background:#0ea5e9; color:#fff;">v0.2.2</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1110,12 +1117,12 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
|
||||
<details class="version-fold">
|
||||
<summary>이전 이력 (v1.2.1 이하)</summary>
|
||||
<summary>이전 이력 (v0.2.1 이하)</summary>
|
||||
|
||||
<!-- v1.2.1 -->
|
||||
<!-- v0.2.1 -->
|
||||
<div class="version-entry" style="border-left:3px solid #db2777;">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#db2777; color:#fff;">v1.2.1</div>
|
||||
<div class="version-badge" style="background:#db2777; color:#fff;">v0.2.1</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1126,10 +1133,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.2.0 -->
|
||||
<!-- v0.2.0 -->
|
||||
<div class="version-entry" style="border-left:3px solid #EC4899;">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#EC4899; color:#fff;">v1.2.0</div>
|
||||
<div class="version-badge" style="background:#EC4899; color:#fff;">v0.2.0</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1141,10 +1148,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.1.1 -->
|
||||
<!-- v0.1.1 -->
|
||||
<div class="version-entry" style="border-left:3px solid #3B82F6;">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#3B82F6; color:#fff;">v1.1.1</div>
|
||||
<div class="version-badge" style="background:#3B82F6; color:#fff;">v0.1.1</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1157,10 +1164,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.1.0 -->
|
||||
<!-- v0.1.0 -->
|
||||
<div class="version-entry" style="border-left:3px solid #EC4899;">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#EC4899; color:#fff;">v1.1.0</div>
|
||||
<div class="version-badge" style="background:#EC4899; color:#fff;">v0.1.0</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1172,10 +1179,10 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- v1.0.7 이하 -->
|
||||
<!-- v0.0.7 이하 -->
|
||||
<div class="version-entry">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#8B2FC9; color:#fff;">v1.0.7</div>
|
||||
<div class="version-badge" style="background:#8B2FC9; color:#fff;">v0.0.7</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1187,7 +1194,7 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
|
||||
<div class="version-entry">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#6b7280; color:#fff;">v1.0.6</div>
|
||||
<div class="version-badge" style="background:#6b7280; color:#fff;">v0.0.6</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1197,11 +1204,11 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</div>
|
||||
|
||||
<details class="version-fold">
|
||||
<summary>이전 이력 모음 (v1.0.0 ~ v1.0.4)</summary>
|
||||
<summary>이전 이력 모음 (v0.0.0 ~ v0.0.4)</summary>
|
||||
|
||||
<div class="version-entry">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#6b7280;">v1.0.4</div>
|
||||
<div class="version-badge" style="background:#6b7280;">v0.0.4</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1218,7 +1225,7 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
|
||||
<div class="version-entry">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#6b7280;">v1.0.3</div>
|
||||
<div class="version-badge" style="background:#6b7280;">v0.0.3</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1237,7 +1244,7 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
|
||||
<div class="version-entry">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#6b7280;">v1.0.2</div>
|
||||
<div class="version-badge" style="background:#6b7280;">v0.0.2</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1249,7 +1256,7 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
|
||||
<div class="version-entry">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#6b7280;">v1.0.1</div>
|
||||
<div class="version-badge" style="background:#6b7280;">v0.0.1</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1265,7 +1272,7 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
|
||||
<div class="version-entry">
|
||||
<div class="version-meta">
|
||||
<div class="version-badge" style="background:#9ca3af;">v1.0.0</div>
|
||||
<div class="version-badge" style="background:#9ca3af;">v0.0.0</div>
|
||||
</div>
|
||||
<div class="version-changes">
|
||||
<ul>
|
||||
@@ -1301,3 +1308,6 @@ Ollama의 모든 모델이 도구 사용을 지원하지는 않습니다. 아래
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
@@ -767,15 +767,6 @@ td {
|
||||
<!-- ══════════════════════════════════════════════════════════ -->
|
||||
<h2 id="sec-agent" class="section-title">🤖 AX Agent 상세 안내</h2>
|
||||
|
||||
<div style="background:#fff3f3; border:2px solid #e74c3c; border-radius:12px; padding:20px; margin:16px 0;">
|
||||
<p style="font-size:15px; font-weight:bold; color:#c0392b; margin:0 0 8px 0;">⚠ 당부의 말씀 드립니다.</p>
|
||||
<p style="font-size:14px; color:#333; margin:0; line-height:1.7;">
|
||||
AX Agent의 <strong style="color:#c0392b;">Cowork / Code 기능은 충분한 검증이 되지 않았습니다.</strong><br>
|
||||
데이터가 있는 폴더를 워크스페이스로 지정하는 경우
|
||||
<strong style="background:#ffe066; color:#333; padding:2px 6px; border-radius:4px;">반드시 백업본을 만들고 진행</strong> 부탁드립니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:20px;">
|
||||
<h3 style="color:#2563eb; border-bottom:2px solid #2563eb; padding-bottom:4px;">💬 Chat 탭 — 연속적인 질의 답변</h3>
|
||||
<ul style="font-size:13.5px; color:#333; line-height:2; margin:8px 0 16px 16px;">
|
||||
@@ -829,3 +820,4 @@ td {
|
||||
</div><!-- /.wrap -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -17,7 +17,7 @@
|
||||
- DEVELOPMENT.md 변경 이력은 수동으로 함께 업데이트하세요.
|
||||
설정 스키마 버전(마이그레이션)은 Services/SettingsService.cs → CurrentSettingsVersion 을 별도로 관리합니다.
|
||||
-->
|
||||
<Version>1.7.2</Version>
|
||||
<Version>0.7.3</Version>
|
||||
<Company>AX</Company>
|
||||
<Product>AX Copilot</Product>
|
||||
<Description>AI 기반 업무 자동화 런처 & 코파일럿</Description>
|
||||
@@ -146,3 +146,4 @@
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -825,6 +825,10 @@ public class LlmSettings
|
||||
[JsonPropertyName("agentTheme")]
|
||||
public string AgentTheme { get; set; } = "system";
|
||||
|
||||
/// <summary>AX Agent 전용 테마 스타일. claw | codex | slate</summary>
|
||||
[JsonPropertyName("agentThemePreset")]
|
||||
public string AgentThemePreset { get; set; } = "claw";
|
||||
|
||||
// ─── 알림 ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>에이전트 작업 완료 시 시스템 알림 표시 여부. (Cowork/Code 공통)</summary>
|
||||
|
||||
@@ -388,6 +388,24 @@ public static class AgentHookRunner
|
||||
}
|
||||
}
|
||||
|
||||
if (ctxProp.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
// 구조화된 컨텍스트 오브젝트도 허용합니다.
|
||||
// 우선순위: message > content > text
|
||||
foreach (var key in new[] { "message", "content", "text" })
|
||||
{
|
||||
if (!ctxProp.TryGetProperty(key, out var value) || value.ValueKind != JsonValueKind.String)
|
||||
continue;
|
||||
|
||||
var text = value.GetString()?.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
additionalContext = text;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,17 +39,26 @@ public static class ContextCondenser
|
||||
/// 2단계: 이전 대화 LLM 요약 (토큰이 여전히 높으면)
|
||||
/// </summary>
|
||||
public static async Task<bool> CondenseIfNeededAsync(
|
||||
List<ChatMessage> messages, LlmService llm, int maxOutputTokens, CancellationToken ct = default)
|
||||
List<ChatMessage> messages,
|
||||
LlmService llm,
|
||||
int maxOutputTokens,
|
||||
bool proactiveEnabled = true,
|
||||
int triggerPercent = 80,
|
||||
bool force = false,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (messages.Count < 6) return false;
|
||||
if (!force && !proactiveEnabled) return false;
|
||||
|
||||
// 현재 모델의 입력 토큰 한도
|
||||
var settings = llm.GetCurrentModelInfo();
|
||||
var inputLimit = GetModelInputLimit(settings.service, settings.model);
|
||||
var threshold = (int)(inputLimit * 0.65); // 65%에서 압축 시작
|
||||
var effectiveMax = maxOutputTokens > 0 ? Math.Min(inputLimit, maxOutputTokens) : inputLimit;
|
||||
var percent = Math.Clamp(triggerPercent, 50, 95);
|
||||
var threshold = (int)(effectiveMax * (percent / 100.0)); // 설정 임계치에서 압축 시작
|
||||
|
||||
var currentTokens = TokenEstimator.EstimateMessages(messages);
|
||||
if (currentTokens < threshold) return false;
|
||||
if (!force && currentTokens < threshold) return false;
|
||||
|
||||
bool didCompress = false;
|
||||
|
||||
@@ -58,7 +67,7 @@ public static class ContextCondenser
|
||||
|
||||
// 1단계 후 다시 추정
|
||||
currentTokens = TokenEstimator.EstimateMessages(messages);
|
||||
if (currentTokens < threshold) return didCompress;
|
||||
if (!force && currentTokens < threshold) return didCompress;
|
||||
|
||||
// ── 2단계: 이전 대화 LLM 요약 ──
|
||||
didCompress |= await SummarizeOldMessagesAsync(messages, llm, ct);
|
||||
|
||||
@@ -55,6 +55,9 @@ public class HttpTool : IAgentTool
|
||||
|
||||
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
|
||||
{
|
||||
if (AxCopilot.Services.OperationModePolicy.IsInternal(context.OperationMode))
|
||||
return ToolResult.Fail("사내모드에서는 HTTP 도구 실행이 차단됩니다. operationMode=external에서만 사용할 수 있습니다.");
|
||||
|
||||
var method = args.GetProperty("method").GetString()?.ToUpperInvariant() ?? "GET";
|
||||
var url = args.GetProperty("url").GetString() ?? "";
|
||||
var body = args.TryGetProperty("body", out var b) ? b.GetString() ?? "" : "";
|
||||
|
||||
@@ -38,6 +38,9 @@ public class OpenExternalTool : IAgentTool
|
||||
if (rawPath.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
rawPath.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (AxCopilot.Services.OperationModePolicy.IsInternal(context.OperationMode))
|
||||
return Task.FromResult(ToolResult.Fail("사내모드에서는 외부 URL 열기가 차단됩니다. operationMode=external에서만 사용할 수 있습니다."));
|
||||
|
||||
Process.Start(new ProcessStartInfo(rawPath) { UseShellExecute = true });
|
||||
return Task.FromResult(ToolResult.Ok($"URL 열기: {rawPath}"));
|
||||
}
|
||||
|
||||
@@ -124,12 +124,12 @@ public static class PermissionModeCatalog
|
||||
var normalized = NormalizeGlobalMode(mode);
|
||||
return normalized switch
|
||||
{
|
||||
Default => "소극 활용",
|
||||
AcceptEdits => "적극 활용",
|
||||
Plan => "계획 중심",
|
||||
BypassPermissions => "완전 자동",
|
||||
Default => "권한 요청",
|
||||
AcceptEdits => "편집 자동 승인",
|
||||
Plan => "계획 모드",
|
||||
BypassPermissions => "권한 건너뛰기",
|
||||
DontAsk => "질문 없이 진행",
|
||||
Deny => "활용하지 않음",
|
||||
Deny => "읽기 전용",
|
||||
_ => normalized,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,32 +14,32 @@ internal static class PermissionModePresentationCatalog
|
||||
new PermissionModePresentation(
|
||||
PermissionModeCatalog.Deny,
|
||||
"\uE711",
|
||||
"활용하지 않음",
|
||||
"읽기 전용",
|
||||
"파일 읽기만 허용하고 생성/수정/삭제는 차단합니다.",
|
||||
"#107C10"),
|
||||
new PermissionModePresentation(
|
||||
PermissionModeCatalog.Default,
|
||||
"\uE8D7",
|
||||
"소극 활용",
|
||||
"변경 전 확인하고, 필요한 경우에만 파일 접근을 진행합니다.",
|
||||
"권한 요청",
|
||||
"변경하기 전에 항상 확인합니다.",
|
||||
"#2563EB"),
|
||||
new PermissionModePresentation(
|
||||
PermissionModeCatalog.AcceptEdits,
|
||||
"\uE73E",
|
||||
"적극 활용",
|
||||
"파일 편집 도구를 자동 승인하고 명령 실행은 계속 확인합니다.",
|
||||
"편집 자동 승인",
|
||||
"모든 파일 편집을 자동 승인합니다.",
|
||||
"#107C10"),
|
||||
new PermissionModePresentation(
|
||||
PermissionModeCatalog.Plan,
|
||||
"\uE7C3",
|
||||
"계획 중심",
|
||||
"쓰기 전 계획과 승인 흐름을 우선합니다.",
|
||||
"계획 모드",
|
||||
"변경하기 전에 계획을 먼저 만듭니다.",
|
||||
"#4338CA"),
|
||||
new PermissionModePresentation(
|
||||
PermissionModeCatalog.BypassPermissions,
|
||||
"\uE814",
|
||||
"완전 자동",
|
||||
"권한 확인을 대부분 생략합니다. 민감한 작업은 주의하세요.",
|
||||
"권한 건너뛰기",
|
||||
"모든 권한을 허용합니다.",
|
||||
"#B45309"),
|
||||
new PermissionModePresentation(
|
||||
PermissionModeCatalog.DontAsk,
|
||||
|
||||
@@ -47,6 +47,7 @@ public class ClipboardHistoryService : IDisposable
|
||||
private HwndSource? _msgSource;
|
||||
private readonly object _lock = new();
|
||||
private volatile bool _ignoreNext; // 자체 클립보드 조작 시 히스토리 추가 방지
|
||||
private uint _lastClipboardSequenceNumber;
|
||||
private bool _disposed;
|
||||
|
||||
private readonly List<ClipboardEntry> _history = new();
|
||||
@@ -332,7 +333,19 @@ public class ClipboardHistoryService : IDisposable
|
||||
|
||||
private void OnClipboardUpdate()
|
||||
{
|
||||
if (_ignoreNext) { _ignoreNext = false; return; }
|
||||
var sequence = GetClipboardSequenceNumber();
|
||||
|
||||
if (_ignoreNext)
|
||||
{
|
||||
_ignoreNext = false;
|
||||
_lastClipboardSequenceNumber = sequence;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sequence != 0 && sequence == _lastClipboardSequenceNumber)
|
||||
return;
|
||||
|
||||
_lastClipboardSequenceNumber = sequence;
|
||||
if (!_settings.Settings.ClipboardHistory.Enabled) return;
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
@@ -529,6 +542,9 @@ public class ClipboardHistoryService : IDisposable
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetClipboardSequenceNumber();
|
||||
}
|
||||
|
||||
/// <summary>클립보드 히스토리 단일 항목. 텍스트 또는 이미지 중 하나를 담습니다.</summary>
|
||||
|
||||
@@ -430,12 +430,11 @@ public partial class LlmService
|
||||
private async Task<List<ContentBlock>> SendOpenAiWithToolsAsync(
|
||||
List<ChatMessage> messages, IReadOnlyCollection<IAgentTool> tools, CancellationToken ct)
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
var activeService = ResolveService();
|
||||
var body = BuildOpenAiToolBody(messages, tools);
|
||||
|
||||
// 등록 모델의 커스텀 엔드포인트 우선 사용 (ResolveServerInfo)
|
||||
var (resolvedEp, _) = ResolveServerInfo();
|
||||
var (resolvedEp, _, allowInsecureTls) = ResolveServerInfo();
|
||||
var endpoint = string.IsNullOrEmpty(resolvedEp)
|
||||
? ResolveEndpointForService(activeService)
|
||||
: resolvedEp;
|
||||
@@ -452,7 +451,7 @@ public partial class LlmService
|
||||
// CP4D 또는 Bearer 인증 적용
|
||||
await ApplyAuthHeaderAsync(req, ct);
|
||||
|
||||
using var resp = await _http.SendAsync(req, ct);
|
||||
using var resp = await SendWithTlsAsync(req, allowInsecureTls, ct);
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
{
|
||||
var errBody = await resp.Content.ReadAsStringAsync(ct);
|
||||
|
||||
@@ -20,6 +20,7 @@ public record TokenUsage(int PromptTokens, int CompletionTokens)
|
||||
public partial class LlmService : IDisposable
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly HttpClient _httpInsecure;
|
||||
private readonly SettingsService _settings;
|
||||
private string? _systemPrompt;
|
||||
|
||||
@@ -136,6 +137,34 @@ public partial class LlmService : IDisposable
|
||||
|
||||
private string? ResolveReasoningEffort() => _reasoningEffortOverride;
|
||||
|
||||
private static bool LooksLikeEncryptedPayload(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value) || value.Length < 32 || (value.Length % 4) != 0)
|
||||
return false;
|
||||
foreach (var ch in value)
|
||||
{
|
||||
var isBase64 = (ch >= 'A' && ch <= 'Z')
|
||||
|| (ch >= 'a' && ch <= 'z')
|
||||
|| (ch >= '0' && ch <= '9')
|
||||
|| ch is '+' or '/' or '=';
|
||||
if (!isBase64) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string ResolveSecretValue(string raw, bool encryptionEnabled)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw)) return "";
|
||||
if (raw.Trim() == "(저장됨)") return "";
|
||||
if (!encryptionEnabled) return raw.Trim();
|
||||
|
||||
var decrypted = CryptoService.DecryptIfEnabled(raw, encryptionEnabled).Trim();
|
||||
if (string.IsNullOrWhiteSpace(decrypted)) return "";
|
||||
if (string.Equals(decrypted, raw, StringComparison.Ordinal) && LooksLikeEncryptedPayload(raw))
|
||||
return "";
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
/// <summary>지정 서비스의 API 키를 반환합니다.</summary>
|
||||
private string ResolveApiKeyForService(string service)
|
||||
{
|
||||
@@ -144,8 +173,8 @@ public partial class LlmService : IDisposable
|
||||
{
|
||||
"gemini" => llm.GeminiApiKey,
|
||||
"sigmoid" => llm.ClaudeApiKey,
|
||||
"vllm" => CryptoService.DecryptIfEnabled(llm.VllmApiKey, llm.EncryptionEnabled),
|
||||
"ollama" => CryptoService.DecryptIfEnabled(llm.OllamaApiKey, llm.EncryptionEnabled),
|
||||
"vllm" => ResolveSecretValue(llm.VllmApiKey, llm.EncryptionEnabled),
|
||||
"ollama" => ResolveSecretValue(llm.OllamaApiKey, llm.EncryptionEnabled),
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
@@ -165,13 +194,41 @@ public partial class LlmService : IDisposable
|
||||
/// <summary>가장 최근 요청의 토큰 사용량. 스트리밍/비스트리밍 완료 후 갱신됩니다.</summary>
|
||||
public TokenUsage? LastTokenUsage { get; private set; }
|
||||
|
||||
public record RuntimeConnectionSnapshot(
|
||||
string Service,
|
||||
string Model,
|
||||
string Endpoint,
|
||||
bool AllowInsecureTls,
|
||||
bool HasApiKey);
|
||||
|
||||
public LlmService(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
_http = new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
|
||||
var insecureHandler = new HttpClientHandler
|
||||
{
|
||||
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
|
||||
};
|
||||
_httpInsecure = new HttpClient(insecureHandler) { Timeout = TimeSpan.FromMinutes(10) };
|
||||
LoadSystemPrompt();
|
||||
}
|
||||
|
||||
public RuntimeConnectionSnapshot GetRuntimeConnectionSnapshot()
|
||||
{
|
||||
var service = ResolveService();
|
||||
var model = ResolveModel();
|
||||
var (endpoint, apiKey, allowInsecureTls) = ResolveServerInfo();
|
||||
if (string.IsNullOrWhiteSpace(endpoint))
|
||||
endpoint = ResolveEndpointForService(service);
|
||||
|
||||
return new RuntimeConnectionSnapshot(
|
||||
service,
|
||||
model,
|
||||
endpoint ?? "",
|
||||
allowInsecureTls,
|
||||
!string.IsNullOrWhiteSpace(apiKey));
|
||||
}
|
||||
|
||||
// ─── 시스템 프롬프트 (빌드 경로에서 동적 로딩) ─────────────────────────
|
||||
|
||||
private void LoadSystemPrompt()
|
||||
@@ -200,7 +257,7 @@ public partial class LlmService : IDisposable
|
||||
/// 현재 활성 모델에 매칭되는 RegisteredModel을 찾아 엔드포인트/API키를 반환합니다.
|
||||
/// RegisteredModel에 전용 서버 정보가 있으면 그것을 사용하고, 없으면 기본 설정을 사용합니다.
|
||||
/// </summary>
|
||||
private (string Endpoint, string ApiKey) ResolveServerInfo()
|
||||
private (string Endpoint, string ApiKey, bool AllowInsecureTls) ResolveServerInfo()
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
var activeService = ResolveService();
|
||||
@@ -209,20 +266,26 @@ public partial class LlmService : IDisposable
|
||||
// RegisteredModel에서 현재 모델과 서비스가 일치하는 항목 검색
|
||||
var registered = FindRegisteredModel(llm, activeService, modelName);
|
||||
|
||||
if (registered != null && !string.IsNullOrEmpty(registered.Endpoint))
|
||||
if (registered != null)
|
||||
{
|
||||
var endpoint = string.IsNullOrWhiteSpace(registered.Endpoint)
|
||||
? ResolveEndpointForService(activeService)
|
||||
: registered.Endpoint;
|
||||
var apiKey = !string.IsNullOrEmpty(registered.ApiKey)
|
||||
? CryptoService.DecryptIfEnabled(registered.ApiKey, llm.EncryptionEnabled)
|
||||
? ResolveSecretValue(registered.ApiKey, llm.EncryptionEnabled)
|
||||
: GetDefaultApiKey(llm, activeService);
|
||||
return (registered.Endpoint, apiKey);
|
||||
var allowInsecureTls = activeService == "vllm"
|
||||
? (registered.AllowInsecureTls || llm.VllmAllowInsecureTls)
|
||||
: false;
|
||||
return (endpoint, apiKey, allowInsecureTls);
|
||||
}
|
||||
|
||||
// 기본 엔드포인트 사용
|
||||
return activeService.ToLowerInvariant() switch
|
||||
{
|
||||
"vllm" => (llm.VllmEndpoint, CryptoService.DecryptIfEnabled(llm.VllmApiKey, llm.EncryptionEnabled)),
|
||||
"ollama" => (llm.OllamaEndpoint, CryptoService.DecryptIfEnabled(llm.OllamaApiKey, llm.EncryptionEnabled)),
|
||||
_ => ("", ""),
|
||||
"vllm" => (llm.VllmEndpoint, ResolveSecretValue(llm.VllmApiKey, llm.EncryptionEnabled), llm.VllmAllowInsecureTls),
|
||||
"ollama" => (llm.OllamaEndpoint, ResolveSecretValue(llm.OllamaApiKey, llm.EncryptionEnabled), false),
|
||||
_ => ("", "", false),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -258,7 +321,7 @@ public partial class LlmService : IDisposable
|
||||
}
|
||||
|
||||
// 기본 Bearer 인증 — 기존 API 키 반환
|
||||
var (_, apiKey) = ResolveServerInfo();
|
||||
var (_, apiKey, _) = ResolveServerInfo();
|
||||
return string.IsNullOrEmpty(apiKey) ? null : apiKey;
|
||||
}
|
||||
|
||||
@@ -278,8 +341,8 @@ public partial class LlmService : IDisposable
|
||||
var svc = service ?? llm.Service;
|
||||
return svc.ToLowerInvariant() switch
|
||||
{
|
||||
"vllm" => CryptoService.DecryptIfEnabled(llm.VllmApiKey, llm.EncryptionEnabled),
|
||||
"ollama" => CryptoService.DecryptIfEnabled(llm.OllamaApiKey, llm.EncryptionEnabled),
|
||||
"vllm" => ResolveSecretValue(llm.VllmApiKey, llm.EncryptionEnabled),
|
||||
"ollama" => ResolveSecretValue(llm.OllamaApiKey, llm.EncryptionEnabled),
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
@@ -356,7 +419,7 @@ public partial class LlmService : IDisposable
|
||||
try
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
var normalizedService = NormalizeServiceName(llm.Service);
|
||||
var normalizedService = ResolveService();
|
||||
if (OperationModePolicy.IsInternal(_settings.Settings) && IsExternalLlmService(normalizedService))
|
||||
{
|
||||
var blockedName = normalizedService == "sigmoid" ? "Claude" : "Gemini";
|
||||
@@ -366,16 +429,26 @@ public partial class LlmService : IDisposable
|
||||
switch (normalizedService)
|
||||
{
|
||||
case "ollama":
|
||||
var resp = await _http.GetAsync(llm.Endpoint.TrimEnd('/') + "/api/tags");
|
||||
{
|
||||
var (endpoint, _, _) = ResolveServerInfo();
|
||||
var ep = string.IsNullOrWhiteSpace(endpoint) ? ResolveEndpointForService("ollama") : endpoint;
|
||||
var resp = await _http.GetAsync(ep.TrimEnd('/') + "/api/tags");
|
||||
return resp.IsSuccessStatusCode
|
||||
? (true, "Ollama 연결 성공")
|
||||
: (false, ClassifyHttpError(resp));
|
||||
}
|
||||
|
||||
case "vllm":
|
||||
var vResp = await _http.GetAsync(llm.Endpoint.TrimEnd('/') + "/v1/models");
|
||||
{
|
||||
var (endpoint, _, allowInsecureTls) = ResolveServerInfo();
|
||||
var ep = string.IsNullOrWhiteSpace(endpoint) ? ResolveEndpointForService("vllm") : endpoint;
|
||||
using var vReq = new HttpRequestMessage(HttpMethod.Get, ep.TrimEnd('/') + "/v1/models");
|
||||
await ApplyAuthHeaderAsync(vReq, CancellationToken.None);
|
||||
using var vResp = await SendWithTlsAsync(vReq, allowInsecureTls, CancellationToken.None);
|
||||
return vResp.IsSuccessStatusCode
|
||||
? (true, "vLLM 연결 성공")
|
||||
: (false, ClassifyHttpError(vResp));
|
||||
}
|
||||
|
||||
case "gemini":
|
||||
var gKey = ResolveApiKeyForService("gemini");
|
||||
@@ -424,10 +497,10 @@ public partial class LlmService : IDisposable
|
||||
private async Task<string> SendOllamaAsync(List<ChatMessage> messages, CancellationToken ct)
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
var (endpoint, _) = ResolveServerInfo();
|
||||
var (endpoint, _, allowInsecureTls) = ResolveServerInfo();
|
||||
var ep = string.IsNullOrEmpty(endpoint) ? llm.Endpoint : endpoint;
|
||||
var body = BuildOllamaBody(messages, stream: false);
|
||||
var resp = await PostJsonWithRetryAsync(ep.TrimEnd('/') + "/api/chat", body, ct);
|
||||
var resp = await PostJsonWithRetryAsync(ep.TrimEnd('/') + "/api/chat", body, allowInsecureTls, ct);
|
||||
return SafeParseJson(resp, root =>
|
||||
{
|
||||
TryParseOllamaUsage(root);
|
||||
@@ -440,13 +513,13 @@ public partial class LlmService : IDisposable
|
||||
[EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
var (endpoint, _) = ResolveServerInfo();
|
||||
var (endpoint, _, allowInsecureTls) = ResolveServerInfo();
|
||||
var ep = string.IsNullOrEmpty(endpoint) ? llm.Endpoint : endpoint;
|
||||
var body = BuildOllamaBody(messages, stream: true);
|
||||
var url = ep.TrimEnd('/') + "/api/chat";
|
||||
|
||||
using var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = JsonContent(body) };
|
||||
using var resp = await SendWithErrorClassificationAsync(req, ct);
|
||||
using var resp = await SendWithErrorClassificationAsync(req, allowInsecureTls, ct);
|
||||
|
||||
using var stream = await resp.Content.ReadAsStreamAsync(ct);
|
||||
using var reader = new StreamReader(stream);
|
||||
@@ -496,7 +569,7 @@ public partial class LlmService : IDisposable
|
||||
private async Task<string> SendOpenAiCompatibleAsync(List<ChatMessage> messages, CancellationToken ct)
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
var (endpoint, _) = ResolveServerInfo();
|
||||
var (endpoint, _, allowInsecureTls) = ResolveServerInfo();
|
||||
var ep = string.IsNullOrEmpty(endpoint) ? llm.Endpoint : endpoint;
|
||||
var body = BuildOpenAiBody(messages, stream: false);
|
||||
var url = ep.TrimEnd('/') + "/v1/chat/completions";
|
||||
@@ -508,7 +581,7 @@ public partial class LlmService : IDisposable
|
||||
};
|
||||
await ApplyAuthHeaderAsync(req, ct);
|
||||
|
||||
using var resp = await SendWithErrorClassificationAsync(req, ct);
|
||||
using var resp = await SendWithErrorClassificationAsync(req, allowInsecureTls, ct);
|
||||
var respBody = await resp.Content.ReadAsStringAsync(ct);
|
||||
return SafeParseJson(respBody, root =>
|
||||
{
|
||||
@@ -524,14 +597,14 @@ public partial class LlmService : IDisposable
|
||||
[EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
var (endpoint, _) = ResolveServerInfo();
|
||||
var (endpoint, _, allowInsecureTls) = ResolveServerInfo();
|
||||
var ep = string.IsNullOrEmpty(endpoint) ? llm.Endpoint : endpoint;
|
||||
var body = BuildOpenAiBody(messages, stream: true);
|
||||
var url = ep.TrimEnd('/') + "/v1/chat/completions";
|
||||
|
||||
using var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = JsonContent(body) };
|
||||
await ApplyAuthHeaderAsync(req, ct);
|
||||
using var resp = await SendWithErrorClassificationAsync(req, ct);
|
||||
using var resp = await SendWithErrorClassificationAsync(req, allowInsecureTls, ct);
|
||||
|
||||
using var stream = await resp.Content.ReadAsStreamAsync(ct);
|
||||
using var reader = new StreamReader(stream);
|
||||
@@ -597,7 +670,7 @@ public partial class LlmService : IDisposable
|
||||
var model = ResolveModel();
|
||||
var body = BuildGeminiBody(messages);
|
||||
var url = $"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={apiKey}";
|
||||
var resp = await PostJsonWithRetryAsync(url, body, ct);
|
||||
var resp = await PostJsonWithRetryAsync(url, body, false, ct);
|
||||
return SafeParseJson(resp, root =>
|
||||
{
|
||||
TryParseGeminiUsage(root);
|
||||
@@ -623,7 +696,7 @@ public partial class LlmService : IDisposable
|
||||
var url = $"https://generativelanguage.googleapis.com/v1beta/models/{model}:streamGenerateContent?alt=sse&key={apiKey}";
|
||||
|
||||
using var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = JsonContent(body) };
|
||||
using var resp = await SendWithErrorClassificationAsync(req, ct);
|
||||
using var resp = await SendWithErrorClassificationAsync(req, false, ct);
|
||||
|
||||
using var stream = await resp.Content.ReadAsStreamAsync(ct);
|
||||
using var reader = new StreamReader(stream);
|
||||
@@ -881,8 +954,19 @@ public partial class LlmService : IDisposable
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendWithTlsAsync(
|
||||
HttpRequestMessage req,
|
||||
bool allowInsecureTls,
|
||||
CancellationToken ct,
|
||||
HttpCompletionOption completion = HttpCompletionOption.ResponseContentRead)
|
||||
{
|
||||
if (!allowInsecureTls)
|
||||
return await _http.SendAsync(req, completion, ct);
|
||||
return await _httpInsecure.SendAsync(req, completion, ct);
|
||||
}
|
||||
|
||||
/// <summary>비스트리밍 POST + 재시도 (일시적 오류 시 최대 2회)</summary>
|
||||
private async Task<string> PostJsonWithRetryAsync(string url, object body, CancellationToken ct)
|
||||
private async Task<string> PostJsonWithRetryAsync(string url, object body, bool allowInsecureTls, CancellationToken ct)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(body);
|
||||
Exception? lastEx = null;
|
||||
@@ -891,8 +975,11 @@ public partial class LlmService : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
using var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
using var resp = await _http.PostAsync(url, content, ct);
|
||||
using var req = new HttpRequestMessage(HttpMethod.Post, url)
|
||||
{
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
};
|
||||
using var resp = await SendWithTlsAsync(req, allowInsecureTls, ct);
|
||||
|
||||
if (resp.IsSuccessStatusCode)
|
||||
return await resp.Content.ReadAsStringAsync(ct);
|
||||
@@ -920,9 +1007,9 @@ public partial class LlmService : IDisposable
|
||||
|
||||
/// <summary>스트리밍 전용 — HTTP 요청 전송 + 에러 분류</summary>
|
||||
private async Task<HttpResponseMessage> SendWithErrorClassificationAsync(
|
||||
HttpRequestMessage req, CancellationToken ct)
|
||||
HttpRequestMessage req, bool allowInsecureTls, CancellationToken ct)
|
||||
{
|
||||
var resp = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct);
|
||||
var resp = await SendWithTlsAsync(req, allowInsecureTls, ct, HttpCompletionOption.ResponseHeadersRead);
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
{
|
||||
var errBody = await resp.Content.ReadAsStringAsync(ct);
|
||||
@@ -1085,5 +1172,9 @@ public partial class LlmService : IDisposable
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void Dispose() => _http.Dispose();
|
||||
public void Dispose()
|
||||
{
|
||||
_http.Dispose();
|
||||
_httpInsecure.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public class McpClientService : IDisposable
|
||||
{
|
||||
protocolVersion = "2024-11-05",
|
||||
capabilities = new { },
|
||||
clientInfo = new { name = "AX Copilot", version = "1.7.2" },
|
||||
clientInfo = new { name = "AX Copilot", version = "0.7.3" },
|
||||
}, ct);
|
||||
|
||||
if (initResult == null) return false;
|
||||
@@ -328,3 +328,4 @@ public class McpClientService : IDisposable
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -174,6 +174,10 @@ public class SettingsService
|
||||
|
||||
private void NormalizeRuntimeSettings()
|
||||
{
|
||||
// AX Agent 사용 기본 정책: 항상 활성화.
|
||||
if (!_settings.AiEnabled)
|
||||
_settings.AiEnabled = true;
|
||||
|
||||
_settings.Llm.FilePermission = PermissionModeCatalog.NormalizeGlobalMode(_settings.Llm.FilePermission);
|
||||
_settings.Llm.DefaultAgentPermission = PermissionModeCatalog.NormalizeGlobalMode(_settings.Llm.DefaultAgentPermission);
|
||||
if (_settings.Llm.ToolPermissions != null && _settings.Llm.ToolPermissions.Count > 0)
|
||||
@@ -183,6 +187,13 @@ public class SettingsService
|
||||
_settings.Llm.ToolPermissions[key] = PermissionModeCatalog.NormalizeToolOverride(_settings.Llm.ToolPermissions[key]);
|
||||
}
|
||||
|
||||
_settings.Llm.MaxFavoriteSlashCommands = Math.Clamp(_settings.Llm.MaxFavoriteSlashCommands <= 0 ? 10 : _settings.Llm.MaxFavoriteSlashCommands, 1, 30);
|
||||
_settings.Llm.MaxRecentSlashCommands = Math.Clamp(_settings.Llm.MaxRecentSlashCommands <= 0 ? 20 : _settings.Llm.MaxRecentSlashCommands, 5, 50);
|
||||
if (_settings.Llm.FavoriteSlashCommands.Count > _settings.Llm.MaxFavoriteSlashCommands)
|
||||
_settings.Llm.FavoriteSlashCommands = _settings.Llm.FavoriteSlashCommands.Take(_settings.Llm.MaxFavoriteSlashCommands).ToList();
|
||||
if (_settings.Llm.RecentSlashCommands.Count > _settings.Llm.MaxRecentSlashCommands)
|
||||
_settings.Llm.RecentSlashCommands = _settings.Llm.RecentSlashCommands.Take(_settings.Llm.MaxRecentSlashCommands).ToList();
|
||||
|
||||
NormalizeLlmThresholds(_settings.Llm);
|
||||
}
|
||||
|
||||
|
||||
17
src/AxCopilot/Themes/AgentClawDark.xaml
Normal file
17
src/AxCopilot/Themes/AgentClawDark.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#1A1714"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#211D19"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#2B241E"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#332A22"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#F4EEE7"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#C5B6A6"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#9F8D7C"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#D28A54"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#45382D"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#251E18"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#F0BB8A"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#493B2F"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#6A5848"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#99000000"/>
|
||||
</ResourceDictionary>
|
||||
17
src/AxCopilot/Themes/AgentClawLight.xaml
Normal file
17
src/AxCopilot/Themes/AgentClawLight.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#FBF8F1"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#FFFDF8"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#F2EADF"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#F6EEE3"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#1F1A16"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#6F6257"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#8E7E70"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#C9733D"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#E7DCCD"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#F6EEE5"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#9A5528"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#E5D8C7"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#CDB9A5"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#22000000"/>
|
||||
</ResourceDictionary>
|
||||
17
src/AxCopilot/Themes/AgentClawSystem.xaml
Normal file
17
src/AxCopilot/Themes/AgentClawSystem.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#F6F1E8"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#FFFDF9"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#EFE5D8"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#F3EADC"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#211B16"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#6E5F53"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#907F70"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#C76B37"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#E0D3C3"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#F4EBDD"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#995326"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#E3D7C8"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#C9B6A2"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#26000000"/>
|
||||
</ResourceDictionary>
|
||||
17
src/AxCopilot/Themes/AgentCodexDark.xaml
Normal file
17
src/AxCopilot/Themes/AgentCodexDark.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#0B1020"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#111827"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#18233B"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#132033"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#E5E7EB"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#94A3B8"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#64748B"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#60A5FA"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#243041"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#0F172A"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#93C5FD"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#2A3950"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#475569"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#99000000"/>
|
||||
</ResourceDictionary>
|
||||
17
src/AxCopilot/Themes/AgentCodexLight.xaml
Normal file
17
src/AxCopilot/Themes/AgentCodexLight.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#FCFCFD"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#EEF2FF"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#F5F7FB"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#111827"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#667085"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#98A2B3"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#2563EB"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#E4E7EC"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#F8FAFC"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#1D4ED8"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#E4E7EC"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#B8C0CC"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#22000000"/>
|
||||
</ResourceDictionary>
|
||||
17
src/AxCopilot/Themes/AgentCodexSystem.xaml
Normal file
17
src/AxCopilot/Themes/AgentCodexSystem.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#F8FAFC"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#E9EEF9"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#F2F6FB"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#0F172A"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#64748B"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#94A3B8"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#3B82F6"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#D9E2EC"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#EFF6FF"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#1D4ED8"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#D9E2EC"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#A7B3C3"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#22000000"/>
|
||||
</ResourceDictionary>
|
||||
17
src/AxCopilot/Themes/AgentDark.xaml
Normal file
17
src/AxCopilot/Themes/AgentDark.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#0F172A"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#111827"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#1E293B"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#1F2937"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#E5E7EB"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#94A3B8"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#64748B"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#38BDF8"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#334155"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#0B1220"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#93C5FD"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#334155"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#475569"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#8B000000"/>
|
||||
</ResourceDictionary>
|
||||
17
src/AxCopilot/Themes/AgentLight.xaml
Normal file
17
src/AxCopilot/Themes/AgentLight.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#F8FAFC"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#E2E8F0"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#EAF2FF"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#0F172A"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#475569"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#64748B"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#0284C7"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#CBD5E1"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#EFF6FF"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#1D4ED8"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#CBD5E1"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#94A3B8"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#33000000"/>
|
||||
</ResourceDictionary>
|
||||
17
src/AxCopilot/Themes/AgentSlateDark.xaml
Normal file
17
src/AxCopilot/Themes/AgentSlateDark.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#10141F"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#161C2B"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#20283B"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#1C2436"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#E6EAF2"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#9AA4B2"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#6B7280"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#818CF8"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#2A3448"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#161E33"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#A5B4FC"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#2E3950"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#4B5563"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#99000000"/>
|
||||
</ResourceDictionary>
|
||||
17
src/AxCopilot/Themes/AgentSlateLight.xaml
Normal file
17
src/AxCopilot/Themes/AgentSlateLight.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#F6F7FB"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#E8ECF7"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#EEF2F8"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#172033"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#667085"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#98A2B3"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#4F46E5"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#D8DFEA"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#EEF2FF"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#4338CA"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#D8DFEA"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#B3BDD0"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#22000000"/>
|
||||
</ResourceDictionary>
|
||||
17
src/AxCopilot/Themes/AgentSystem.xaml
Normal file
17
src/AxCopilot/Themes/AgentSystem.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="LauncherBackground" Color="#111827"/>
|
||||
<SolidColorBrush x:Key="ItemBackground" Color="#1F2937"/>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackground" Color="#243244"/>
|
||||
<SolidColorBrush x:Key="ItemHoverBackground" Color="#2A3748"/>
|
||||
<SolidColorBrush x:Key="PrimaryText" Color="#F1F5F9"/>
|
||||
<SolidColorBrush x:Key="SecondaryText" Color="#9CA3AF"/>
|
||||
<SolidColorBrush x:Key="PlaceholderText" Color="#6B7280"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#22D3EE"/>
|
||||
<SolidColorBrush x:Key="SeparatorColor" Color="#374151"/>
|
||||
<SolidColorBrush x:Key="HintBackground" Color="#0F172A"/>
|
||||
<SolidColorBrush x:Key="HintText" Color="#A5F3FC"/>
|
||||
<SolidColorBrush x:Key="BorderColor" Color="#4B5563"/>
|
||||
<SolidColorBrush x:Key="ScrollbarThumb" Color="#4B5563"/>
|
||||
<SolidColorBrush x:Key="ShadowColor" Color="#8B000000"/>
|
||||
</ResourceDictionary>
|
||||
@@ -178,6 +178,7 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
private string _vllmEndpoint = "";
|
||||
private string _vllmApiKey = "";
|
||||
private string _vllmModel = "";
|
||||
private bool _vllmAllowInsecureTls;
|
||||
private string _geminiApiKey = "";
|
||||
private string _geminiModel = "gemini-2.5-flash";
|
||||
private string _sigmoidApiKey = "";
|
||||
@@ -215,6 +216,7 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
public string VllmEndpoint { get => _vllmEndpoint; set { _vllmEndpoint = value; OnPropertyChanged(); } }
|
||||
public string VllmApiKey { get => _vllmApiKey; set { _vllmApiKey = value; OnPropertyChanged(); } }
|
||||
public string VllmModel { get => _vllmModel; set { _vllmModel = value; OnPropertyChanged(); } }
|
||||
public bool VllmAllowInsecureTls { get => _vllmAllowInsecureTls; set { _vllmAllowInsecureTls = value; OnPropertyChanged(); } }
|
||||
|
||||
// ── Gemini 설정 ──
|
||||
public string GeminiApiKey { get => _geminiApiKey; set { _geminiApiKey = value; OnPropertyChanged(); } }
|
||||
@@ -285,6 +287,20 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
set { _maxRetryOnError = Math.Clamp(value, 0, 10); OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
private bool _enableProactiveContextCompact;
|
||||
public bool EnableProactiveContextCompact
|
||||
{
|
||||
get => _enableProactiveContextCompact;
|
||||
set { _enableProactiveContextCompact = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
private int _contextCompactTriggerPercent;
|
||||
public int ContextCompactTriggerPercent
|
||||
{
|
||||
get => _contextCompactTriggerPercent;
|
||||
set { _contextCompactTriggerPercent = Math.Clamp(value, 50, 95); OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
private string _agentLogLevel;
|
||||
public string AgentLogLevel
|
||||
{
|
||||
@@ -292,6 +308,22 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
set { _agentLogLevel = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
private string _agentUiExpressionLevel = "balanced";
|
||||
public string AgentUiExpressionLevel
|
||||
{
|
||||
get => _agentUiExpressionLevel;
|
||||
set
|
||||
{
|
||||
_agentUiExpressionLevel = (value ?? "balanced").Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"rich" => "rich",
|
||||
"simple" => "simple",
|
||||
_ => "balanced",
|
||||
};
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _planDiffSeverityMediumCount = 2;
|
||||
public int PlanDiffSeverityMediumCount
|
||||
{
|
||||
@@ -479,6 +511,20 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
set { _slashPopupPageSize = Math.Clamp(value, 3, 10); OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
private int _maxFavoriteSlashCommands = 10;
|
||||
public int MaxFavoriteSlashCommands
|
||||
{
|
||||
get => _maxFavoriteSlashCommands;
|
||||
set { _maxFavoriteSlashCommands = Math.Clamp(value, 1, 30); OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
private int _maxRecentSlashCommands = 20;
|
||||
public int MaxRecentSlashCommands
|
||||
{
|
||||
get => _maxRecentSlashCommands;
|
||||
set { _maxRecentSlashCommands = Math.Clamp(value, 5, 50); OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
// ── 드래그&드롭 AI ──
|
||||
private bool _enableDragDropAiActions = true;
|
||||
public bool EnableDragDropAiActions
|
||||
@@ -988,7 +1034,17 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
_autoPreview = llm.AutoPreview;
|
||||
_maxAgentIterations = llm.MaxAgentIterations > 0 ? llm.MaxAgentIterations : 25;
|
||||
_maxRetryOnError = llm.MaxRetryOnError > 0 ? llm.MaxRetryOnError : 3;
|
||||
_enableProactiveContextCompact = llm.EnableProactiveContextCompact;
|
||||
_contextCompactTriggerPercent = llm.ContextCompactTriggerPercent > 0
|
||||
? Math.Clamp(llm.ContextCompactTriggerPercent, 50, 95)
|
||||
: 80;
|
||||
_agentLogLevel = llm.AgentLogLevel;
|
||||
_agentUiExpressionLevel = (llm.AgentUiExpressionLevel ?? "balanced").Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"rich" => "rich",
|
||||
"simple" => "simple",
|
||||
_ => "balanced",
|
||||
};
|
||||
_planDiffSeverityMediumCount = llm.PlanDiffSeverityMediumCount > 0 ? Math.Clamp(llm.PlanDiffSeverityMediumCount, 1, 20) : 2;
|
||||
_planDiffSeverityHighCount = llm.PlanDiffSeverityHighCount > 0 ? Math.Clamp(llm.PlanDiffSeverityHighCount, 1, 30) : 5;
|
||||
_planDiffSeverityMediumRatioPercent = llm.PlanDiffSeverityMediumRatioPercent > 0 ? Math.Clamp(llm.PlanDiffSeverityMediumRatioPercent, 1, 100) : 25;
|
||||
@@ -1013,6 +1069,8 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
_enableForkSkillDelegationEnforcement = llm.EnableForkSkillDelegationEnforcement;
|
||||
_skillsFolderPath = llm.SkillsFolderPath;
|
||||
_slashPopupPageSize = llm.SlashPopupPageSize > 0 ? Math.Clamp(llm.SlashPopupPageSize, 3, 10) : 6;
|
||||
_maxFavoriteSlashCommands = llm.MaxFavoriteSlashCommands > 0 ? Math.Clamp(llm.MaxFavoriteSlashCommands, 1, 30) : 10;
|
||||
_maxRecentSlashCommands = llm.MaxRecentSlashCommands > 0 ? Math.Clamp(llm.MaxRecentSlashCommands, 5, 50) : 20;
|
||||
_enableDragDropAiActions = llm.EnableDragDropAiActions;
|
||||
_dragDropAutoSend = llm.DragDropAutoSend;
|
||||
_enableCodeReview = llm.Code.EnableCodeReview;
|
||||
@@ -1033,6 +1091,7 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
_ollamaModel = llm.OllamaModel;
|
||||
_vllmEndpoint = llm.VllmEndpoint;
|
||||
_vllmModel = llm.VllmModel;
|
||||
_vllmAllowInsecureTls = llm.VllmAllowInsecureTls;
|
||||
_geminiModel = string.IsNullOrEmpty(llm.GeminiModel) ? "gemini-2.5-flash" : llm.GeminiModel;
|
||||
_sigmoidModel = string.IsNullOrEmpty(llm.ClaudeModel) ? "cl" + "aude-sonnet-4-6" : llm.ClaudeModel;
|
||||
|
||||
@@ -1087,6 +1146,7 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
Service = rm.Service,
|
||||
Endpoint = rm.Endpoint,
|
||||
ApiKey = rm.ApiKey,
|
||||
AllowInsecureTls = rm.AllowInsecureTls,
|
||||
AuthType = rm.AuthType ?? "bearer",
|
||||
Cp4dUrl = rm.Cp4dUrl ?? "",
|
||||
Cp4dUsername = rm.Cp4dUsername ?? "",
|
||||
@@ -1412,7 +1472,10 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
s.Llm.AutoPreview = _autoPreview;
|
||||
s.Llm.MaxAgentIterations = _maxAgentIterations;
|
||||
s.Llm.MaxRetryOnError = _maxRetryOnError;
|
||||
s.Llm.EnableProactiveContextCompact = _enableProactiveContextCompact;
|
||||
s.Llm.ContextCompactTriggerPercent = _contextCompactTriggerPercent;
|
||||
s.Llm.AgentLogLevel = _agentLogLevel;
|
||||
s.Llm.AgentUiExpressionLevel = _agentUiExpressionLevel;
|
||||
s.Llm.PlanDiffSeverityMediumCount = _planDiffSeverityMediumCount;
|
||||
s.Llm.PlanDiffSeverityHighCount = _planDiffSeverityHighCount;
|
||||
s.Llm.PlanDiffSeverityMediumRatioPercent = _planDiffSeverityMediumRatioPercent;
|
||||
@@ -1437,6 +1500,8 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
s.Llm.EnableForkSkillDelegationEnforcement = _enableForkSkillDelegationEnforcement;
|
||||
s.Llm.SkillsFolderPath = _skillsFolderPath;
|
||||
s.Llm.SlashPopupPageSize = _slashPopupPageSize;
|
||||
s.Llm.MaxFavoriteSlashCommands = _maxFavoriteSlashCommands;
|
||||
s.Llm.MaxRecentSlashCommands = _maxRecentSlashCommands;
|
||||
s.Llm.EnableDragDropAiActions = _enableDragDropAiActions;
|
||||
s.Llm.DragDropAutoSend = _dragDropAutoSend;
|
||||
s.Llm.Code.EnableCodeReview = _enableCodeReview;
|
||||
@@ -1457,6 +1522,7 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
s.Llm.OllamaModel = _ollamaModel;
|
||||
s.Llm.VllmEndpoint = _vllmEndpoint;
|
||||
s.Llm.VllmModel = _vllmModel;
|
||||
s.Llm.VllmAllowInsecureTls = _vllmAllowInsecureTls;
|
||||
s.Llm.GeminiModel = _geminiModel;
|
||||
s.Llm.ClaudeModel = _sigmoidModel;
|
||||
s.Llm.GeminiApiKey = _geminiApiKey;
|
||||
@@ -1502,6 +1568,7 @@ public class SettingsViewModel : INotifyPropertyChanged
|
||||
Service = rm.Service,
|
||||
Endpoint = rm.Endpoint,
|
||||
ApiKey = rm.ApiKey,
|
||||
AllowInsecureTls = rm.AllowInsecureTls,
|
||||
AuthType = rm.AuthType ?? "bearer",
|
||||
Cp4dUrl = rm.Cp4dUrl ?? "",
|
||||
Cp4dUsername = rm.Cp4dUsername ?? "",
|
||||
@@ -1820,6 +1887,7 @@ public class RegisteredModelRow : INotifyPropertyChanged
|
||||
private string _service = "ollama";
|
||||
private string _endpoint = "";
|
||||
private string _apiKey = "";
|
||||
private bool _allowInsecureTls;
|
||||
|
||||
/// <summary>UI 표시용 별칭</summary>
|
||||
public string Alias
|
||||
@@ -1856,6 +1924,13 @@ public class RegisteredModelRow : INotifyPropertyChanged
|
||||
set { _apiKey = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
/// <summary>이 모델 요청 시 TLS 인증서 검증을 생략합니다.</summary>
|
||||
public bool AllowInsecureTls
|
||||
{
|
||||
get => _allowInsecureTls;
|
||||
set { _allowInsecureTls = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
// ── CP4D 인증 필드 ──────────────────────────────────────────────────
|
||||
|
||||
private string _authType = "bearer";
|
||||
|
||||
498
src/AxCopilot/Views/AgentSettingsWindow.xaml
Normal file
498
src/AxCopilot/Views/AgentSettingsWindow.xaml
Normal file
@@ -0,0 +1,498 @@
|
||||
<Window x:Class="AxCopilot.Views.AgentSettingsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="AX Agent 설정"
|
||||
Width="780"
|
||||
Height="760"
|
||||
MinWidth="700"
|
||||
MinHeight="580"
|
||||
WindowStyle="None"
|
||||
ResizeMode="CanResizeWithGrip"
|
||||
Background="{DynamicResource LauncherBackground}"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ShowInTaskbar="False">
|
||||
<Window.Resources>
|
||||
<Style x:Key="OutlineHoverBtn" TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BorderColor}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryText}"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Padding" Value="9,5"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="8"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ToggleSwitch" TargetType="CheckBox">
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="CheckBox">
|
||||
<Grid Width="46" Height="26" VerticalAlignment="Center">
|
||||
<Border x:Name="Track"
|
||||
Width="46"
|
||||
Height="26"
|
||||
CornerRadius="13"
|
||||
Background="#D0D0E0"/>
|
||||
<Ellipse x:Name="Thumb"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Margin="3,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Fill="White">
|
||||
<Ellipse.Effect>
|
||||
<DropShadowEffect BlurRadius="5" ShadowDepth="1" Opacity="0.25" Direction="270"/>
|
||||
</Ellipse.Effect>
|
||||
</Ellipse>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Track" Property="Background" Value="{DynamicResource AccentColor}"/>
|
||||
<Setter TargetName="Thumb" Property="HorizontalAlignment" Value="Right"/>
|
||||
<Setter TargetName="Thumb" Property="Margin" Value="0,0,3,0"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Border Background="{DynamicResource LauncherBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="56"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource SeparatorColor}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<Grid Margin="14,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="AX Agent 설정"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Border Grid.Column="1"
|
||||
Cursor="Hand"
|
||||
Padding="8,4"
|
||||
CornerRadius="8"
|
||||
Background="Transparent"
|
||||
MouseLeftButtonUp="BtnClose_MouseLeftButtonUp">
|
||||
<TextBlock Text="닫기"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
FontSize="12"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="18,14,18,16">
|
||||
<TextBlock Text="테마"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<WrapPanel Margin="0,8,0,0">
|
||||
<Border x:Name="ThemeSystemCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="ThemeSystemCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="System" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
<Border x:Name="ThemeLightCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="ThemeLightCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="Light" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
<Border x:Name="ThemeDarkCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="ThemeDarkCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="Dark" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
|
||||
<Border Height="1" Margin="0,10,0,10" Background="{DynamicResource SeparatorColor}"/>
|
||||
|
||||
<TextBlock Text="모델 및 연결"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<TextBlock Text="서비스를 선택하고 모델, 연결 옵션, 운영 모드를 조정합니다."
|
||||
Margin="0,4,0,10"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
<WrapPanel>
|
||||
<Border x:Name="SvcOllamaCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="SvcOllamaCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="Ollama" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
<Border x:Name="SvcVllmCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="SvcVllmCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="vLLM" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
<Border x:Name="SvcGeminiCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="SvcGeminiCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="Gemini" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
<Border x:Name="SvcClaudeCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="SvcClaudeCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="Claude" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
<TextBox x:Name="ModelInput"
|
||||
Margin="0,6,0,8"
|
||||
Padding="8,6"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
FontSize="12"/>
|
||||
<WrapPanel x:Name="ModelChipPanel" Margin="0,0,0,8"/>
|
||||
<Grid Margin="0,6,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="vLLM SSL 인증서 검증 우회 허용"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<CheckBox x:Name="ChkVllmAllowInsecureTls"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="운영 모드"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button x:Name="BtnOperationMode"
|
||||
Grid.Column="1"
|
||||
MinWidth="140"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Click="BtnOperationMode_Click"/>
|
||||
</Grid>
|
||||
|
||||
<Border Height="1" Margin="0,10,0,10" Background="{DynamicResource SeparatorColor}"/>
|
||||
|
||||
<TextBlock Text="권한 및 실행"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="권한 모드"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button x:Name="BtnPermissionMode"
|
||||
Grid.Column="1"
|
||||
MinWidth="120"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Click="BtnPermissionMode_Click"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="계획 모드"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button x:Name="BtnPlanMode"
|
||||
Grid.Column="1"
|
||||
MinWidth="120"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Click="BtnPlanMode_Click"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="추론 강도"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button x:Name="BtnReasoningMode"
|
||||
Grid.Column="1"
|
||||
MinWidth="120"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Click="BtnReasoningMode_Click"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="폴더 데이터 활용"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button x:Name="BtnFolderDataUsage"
|
||||
Grid.Column="1"
|
||||
MinWidth="120"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Click="BtnFolderDataUsage_Click"/>
|
||||
</Grid>
|
||||
|
||||
<StackPanel x:Name="AdvancedPanel">
|
||||
<Border Height="1" Margin="0,10,0,10" Background="{DynamicResource SeparatorColor}"/>
|
||||
|
||||
<TextBlock Text="컨텍스트 및 오류 관리"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="자동 컨텍스트 압축 사용"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<CheckBox x:Name="ChkEnableProactiveCompact"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="압축 시작 한도(%)"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox x:Name="TxtContextCompactTriggerPercent"
|
||||
Grid.Column="1"
|
||||
Padding="8,5"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
FontSize="12"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="최대 컨텍스트 토큰"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox x:Name="TxtMaxContextTokens"
|
||||
Grid.Column="1"
|
||||
Padding="8,5"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
FontSize="12"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="오류 재시도 횟수"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox x:Name="TxtMaxRetryOnError"
|
||||
Grid.Column="1"
|
||||
Padding="8,5"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
FontSize="12"/>
|
||||
</Grid>
|
||||
|
||||
<Border Height="1" Margin="0,10,0,10" Background="{DynamicResource SeparatorColor}"/>
|
||||
|
||||
<TextBlock Text="도구 및 검증"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="스킬 시스템"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableSkillSystem"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="도구 훅 사용"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableToolHooks"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="입력 변형 반영"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableHookInputMutation"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="권한 갱신 반영"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableHookPermissionUpdate"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Cowork 검증 강제"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableCoworkVerification"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Code 검증 강제"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableCodeVerification"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="병렬 도구 실행"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableParallelTools"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<Border Grid.Row="2"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource SeparatorColor}"
|
||||
BorderThickness="0,1,0,0">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,14,0">
|
||||
<Button Content="고급 설정 열기"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Margin="0,0,8,0"
|
||||
Click="BtnOpenFullSettings_Click"/>
|
||||
<Button Content="저장"
|
||||
MinWidth="96"
|
||||
Margin="0,0,8,0"
|
||||
Background="{DynamicResource AccentColor}"
|
||||
Foreground="White"
|
||||
BorderThickness="0"
|
||||
Padding="10,6"
|
||||
Cursor="Hand"
|
||||
Click="BtnSave_Click"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -112,6 +112,36 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="SettingsNavBtn" TargetType="RadioButton">
|
||||
<Setter Property="Foreground" Value="{DynamicResource SecondaryText}"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Padding" Value="14,10"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="RadioButton">
|
||||
<Border x:Name="Bd"
|
||||
Background="Transparent"
|
||||
CornerRadius="10"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryText}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource HintBackground}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryText}"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<!-- 모든 드롭다운 메뉴는 코드-비하인드에서 커스텀 Popup으로 렌더링 -->
|
||||
</Window.Resources>
|
||||
|
||||
@@ -185,7 +215,9 @@
|
||||
<!-- 좌측: 사이드바 -->
|
||||
<!-- ══════════════════════════════════════════════════════ -->
|
||||
<Border x:Name="SidebarPanel" Grid.Column="1"
|
||||
Background="{DynamicResource ItemBackground}">
|
||||
Background="{DynamicResource LauncherBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="0,0,1,0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="52"/>
|
||||
@@ -198,14 +230,14 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 헤더: 로고 + 새 대화 -->
|
||||
<Grid Grid.Row="0" Margin="14,0">
|
||||
<Grid Grid.Row="0" Margin="16,0">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Border Background="{DynamicResource AccentColor}" CornerRadius="6"
|
||||
Width="24" Height="24">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="12"
|
||||
<Border Background="{DynamicResource AccentColor}" CornerRadius="8"
|
||||
Width="28" Height="28">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="14"
|
||||
Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock Text="AX Agent" FontSize="13.5" FontWeight="SemiBold"
|
||||
<TextBlock Text="AX Agent" FontSize="14.5" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center" Margin="10,0,0,0"/>
|
||||
</StackPanel>
|
||||
@@ -219,8 +251,9 @@
|
||||
</Grid>
|
||||
|
||||
<!-- 검색 -->
|
||||
<Border Grid.Row="1" Background="{DynamicResource HintBackground}"
|
||||
CornerRadius="10" Margin="12,0,12,6" Padding="10,6">
|
||||
<Border Grid.Row="1" Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1"
|
||||
CornerRadius="14" Margin="12,2,12,8" Padding="12,7">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -299,12 +332,12 @@
|
||||
</Border>
|
||||
|
||||
<!-- 탭별 좌측 메뉴 -->
|
||||
<Border Grid.Row="3" Margin="12,0,12,6"
|
||||
Background="{DynamicResource HintBackground}"
|
||||
<Border Grid.Row="3" Margin="12,0,12,8"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="10"
|
||||
Padding="10,8">
|
||||
CornerRadius="14"
|
||||
Padding="12,10">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,4" Visibility="Collapsed">
|
||||
<TextBlock x:Name="SidebarModeBadgeIcon" Text=""
|
||||
@@ -648,6 +681,21 @@
|
||||
<Border Grid.Row="0" Background="{DynamicResource LauncherBackground}"
|
||||
BorderBrush="{DynamicResource SeparatorColor}" BorderThickness="0,0,0,1">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0"
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center"
|
||||
Margin="46,0,0,0">
|
||||
<TextBlock x:Name="CurrentTabTitle"
|
||||
Text="AX Agent"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
</StackPanel>
|
||||
<!-- 좌: 사이드바 토글 -->
|
||||
<Button x:Name="BtnToggleSidebar" Style="{StaticResource GhostBtn}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center" Margin="8,0"
|
||||
@@ -658,9 +706,10 @@
|
||||
</Button>
|
||||
|
||||
<!-- 중앙: 탭 메뉴 -->
|
||||
<Border HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
<Border Grid.Column="1"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Background="{DynamicResource HintBackground}"
|
||||
CornerRadius="10" Padding="3"
|
||||
CornerRadius="12" Padding="4"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<RadioButton x:Name="TabChat" Content="Chat" Style="{StaticResource TopTabBtn}"
|
||||
@@ -676,7 +725,8 @@
|
||||
</Border>
|
||||
|
||||
<!-- 우: 최소화 + 최대화 + 닫기 -->
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right"
|
||||
<StackPanel Grid.Column="2"
|
||||
Orientation="Horizontal" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center" Margin="0,0,4,0"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<Button Style="{StaticResource GhostBtn}" Click="BtnMinimize_Click" ToolTip="최소화">
|
||||
@@ -744,9 +794,10 @@
|
||||
<ScrollViewer x:Name="MessageScroll" Grid.Row="3"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
Background="{DynamicResource LauncherBackground}">
|
||||
Background="{DynamicResource LauncherBackground}"
|
||||
Padding="22,20,22,0">
|
||||
<StackPanel x:Name="MessagePanel"
|
||||
Margin="0,34,0,22"
|
||||
Margin="0,20,0,28"
|
||||
HorizontalAlignment="Stretch">
|
||||
<StackPanel.RenderTransform>
|
||||
<TranslateTransform/>
|
||||
@@ -757,7 +808,7 @@
|
||||
<!-- 빈 상태 -->
|
||||
<StackPanel x:Name="EmptyState" Grid.Row="3"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
MaxWidth="520">
|
||||
MaxWidth="660">
|
||||
|
||||
<!-- 부유 AI 아이콘 -->
|
||||
<Border x:Name="EmptyIcon" CornerRadius="24" Width="72" Height="72"
|
||||
@@ -791,11 +842,11 @@
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock x:Name="EmptyStateTitle" Text="작업 유형을 선택하세요" FontSize="18" FontWeight="SemiBold"
|
||||
<TextBlock x:Name="EmptyStateTitle" Text="작업 유형을 선택하세요" FontSize="22" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}" HorizontalAlignment="Center"/>
|
||||
<TextBlock x:Name="EmptyStateDesc" Text="주제에 맞는 전문 프리셋이 자동 적용됩니다"
|
||||
FontSize="12" Foreground="{DynamicResource SecondaryText}"
|
||||
HorizontalAlignment="Center" Margin="0,8,0,24"/>
|
||||
FontSize="12.5" Foreground="{DynamicResource SecondaryText}"
|
||||
HorizontalAlignment="Center" Margin="0,10,0,28"/>
|
||||
|
||||
<!-- 대화 주제 버튼 (프리셋에서 동적 생성) -->
|
||||
<WrapPanel x:Name="TopicButtonPanel" HorizontalAlignment="Center"
|
||||
@@ -992,15 +1043,18 @@
|
||||
</Border>
|
||||
|
||||
<!-- ── 입력 바 ── -->
|
||||
<Border Grid.Row="4" Margin="48,0,48,20">
|
||||
<Border Grid.Row="4"
|
||||
Margin="28,0,28,22"
|
||||
HorizontalAlignment="Center"
|
||||
MaxWidth="980">
|
||||
<StackPanel>
|
||||
<Border x:Name="DraftPreviewCard"
|
||||
Visibility="Collapsed"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
Background="{DynamicResource LauncherBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="18"
|
||||
Padding="12,10,12,10"
|
||||
CornerRadius="22"
|
||||
Padding="14,12,14,12"
|
||||
Margin="0,0,0,10">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="14" ShadowDepth="0" Opacity="0.06"/>
|
||||
@@ -1056,7 +1110,7 @@
|
||||
<StackPanel x:Name="DraftQueuePanel"
|
||||
Visibility="Collapsed"
|
||||
Margin="0,0,0,10"/>
|
||||
<Border x:Name="InputGlowBorder" CornerRadius="26" Opacity="0"
|
||||
<Border x:Name="InputGlowBorder" CornerRadius="30" Opacity="0"
|
||||
Margin="-3" IsHitTestVisible="False">
|
||||
<Border.BorderBrush>
|
||||
<LinearGradientBrush x:Name="RainbowBrush" StartPoint="0,0" EndPoint="1,1">
|
||||
@@ -1078,11 +1132,11 @@
|
||||
</Border>
|
||||
<!-- 실제 입력 영역 -->
|
||||
<Border x:Name="InputBorder"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
CornerRadius="22" Padding="6"
|
||||
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1">
|
||||
Background="{DynamicResource LauncherBackground}"
|
||||
CornerRadius="28" Padding="8"
|
||||
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1.2">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="18" ShadowDepth="0" Opacity="0.08"/>
|
||||
<DropShadowEffect BlurRadius="22" ShadowDepth="0" Opacity="0.10"/>
|
||||
</Border.Effect>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -1093,7 +1147,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Row 0: 모델 셀렉터 + 템플릿 버튼 -->
|
||||
<Grid Grid.Row="0" Margin="5,1,5,2">
|
||||
<Grid Grid.Row="0" Margin="8,4,8,6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -1104,8 +1158,8 @@
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="0"
|
||||
MinHeight="26"
|
||||
Padding="9,2.5"
|
||||
MinHeight="30"
|
||||
Padding="11,4"
|
||||
Click="BtnModelSelector_Click"
|
||||
ToolTip="모델/추론 빠른 설정"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
@@ -1128,9 +1182,9 @@
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0"
|
||||
MinHeight="26"
|
||||
MinHeight="30"
|
||||
MinWidth="60"
|
||||
Padding="7,2.5"
|
||||
Padding="10,4"
|
||||
Click="BtnTemplateSelector_Click"
|
||||
ToolTip="프롬프트 템플릿"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
@@ -1151,9 +1205,9 @@
|
||||
<!-- Row 1: 통합 설정 패널 -->
|
||||
<Border x:Name="InlineSettingsPanel"
|
||||
Grid.Row="1"
|
||||
Margin="8,2,8,3"
|
||||
Padding="9,8,9,7"
|
||||
CornerRadius="12"
|
||||
Margin="8,2,8,6"
|
||||
Padding="11,10,11,9"
|
||||
CornerRadius="14"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
Background="{DynamicResource SecondaryBackground}"
|
||||
@@ -1275,7 +1329,7 @@
|
||||
</ItemsControl>
|
||||
|
||||
<!-- 입력 영역 (Row 3) -->
|
||||
<Grid Grid.Row="3">
|
||||
<Grid Grid.Row="3" Margin="2,0,2,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -1290,7 +1344,7 @@
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
Background="Transparent"
|
||||
CaretBrush="{DynamicResource AccentColor}"
|
||||
BorderThickness="0" Padding="12,8"
|
||||
BorderThickness="0" Padding="14,10"
|
||||
VerticalContentAlignment="Top"
|
||||
AcceptsReturn="True"
|
||||
PreviewKeyDown="InputBox_PreviewKeyDown"
|
||||
@@ -1330,7 +1384,7 @@
|
||||
|
||||
<!-- 파일 첨부 -->
|
||||
<Button x:Name="BtnAttach" Style="{StaticResource GhostBtn}" Grid.Column="1"
|
||||
Width="34" Height="34" Margin="0,0,2,0" VerticalAlignment="Bottom"
|
||||
Width="36" Height="36" Margin="0,0,4,0" VerticalAlignment="Bottom"
|
||||
Click="BtnAttach_Click" ToolTip="파일 첨부"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"
|
||||
AllowDrop="True">
|
||||
@@ -1340,7 +1394,7 @@
|
||||
|
||||
<!-- 내보내기 -->
|
||||
<Button Style="{StaticResource GhostBtn}" Grid.Column="2"
|
||||
Width="34" Height="34" Margin="0,0,2,0" VerticalAlignment="Bottom"
|
||||
Width="36" Height="36" Margin="0,0,4,0" VerticalAlignment="Bottom"
|
||||
Click="BtnExport_Click" ToolTip="대화 내보내기"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="14"
|
||||
@@ -1349,7 +1403,7 @@
|
||||
|
||||
<!-- 일시정지 -->
|
||||
<Border x:Name="BtnPause" Grid.Column="3"
|
||||
Width="32" Height="32" Margin="0,0,2,0"
|
||||
Width="34" Height="34" Margin="0,0,4,0"
|
||||
CornerRadius="8" Cursor="Hand"
|
||||
Background="{DynamicResource ItemHoverBackground}" Visibility="Collapsed"
|
||||
VerticalAlignment="Bottom"
|
||||
@@ -1363,7 +1417,7 @@
|
||||
|
||||
<!-- 중지/전송 -->
|
||||
<Button x:Name="BtnStop" Grid.Column="3"
|
||||
Width="38" Height="38" Margin="4,0,4,0"
|
||||
Width="40" Height="40" Margin="4,0,4,0"
|
||||
Cursor="Hand" Click="BtnStop_Click"
|
||||
VerticalAlignment="Bottom" Visibility="Collapsed"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
@@ -1378,7 +1432,7 @@
|
||||
</Button.Template>
|
||||
</Button>
|
||||
<Button x:Name="BtnSend" Grid.Column="4"
|
||||
Width="38" Height="38" Margin="4,0,4,0"
|
||||
Width="42" Height="42" Margin="6,0,4,0"
|
||||
Cursor="Hand" Click="BtnSend_Click"
|
||||
VerticalAlignment="Bottom">
|
||||
<Button.Template>
|
||||
@@ -1657,6 +1711,507 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- ══ AX Agent 설정 오버레이 (채팅 화면 내부 전체 덮기) ══ -->
|
||||
<Border x:Name="AgentSettingsOverlay"
|
||||
Grid.Column="2"
|
||||
Grid.ColumnSpan="3"
|
||||
Background="{DynamicResource LauncherBackground}"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="120">
|
||||
<Grid Margin="0">
|
||||
<Border Background="{DynamicResource LauncherBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="0"
|
||||
Padding="0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="220"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Grid.RowSpan="2"
|
||||
Grid.Column="0"
|
||||
Background="{DynamicResource LauncherBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="0,0,1,0">
|
||||
<StackPanel Margin="24,26,20,18">
|
||||
<TextBlock Text="AX Agent 설정"
|
||||
FontSize="26"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
Margin="0,0,0,8"/>
|
||||
<TextBlock Text="핵심 동작과 연결 설정을 이 화면에서 바로 관리합니다."
|
||||
FontSize="12.5"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
Margin="0,0,0,20"/>
|
||||
<TextBlock Text="환경"
|
||||
FontSize="11"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
Margin="6,0,0,10"/>
|
||||
<RadioButton x:Name="OverlayNavCommon"
|
||||
Content="일반"
|
||||
GroupName="OverlayNav"
|
||||
Style="{StaticResource SettingsNavBtn}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Checked="OverlayNav_Checked"
|
||||
Tag="common"
|
||||
IsChecked="True"/>
|
||||
<RadioButton x:Name="OverlayNavService"
|
||||
Content="서비스와 모델"
|
||||
GroupName="OverlayNav"
|
||||
Style="{StaticResource SettingsNavBtn}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="0,6,0,0"
|
||||
Checked="OverlayNav_Checked"
|
||||
Tag="service"/>
|
||||
<RadioButton x:Name="OverlayNavPermission"
|
||||
Content="권한과 실행"
|
||||
GroupName="OverlayNav"
|
||||
Style="{StaticResource SettingsNavBtn}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="0,6,0,0"
|
||||
Checked="OverlayNav_Checked"
|
||||
Tag="permission"/>
|
||||
<RadioButton x:Name="OverlayNavAdvanced"
|
||||
Content="고급"
|
||||
GroupName="OverlayNav"
|
||||
Style="{StaticResource SettingsNavBtn}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="0,6,0,0"
|
||||
Checked="OverlayNav_Checked"
|
||||
Tag="advanced"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Grid Grid.Row="0" Grid.Column="1" Margin="16,14,16,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Style="{StaticResource GhostBtn}"
|
||||
Padding="10,6"
|
||||
Click="BtnOverlaySettingsClose_Click"
|
||||
ToolTip="대화로 돌아가기">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
Margin="0,0,6,0"/>
|
||||
<TextBlock Text="설정"
|
||||
FontSize="12.5"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="16,0,0,0">
|
||||
<TextBlock Text="AX Agent 설정"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<TextBlock Text="채팅을 벗어나지 않고 AX Agent 전용 설정을 정리합니다"
|
||||
Margin="0,6,0,0"
|
||||
FontSize="12.5"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="2"
|
||||
Style="{StaticResource GhostBtn}"
|
||||
Padding="8,6"
|
||||
Click="BtnOverlaySettingsClose_Click"
|
||||
ToolTip="설정 닫기">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="1" Grid.Column="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
Padding="16,4,16,16">
|
||||
<StackPanel>
|
||||
<Border x:Name="OverlaySectionService"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Padding="12"
|
||||
Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<TextBlock Text="사용 서비스"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<TextBlock Text="대화에 사용할 LLM 서비스와 모델을 선택합니다."
|
||||
Margin="0,4,0,10"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
<WrapPanel Margin="0,0,0,8">
|
||||
<Border x:Name="OverlaySvcOllamaCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="OverlaySvcOllamaCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="Ollama" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
<Border x:Name="OverlaySvcVllmCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="OverlaySvcVllmCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="vLLM" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
<Border x:Name="OverlaySvcGeminiCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="OverlaySvcGeminiCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="Gemini" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
<Border x:Name="OverlaySvcClaudeCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="OverlaySvcClaudeCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="Claude" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Margin="0,0,6,0">
|
||||
<TextBlock Text="서비스"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
Margin="0,0,0,6"/>
|
||||
<ComboBox x:Name="CmbOverlayService"
|
||||
Height="30"
|
||||
SelectionChanged="CmbOverlayService_SelectionChanged"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Margin="6,0,0,0">
|
||||
<TextBlock Text="모델"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
Margin="0,0,0,6"/>
|
||||
<ComboBox x:Name="CmbOverlayModel"
|
||||
Height="30"
|
||||
SelectionChanged="CmbOverlayModel_SelectionChanged"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid x:Name="OverlayServiceDetailPanel" Margin="0,12,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Margin="0,0,6,0">
|
||||
<TextBlock x:Name="OverlayEndpointLabel"
|
||||
Text="기본 서버 주소"
|
||||
FontSize="11.5"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<TextBlock x:Name="OverlayEndpointHint"
|
||||
Text="선택한 서비스의 기본 연결 주소를 입력합니다."
|
||||
Margin="0,4,0,6"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
<TextBox x:Name="TxtOverlayServiceEndpoint"
|
||||
Padding="9,7"
|
||||
Background="{DynamicResource LauncherBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
FontSize="12"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Margin="6,0,0,0">
|
||||
<TextBlock x:Name="OverlayApiKeyLabel"
|
||||
Text="API 키"
|
||||
FontSize="11.5"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<TextBlock x:Name="OverlayApiKeyHint"
|
||||
Text="접근 제어가 필요한 경우에만 입력합니다."
|
||||
Margin="0,4,0,6"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
<PasswordBox x:Name="TxtOverlayServiceApiKey"
|
||||
Padding="9,7"
|
||||
Background="{DynamicResource LauncherBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
FontSize="12"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border x:Name="OverlaySectionQuick"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Padding="12">
|
||||
<StackPanel>
|
||||
<TextBlock Text="빠른 제어"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<TextBlock Text="Codex/Claude 스타일로 핵심 기능만 간결하게 제공합니다."
|
||||
Margin="0,4,0,10"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
<WrapPanel>
|
||||
<Button x:Name="BtnOverlayFastMode"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Margin="0,0,8,8"
|
||||
Padding="11,6"
|
||||
Content="Fast"
|
||||
Click="BtnInlineFastMode_Click"/>
|
||||
<Button x:Name="BtnOverlayReasoning"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Margin="0,0,8,8"
|
||||
Padding="11,6"
|
||||
Content="Reasoning"
|
||||
Click="BtnInlineReasoning_Click"/>
|
||||
<Button x:Name="BtnOverlayPlanMode"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Margin="0,0,8,8"
|
||||
Padding="11,6"
|
||||
Content="Plan"
|
||||
Click="BtnInlinePlanMode_Click"/>
|
||||
<Button x:Name="BtnOverlayPermission"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Margin="0,0,8,8"
|
||||
Padding="11,6"
|
||||
Content="권한"
|
||||
Click="BtnInlinePermission_Click"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border x:Name="OverlaySectionDetail"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Padding="12"
|
||||
Margin="0,10,0,0">
|
||||
<StackPanel>
|
||||
<TextBlock x:Name="OverlayAnchorCommon"
|
||||
Text="상세 설정"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<TextBlock Text="트레이의 AX Agent 설정 항목을 이 화면으로 통합합니다."
|
||||
Margin="0,4,0,10"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
<Grid x:Name="OverlayAiEnabledRow" Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel>
|
||||
<TextBlock Text="AX Agent 사용"
|
||||
FontSize="12.5"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<TextBlock Text="비활성화하면 AX Agent 대화 기능이 숨겨집니다."
|
||||
Margin="0,4,0,0"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="ChkOverlayAiEnabled"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<WrapPanel x:Name="OverlayThemePanel" Margin="0,0,0,8">
|
||||
<Border x:Name="OverlayThemeSystemCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="OverlayThemeSystemCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="System" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
<Border x:Name="OverlayThemeLightCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="OverlayThemeLightCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="Light" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
<Border x:Name="OverlayThemeDarkCard"
|
||||
Cursor="Hand"
|
||||
CornerRadius="10"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
Padding="10,8"
|
||||
Margin="0,0,8,8"
|
||||
MouseLeftButtonUp="OverlayThemeDarkCard_MouseLeftButtonUp">
|
||||
<TextBlock Text="Dark" FontSize="12" Foreground="{DynamicResource PrimaryText}"/>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
<StackPanel x:Name="OverlayModelEditorPanel">
|
||||
<TextBlock Text="등록 모델"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
Margin="0,4,0,6"/>
|
||||
<TextBox x:Name="TxtOverlayModelInput"
|
||||
Padding="8,6"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
FontSize="12"/>
|
||||
<WrapPanel x:Name="OverlayModelChipPanel" Margin="0,10,0,0"/>
|
||||
</StackPanel>
|
||||
<Grid x:Name="OverlayAnchorPermission" Margin="12,12,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="운영 모드"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button x:Name="BtnOverlayOperationMode"
|
||||
Grid.Column="1"
|
||||
MinWidth="140"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Click="BtnOverlayOperationMode_Click"/>
|
||||
</Grid>
|
||||
<Grid x:Name="OverlayFolderDataUsageRow" Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="폴더 데이터 활용"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button x:Name="BtnOverlayFolderDataUsage"
|
||||
Grid.Column="1"
|
||||
MinWidth="140"
|
||||
Style="{StaticResource OutlineHoverBtn}"
|
||||
Click="BtnOverlayFolderDataUsage_Click"/>
|
||||
</Grid>
|
||||
<Grid x:Name="OverlayTlsRow" Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="vLLM SSL 인증서 검증 우회 허용"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<CheckBox x:Name="ChkOverlayVllmAllowInsecureTls"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<Grid x:Name="OverlayAnchorAdvanced" Margin="0,14,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="압축 시작 한도(%)"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox x:Name="TxtOverlayContextCompactTriggerPercent"
|
||||
Grid.Column="1"
|
||||
Padding="8,5"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
FontSize="12"/>
|
||||
</Grid>
|
||||
<Grid x:Name="OverlayMaxContextTokensRow" Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="최대 컨텍스트 토큰"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox x:Name="TxtOverlayMaxContextTokens"
|
||||
Grid.Column="1"
|
||||
Padding="8,5"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
FontSize="12"/>
|
||||
</Grid>
|
||||
<Grid x:Name="OverlayMaxRetryRow" Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="오류 재시도 횟수"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox x:Name="TxtOverlayMaxRetryOnError"
|
||||
Grid.Column="1"
|
||||
Padding="8,5"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
Foreground="{DynamicResource PrimaryText}"
|
||||
FontSize="12"/>
|
||||
</Grid>
|
||||
<WrapPanel x:Name="OverlayAdvancedTogglePanel" Margin="0,14,0,0">
|
||||
<CheckBox x:Name="ChkOverlayEnableProactiveCompact" Content="자동 컨텍스트 압축" Margin="0,0,14,8"/>
|
||||
<CheckBox x:Name="ChkOverlayEnableSkillSystem" Content="스킬 시스템" Margin="0,0,14,8"/>
|
||||
<CheckBox x:Name="ChkOverlayEnableToolHooks" Content="도구 훅 사용" Margin="0,0,14,8"/>
|
||||
<CheckBox x:Name="ChkOverlayEnableHookInputMutation" Content="입력 변형 반영" Margin="0,0,14,8"/>
|
||||
<CheckBox x:Name="ChkOverlayEnableHookPermissionUpdate" Content="권한 갱신 반영" Margin="0,0,14,8"/>
|
||||
<CheckBox x:Name="ChkOverlayEnableCoworkVerification" Content="Cowork 검증" Margin="0,0,14,8"/>
|
||||
<CheckBox x:Name="ChkOverlayEnableCodeVerification" Content="Code 검증" Margin="0,0,14,8"/>
|
||||
<CheckBox x:Name="ChkOverlayEnableParallelTools" Content="병렬 도구 실행" Margin="0,0,14,8"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="{DynamicResource LauncherBackground}"
|
||||
Margin="0,12,0,0"
|
||||
Padding="0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="저장"
|
||||
Background="{DynamicResource AccentColor}"
|
||||
Foreground="White"
|
||||
BorderThickness="0"
|
||||
Padding="12,6"
|
||||
Cursor="Hand"
|
||||
Click="BtnOverlaySave_Click"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ══ 미리보기 스플리터 (Column 3) ══ -->
|
||||
<GridSplitter x:Name="PreviewSplitter" Grid.Column="3" Width="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace AxCopilot.Views;
|
||||
/// <summary>AX Agent 창. 데스크톱 코파일럿 스타일 — 사이드바 + 카테고리 분류 + 타임라인.</summary>
|
||||
public partial class ChatWindow : Window
|
||||
{
|
||||
private const string UnifiedAdminPassword = "axgo123!";
|
||||
private readonly SettingsService _settings;
|
||||
private readonly ChatStorageService _storage;
|
||||
private readonly DraftQueueProcessorService _draftQueueProcessor = new();
|
||||
@@ -76,7 +77,7 @@ public partial class ChatWindow : Window
|
||||
private readonly DispatcherTimer _typingTimer;
|
||||
private int _displayedLength; // 현재 화면에 표시된 글자 수
|
||||
private ResourceDictionary? _agentThemeDictionary;
|
||||
private AgentSettingsWindow? _agentSettingsWindow;
|
||||
private bool _isOverlaySettingsSyncing;
|
||||
|
||||
private sealed class ConversationMeta
|
||||
{
|
||||
@@ -984,6 +985,15 @@ public partial class ChatWindow : Window
|
||||
ApplyAgentThemeResources();
|
||||
ApplyExpressionLevelUi();
|
||||
ApplySidebarStateForActiveTab(animated: false);
|
||||
if (CurrentTabTitle != null)
|
||||
{
|
||||
CurrentTabTitle.Text = _activeTab switch
|
||||
{
|
||||
"Cowork" => "AX Agent · Cowork",
|
||||
"Code" => "AX Agent · Code",
|
||||
_ => "AX Agent · Chat",
|
||||
};
|
||||
}
|
||||
|
||||
// 폴더 바는 Cowork/Code 탭에서만 표시
|
||||
if (FolderBar != null)
|
||||
@@ -1892,9 +1902,9 @@ public partial class ChatWindow : Window
|
||||
return button;
|
||||
}
|
||||
|
||||
actionRow.Children.Add(CreateActionButton("활용하지 않음", "#FEF2F2", "#991B1B", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Deny)));
|
||||
actionRow.Children.Add(CreateActionButton("소극 활용", "#EEF2FF", "#1D4ED8", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Default)));
|
||||
actionRow.Children.Add(CreateActionButton("적극 활용", "#ECFDF5", "#166534", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.AcceptEdits)));
|
||||
actionRow.Children.Add(CreateActionButton("읽기 전용", "#FEF2F2", "#991B1B", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Deny)));
|
||||
actionRow.Children.Add(CreateActionButton("권한 요청", "#EEF2FF", "#1D4ED8", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Default)));
|
||||
actionRow.Children.Add(CreateActionButton("편집 자동 승인", "#ECFDF5", "#166534", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.AcceptEdits)));
|
||||
actionRow.Children.Add(CreateActionButton("예외 해제", "#F3F4F6", "#374151", () => SetToolPermissionOverride(latestDenied.ToolName!, null)));
|
||||
deniedStack.Children.Add(actionRow);
|
||||
}
|
||||
@@ -2117,7 +2127,6 @@ public partial class ChatWindow : Window
|
||||
BtnPermission_Click(this, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
private bool _permissionTopBannerDismissed; // 상단 권한 배너 닫힘 상태
|
||||
private string _lastPermissionBannerMode = "";
|
||||
|
||||
private bool GetPermissionPopupSectionExpanded(string sectionKey, bool defaultValue = false)
|
||||
@@ -2137,7 +2146,6 @@ public partial class ChatWindow : Window
|
||||
|
||||
private void BtnPermissionTopBannerClose_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_permissionTopBannerDismissed = true;
|
||||
if (PermissionTopBanner != null)
|
||||
PermissionTopBanner.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
@@ -2167,7 +2175,6 @@ public partial class ChatWindow : Window
|
||||
|
||||
if (!string.Equals(_lastPermissionBannerMode, perm, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_permissionTopBannerDismissed = false;
|
||||
_lastPermissionBannerMode = perm;
|
||||
}
|
||||
|
||||
@@ -2182,10 +2189,10 @@ public partial class ChatWindow : Window
|
||||
PermissionTopBanner.BorderBrush = BrushFromHex("#86EFAC");
|
||||
PermissionTopBannerIcon.Text = "\uE73E";
|
||||
PermissionTopBannerIcon.Foreground = activeColor;
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 적극 활용";
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 편집 자동 승인";
|
||||
PermissionTopBannerTitle.Foreground = BrushFromHex("#166534");
|
||||
PermissionTopBannerText.Text = "파일 편집 도구는 자동 승인하고, 명령 실행은 계속 확인합니다.";
|
||||
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
|
||||
PermissionTopBannerText.Text = "모든 파일 편집을 자동 승인합니다. 명령 실행은 계속 확인합니다.";
|
||||
PermissionTopBanner.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
else if (perm == PermissionModeCatalog.Deny)
|
||||
@@ -2198,10 +2205,10 @@ public partial class ChatWindow : Window
|
||||
PermissionTopBanner.BorderBrush = BrushFromHex("#86EFAC");
|
||||
PermissionTopBannerIcon.Text = "\uE73E";
|
||||
PermissionTopBannerIcon.Foreground = denyColor;
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 활용하지 않음";
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 읽기 전용";
|
||||
PermissionTopBannerTitle.Foreground = denyColor;
|
||||
PermissionTopBannerText.Text = "파일 읽기만 허용하고 생성/수정/삭제는 차단합니다.";
|
||||
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
|
||||
PermissionTopBanner.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
else if (perm == PermissionModeCatalog.BypassPermissions)
|
||||
@@ -2214,10 +2221,10 @@ public partial class ChatWindow : Window
|
||||
PermissionTopBanner.BorderBrush = BrushFromHex("#FDBA74");
|
||||
PermissionTopBannerIcon.Text = "\uE814";
|
||||
PermissionTopBannerIcon.Foreground = autoColor;
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 완전 자동";
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 권한 건너뛰기";
|
||||
PermissionTopBannerTitle.Foreground = autoColor;
|
||||
PermissionTopBannerText.Text = "권한 확인을 대부분 생략합니다. 민감한 작업 전에는 설정을 다시 확인하세요.";
|
||||
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
|
||||
PermissionTopBannerText.Text = "모든 권한을 허용합니다. 민감한 작업 전에는 설정을 다시 확인하세요.";
|
||||
PermissionTopBanner.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
else if (perm == PermissionModeCatalog.DontAsk)
|
||||
@@ -2233,7 +2240,7 @@ public partial class ChatWindow : Window
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 질문 없이 진행";
|
||||
PermissionTopBannerTitle.Foreground = dangerColor;
|
||||
PermissionTopBannerText.Text = "권한 확인을 거의 생략합니다. 민감한 작업 전에는 설정을 다시 확인하세요.";
|
||||
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
|
||||
PermissionTopBanner.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2253,20 +2260,20 @@ public partial class ChatWindow : Window
|
||||
PermissionTopBanner.BorderBrush = BrushFromHex("#C7D2FE");
|
||||
PermissionTopBannerIcon.Text = "\uE7C3";
|
||||
PermissionTopBannerIcon.Foreground = BrushFromHex("#4338CA");
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 계획 중심";
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 계획 모드";
|
||||
PermissionTopBannerTitle.Foreground = BrushFromHex("#4338CA");
|
||||
PermissionTopBannerText.Text = "쓰기 작업은 제한하고, 먼저 계획과 승인 흐름을 우선합니다.";
|
||||
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
|
||||
PermissionTopBannerText.Text = "변경 전에 계획을 먼저 만들고 승인 흐름을 우선합니다.";
|
||||
PermissionTopBanner.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else if (perm == PermissionModeCatalog.Default)
|
||||
{
|
||||
PermissionTopBanner.BorderBrush = BrushFromHex("#BFDBFE");
|
||||
PermissionTopBannerIcon.Text = "\uE8D7";
|
||||
PermissionTopBannerIcon.Foreground = BrushFromHex("#1D4ED8");
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 소극 활용";
|
||||
PermissionTopBannerTitle.Text = "현재 권한 모드 · 권한 요청";
|
||||
PermissionTopBannerTitle.Foreground = BrushFromHex("#1D4ED8");
|
||||
PermissionTopBannerText.Text = "변경 전 확인하고, 필요한 경우에만 파일 접근을 진행합니다.";
|
||||
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
|
||||
PermissionTopBannerText.Text = "변경하기 전에 항상 확인합니다.";
|
||||
PermissionTopBanner.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -5667,8 +5674,20 @@ public partial class ChatWindow : Window
|
||||
if (!_slashVisibleItemByAbsoluteIndex.TryGetValue(_slashPalette.SelectedIndex, out var item))
|
||||
return;
|
||||
|
||||
var bounds = item.TransformToAncestor(SlashScrollViewer)
|
||||
.TransformBounds(new Rect(0, 0, item.ActualWidth, item.ActualHeight));
|
||||
if (!IsVisualDescendantOf(item, SlashScrollViewer))
|
||||
return;
|
||||
|
||||
Rect bounds;
|
||||
try
|
||||
{
|
||||
bounds = item.TransformToAncestor(SlashScrollViewer)
|
||||
.TransformBounds(new Rect(0, 0, item.ActualWidth, item.ActualHeight));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 렌더 트리 갱신 중에는 transform이 실패할 수 있어 조용히 무시.
|
||||
return;
|
||||
}
|
||||
|
||||
if (bounds.Top < 0)
|
||||
SlashScrollViewer.ScrollToVerticalOffset(SlashScrollViewer.VerticalOffset + bounds.Top - 8);
|
||||
@@ -5676,6 +5695,22 @@ public partial class ChatWindow : Window
|
||||
SlashScrollViewer.ScrollToVerticalOffset(SlashScrollViewer.VerticalOffset + (bounds.Bottom - SlashScrollViewer.ViewportHeight) + 8);
|
||||
}
|
||||
|
||||
private static bool IsVisualDescendantOf(DependencyObject? child, DependencyObject? parent)
|
||||
{
|
||||
if (child == null || parent == null)
|
||||
return false;
|
||||
|
||||
var current = child;
|
||||
while (current != null)
|
||||
{
|
||||
if (ReferenceEquals(current, parent))
|
||||
return true;
|
||||
current = VisualTreeHelper.GetParent(current);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdateSlashSelectionVisualState()
|
||||
{
|
||||
if (_slashVisibleItemByAbsoluteIndex.Count == 0)
|
||||
@@ -12997,66 +13032,17 @@ public partial class ChatWindow : Window
|
||||
|
||||
private void OpenAgentSettingsWindow()
|
||||
{
|
||||
if (_agentSettingsWindow != null)
|
||||
RefreshOverlaySettingsPanel();
|
||||
AgentSettingsOverlay.Visibility = Visibility.Visible;
|
||||
InlineSettingsPanel.Visibility = Visibility.Collapsed;
|
||||
SetOverlaySection("common");
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_agentSettingsWindow.IsVisible)
|
||||
{
|
||||
_agentSettingsWindow.Activate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore stale window instance
|
||||
}
|
||||
_agentSettingsWindow = null;
|
||||
}
|
||||
|
||||
var win = new AgentSettingsWindow(_settings);
|
||||
if (IsLoaded && IsVisible)
|
||||
win.Owner = this;
|
||||
_agentSettingsWindow = win;
|
||||
win.Closed += (_, _) => _agentSettingsWindow = null;
|
||||
try
|
||||
{
|
||||
win.Resources.MergedDictionaries.Insert(0, new ResourceDictionary
|
||||
{
|
||||
Source = BuildAgentThemeDictionaryUri(),
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 테마 사전 로드 실패 시에도 설정창 자체는 열리도록 유지
|
||||
}
|
||||
|
||||
bool changed;
|
||||
try
|
||||
{
|
||||
changed = win.ShowDialog() == true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 모달 창 오픈에 실패하면 일반 창으로라도 설정 접근을 보장
|
||||
try
|
||||
{
|
||||
win.Show();
|
||||
win.Activate();
|
||||
}
|
||||
catch { }
|
||||
return;
|
||||
}
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
_appState.LoadFromSettings(_settings);
|
||||
ApplyAgentThemeResources();
|
||||
UpdateSidebarModeMenu();
|
||||
UpdateModelLabel();
|
||||
RefreshInlineSettingsPanel();
|
||||
UpdateTabUI();
|
||||
ShowToast("AX Agent 설정이 저장되었습니다.");
|
||||
if (CmbOverlayModel.Items.Count > 0)
|
||||
CmbOverlayModel.Focus();
|
||||
else
|
||||
InputBox.Focus();
|
||||
}, DispatcherPriority.Input);
|
||||
}
|
||||
|
||||
public void OpenAgentSettingsFromExternal()
|
||||
@@ -13069,6 +13055,515 @@ public partial class ChatWindow : Window
|
||||
}, DispatcherPriority.Input);
|
||||
}
|
||||
|
||||
private void BtnOverlaySettingsClose_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AgentSettingsOverlay.Visibility = Visibility.Collapsed;
|
||||
InputBox.Focus();
|
||||
}
|
||||
|
||||
private void OverlayNav_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not RadioButton rb || rb.Tag is not string tag)
|
||||
return;
|
||||
|
||||
SetOverlaySection(tag);
|
||||
}
|
||||
|
||||
private void SetOverlaySection(string tag)
|
||||
{
|
||||
if (OverlaySectionService == null || OverlaySectionQuick == null || OverlaySectionDetail == null)
|
||||
return;
|
||||
|
||||
var section = string.IsNullOrWhiteSpace(tag) ? "common" : tag.Trim().ToLowerInvariant();
|
||||
|
||||
OverlaySectionService.Visibility = section == "service" ? Visibility.Visible : Visibility.Collapsed;
|
||||
OverlaySectionQuick.Visibility = section == "permission" ? Visibility.Visible : Visibility.Collapsed;
|
||||
OverlaySectionDetail.Visibility = section == "service" ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
var showCommon = section == "common";
|
||||
var showPermission = section == "permission";
|
||||
var showAdvanced = section == "advanced";
|
||||
|
||||
if (OverlayAiEnabledRow != null)
|
||||
OverlayAiEnabledRow.Visibility = showCommon ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (OverlayThemePanel != null)
|
||||
OverlayThemePanel.Visibility = showCommon ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (OverlayModelEditorPanel != null)
|
||||
OverlayModelEditorPanel.Visibility = showCommon ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (OverlayAnchorPermission != null)
|
||||
OverlayAnchorPermission.Visibility = showPermission ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (OverlayFolderDataUsageRow != null)
|
||||
OverlayFolderDataUsageRow.Visibility = showCommon || showPermission ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (OverlayTlsRow != null)
|
||||
OverlayTlsRow.Visibility = showCommon || showPermission ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (OverlayAnchorAdvanced != null)
|
||||
OverlayAnchorAdvanced.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (OverlayMaxContextTokensRow != null)
|
||||
OverlayMaxContextTokensRow.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (OverlayMaxRetryRow != null)
|
||||
OverlayMaxRetryRow.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (OverlayAdvancedTogglePanel != null)
|
||||
OverlayAdvancedTogglePanel.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void RefreshOverlaySettingsPanel()
|
||||
{
|
||||
if (CmbOverlayService == null || CmbOverlayModel == null)
|
||||
return;
|
||||
|
||||
var llm = _settings.Settings.Llm;
|
||||
var service = (llm.Service ?? "ollama").ToLowerInvariant();
|
||||
var models = GetModelCandidates(service);
|
||||
|
||||
_isOverlaySettingsSyncing = true;
|
||||
try
|
||||
{
|
||||
CmbOverlayService.Items.Clear();
|
||||
foreach (var svc in new[] { "ollama", "vllm", "gemini", "claude" })
|
||||
{
|
||||
CmbOverlayService.Items.Add(new ComboBoxItem
|
||||
{
|
||||
Content = ServiceLabel(svc),
|
||||
Tag = svc
|
||||
});
|
||||
}
|
||||
CmbOverlayService.SelectedItem = CmbOverlayService.Items
|
||||
.OfType<ComboBoxItem>()
|
||||
.FirstOrDefault(i => string.Equals(i.Tag as string, service, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
CmbOverlayModel.Items.Clear();
|
||||
foreach (var model in models)
|
||||
{
|
||||
CmbOverlayModel.Items.Add(new ComboBoxItem
|
||||
{
|
||||
Content = model.Label,
|
||||
Tag = model.Id
|
||||
});
|
||||
}
|
||||
CmbOverlayModel.SelectedItem = CmbOverlayModel.Items
|
||||
.OfType<ComboBoxItem>()
|
||||
.FirstOrDefault(i => string.Equals(i.Tag as string, llm.Model, StringComparison.OrdinalIgnoreCase));
|
||||
if (CmbOverlayModel.SelectedItem == null && CmbOverlayModel.Items.Count > 0)
|
||||
CmbOverlayModel.SelectedIndex = 0;
|
||||
|
||||
if (TxtOverlayModelInput != null)
|
||||
TxtOverlayModelInput.Text = llm.Model ?? "";
|
||||
if (ChkOverlayAiEnabled != null)
|
||||
ChkOverlayAiEnabled.IsChecked = _settings.Settings.AiEnabled;
|
||||
if (TxtOverlayServiceEndpoint != null)
|
||||
TxtOverlayServiceEndpoint.Text = GetOverlayServiceEndpoint(service);
|
||||
if (TxtOverlayServiceApiKey != null)
|
||||
TxtOverlayServiceApiKey.Password = GetOverlayServiceApiKey(service);
|
||||
if (ChkOverlayVllmAllowInsecureTls != null)
|
||||
ChkOverlayVllmAllowInsecureTls.IsChecked = llm.VllmAllowInsecureTls;
|
||||
if (TxtOverlayContextCompactTriggerPercent != null)
|
||||
TxtOverlayContextCompactTriggerPercent.Text = Math.Clamp(llm.ContextCompactTriggerPercent, 10, 95).ToString();
|
||||
if (TxtOverlayMaxContextTokens != null)
|
||||
TxtOverlayMaxContextTokens.Text = Math.Max(1024, llm.MaxContextTokens).ToString();
|
||||
if (TxtOverlayMaxRetryOnError != null)
|
||||
TxtOverlayMaxRetryOnError.Text = Math.Clamp(llm.MaxRetryOnError, 0, 10).ToString();
|
||||
if (ChkOverlayEnableProactiveCompact != null)
|
||||
ChkOverlayEnableProactiveCompact.IsChecked = llm.EnableProactiveContextCompact;
|
||||
if (ChkOverlayEnableSkillSystem != null)
|
||||
ChkOverlayEnableSkillSystem.IsChecked = llm.EnableSkillSystem;
|
||||
if (ChkOverlayEnableToolHooks != null)
|
||||
ChkOverlayEnableToolHooks.IsChecked = llm.EnableToolHooks;
|
||||
if (ChkOverlayEnableHookInputMutation != null)
|
||||
ChkOverlayEnableHookInputMutation.IsChecked = llm.EnableHookInputMutation;
|
||||
if (ChkOverlayEnableHookPermissionUpdate != null)
|
||||
ChkOverlayEnableHookPermissionUpdate.IsChecked = llm.EnableHookPermissionUpdate;
|
||||
if (ChkOverlayEnableCoworkVerification != null)
|
||||
ChkOverlayEnableCoworkVerification.IsChecked = llm.EnableCoworkVerification;
|
||||
if (ChkOverlayEnableCodeVerification != null)
|
||||
ChkOverlayEnableCodeVerification.IsChecked = llm.Code.EnableCodeVerification;
|
||||
if (ChkOverlayEnableParallelTools != null)
|
||||
ChkOverlayEnableParallelTools.IsChecked = llm.EnableParallelTools;
|
||||
|
||||
RefreshOverlayThemeCards();
|
||||
RefreshOverlayServiceCards();
|
||||
RefreshOverlayModeButtons();
|
||||
RefreshOverlayServiceFieldLabels(service);
|
||||
BuildOverlayModelChips(service);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isOverlaySettingsSyncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CmbOverlayService_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isOverlaySettingsSyncing || CmbOverlayService.SelectedItem is not ComboBoxItem serviceItem || serviceItem.Tag is not string service)
|
||||
return;
|
||||
|
||||
var llm = _settings.Settings.Llm;
|
||||
llm.Service = service;
|
||||
var candidates = GetModelCandidates(service);
|
||||
if (candidates.Count > 0 && !candidates.Any(m => m.Id == llm.Model))
|
||||
llm.Model = candidates[0].Id;
|
||||
|
||||
_settings.Save();
|
||||
_appState.LoadFromSettings(_settings);
|
||||
UpdateModelLabel();
|
||||
RefreshInlineSettingsPanel();
|
||||
RefreshOverlaySettingsPanel();
|
||||
}
|
||||
|
||||
private void CmbOverlayModel_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isOverlaySettingsSyncing || CmbOverlayModel.SelectedItem is not ComboBoxItem modelItem || modelItem.Tag is not string modelId)
|
||||
return;
|
||||
|
||||
_settings.Settings.Llm.Model = modelId;
|
||||
_settings.Save();
|
||||
_appState.LoadFromSettings(_settings);
|
||||
UpdateModelLabel();
|
||||
RefreshInlineSettingsPanel();
|
||||
}
|
||||
|
||||
private void RefreshOverlayThemeCards()
|
||||
{
|
||||
var selected = (_settings.Settings.Llm.AgentTheme ?? "system").ToLowerInvariant();
|
||||
SetOverlayCardSelection(OverlayThemeSystemCard, selected == "system");
|
||||
SetOverlayCardSelection(OverlayThemeLightCard, selected == "light");
|
||||
SetOverlayCardSelection(OverlayThemeDarkCard, selected == "dark");
|
||||
}
|
||||
|
||||
private void RefreshOverlayServiceCards()
|
||||
{
|
||||
var service = (_settings.Settings.Llm.Service ?? "ollama").ToLowerInvariant();
|
||||
SetOverlayCardSelection(OverlaySvcOllamaCard, service == "ollama");
|
||||
SetOverlayCardSelection(OverlaySvcVllmCard, service == "vllm");
|
||||
SetOverlayCardSelection(OverlaySvcGeminiCard, service == "gemini");
|
||||
SetOverlayCardSelection(OverlaySvcClaudeCard, service is "claude" or "sigmoid");
|
||||
}
|
||||
|
||||
private void RefreshOverlayModeButtons()
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
BtnOverlayOperationMode.Content = OperationModePolicy.Normalize(_settings.Settings.OperationMode) == OperationModePolicy.ExternalMode
|
||||
? "사외 모드"
|
||||
: "사내 모드";
|
||||
BtnOverlayFolderDataUsage.Content = _folderDataUsage switch
|
||||
{
|
||||
"active" => "적극 활용",
|
||||
"passive" => "소극 활용",
|
||||
_ => "활용하지 않음",
|
||||
};
|
||||
BtnOverlayPermission.Content = PermissionModeCatalog.ToDisplayLabel(PermissionModeCatalog.NormalizeGlobalMode(llm.FilePermission));
|
||||
BtnOverlayPlanMode.Content = PlanModeLabel(llm.PlanMode);
|
||||
BtnOverlayReasoning.Content = ReasoningLabel(llm.AgentDecisionLevel);
|
||||
}
|
||||
|
||||
private void RefreshOverlayServiceFieldLabels(string service)
|
||||
{
|
||||
if (OverlayEndpointLabel == null || OverlayEndpointHint == null || OverlayApiKeyLabel == null || OverlayApiKeyHint == null)
|
||||
return;
|
||||
|
||||
switch (service)
|
||||
{
|
||||
case "ollama":
|
||||
OverlayEndpointLabel.Text = "Ollama 서버 주소";
|
||||
OverlayEndpointHint.Text = "사내 로컬 Ollama 기본 주소를 입력합니다.";
|
||||
OverlayApiKeyLabel.Text = "Ollama API 키";
|
||||
OverlayApiKeyHint.Text = "사내 게이트웨이를 쓰는 경우에만 입력합니다.";
|
||||
break;
|
||||
case "vllm":
|
||||
OverlayEndpointLabel.Text = "vLLM 서버 주소";
|
||||
OverlayEndpointHint.Text = "OpenAI 호환 엔드포인트 주소를 입력합니다.";
|
||||
OverlayApiKeyLabel.Text = "vLLM API 키";
|
||||
OverlayApiKeyHint.Text = "사내 인증 게이트웨이를 쓰는 경우에만 입력합니다.";
|
||||
break;
|
||||
case "gemini":
|
||||
OverlayEndpointLabel.Text = "기본 서버 주소";
|
||||
OverlayEndpointHint.Text = "Gemini는 기본 주소를 사용합니다. 비워두면 기본값을 사용합니다.";
|
||||
OverlayApiKeyLabel.Text = "Gemini API 키";
|
||||
OverlayApiKeyHint.Text = "외부 호출에 필요한 키를 입력합니다.";
|
||||
break;
|
||||
default:
|
||||
OverlayEndpointLabel.Text = "기본 서버 주소";
|
||||
OverlayEndpointHint.Text = "Claude는 기본 주소를 사용합니다. 비워두면 기본값을 사용합니다.";
|
||||
OverlayApiKeyLabel.Text = "Claude API 키";
|
||||
OverlayApiKeyHint.Text = "외부 호출에 필요한 키를 입력합니다.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetOverlayServiceEndpoint(string service)
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
return service switch
|
||||
{
|
||||
"ollama" => llm.OllamaEndpoint ?? "",
|
||||
"vllm" => llm.VllmEndpoint ?? "",
|
||||
"gemini" => llm.Endpoint ?? "",
|
||||
"claude" or "sigmoid" => llm.Endpoint ?? "",
|
||||
_ => llm.Endpoint ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
private string GetOverlayServiceApiKey(string service)
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
return service switch
|
||||
{
|
||||
"ollama" => llm.OllamaApiKey ?? "",
|
||||
"vllm" => llm.VllmApiKey ?? "",
|
||||
"gemini" => llm.GeminiApiKey ?? "",
|
||||
"claude" or "sigmoid" => llm.ClaudeApiKey ?? "",
|
||||
_ => llm.ApiKey ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
private void BuildOverlayModelChips(string service)
|
||||
{
|
||||
if (OverlayModelChipPanel == null)
|
||||
return;
|
||||
|
||||
OverlayModelChipPanel.Children.Clear();
|
||||
foreach (var model in GetModelCandidates(service))
|
||||
{
|
||||
var captured = model.Id;
|
||||
var border = new Border
|
||||
{
|
||||
Cursor = Cursors.Hand,
|
||||
CornerRadius = new CornerRadius(10),
|
||||
BorderThickness = new Thickness(1),
|
||||
BorderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray,
|
||||
Background = Brushes.Transparent,
|
||||
Padding = new Thickness(10, 6, 10, 6),
|
||||
Margin = new Thickness(0, 0, 8, 8),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = model.Label,
|
||||
FontSize = 11,
|
||||
Foreground = TryFindResource("PrimaryText") as Brush ?? Brushes.Black,
|
||||
}
|
||||
};
|
||||
border.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
if (TxtOverlayModelInput != null)
|
||||
TxtOverlayModelInput.Text = captured;
|
||||
};
|
||||
OverlayModelChipPanel.Children.Add(border);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetOverlayCardSelection(Border border, bool selected)
|
||||
{
|
||||
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.DodgerBlue;
|
||||
var normal = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
border.BorderBrush = selected ? accent : normal;
|
||||
border.Background = selected
|
||||
? (TryFindResource("HintBackground") as Brush ?? Brushes.Transparent)
|
||||
: Brushes.Transparent;
|
||||
}
|
||||
|
||||
private void OverlayThemeSystemCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_settings.Settings.Llm.AgentTheme = "system";
|
||||
RefreshOverlayThemeCards();
|
||||
}
|
||||
|
||||
private void OverlayThemeLightCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_settings.Settings.Llm.AgentTheme = "light";
|
||||
RefreshOverlayThemeCards();
|
||||
}
|
||||
|
||||
private void OverlayThemeDarkCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_settings.Settings.Llm.AgentTheme = "dark";
|
||||
RefreshOverlayThemeCards();
|
||||
}
|
||||
|
||||
private void OverlaySvcOllamaCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("ollama");
|
||||
private void OverlaySvcVllmCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("vllm");
|
||||
private void OverlaySvcGeminiCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("gemini");
|
||||
private void OverlaySvcClaudeCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("claude");
|
||||
|
||||
private void SetOverlayService(string service)
|
||||
{
|
||||
_settings.Settings.Llm.Service = service;
|
||||
RefreshOverlaySettingsPanel();
|
||||
}
|
||||
|
||||
private void BtnOverlayOperationMode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var next = OperationModePolicy.Normalize(_settings.Settings.OperationMode) == OperationModePolicy.ExternalMode
|
||||
? OperationModePolicy.InternalMode
|
||||
: OperationModePolicy.ExternalMode;
|
||||
|
||||
if (!PromptOverlayPasswordDialog("운영 모드 변경", "사내/사외 모드 변경", "비밀번호를 입력하세요."))
|
||||
return;
|
||||
|
||||
_settings.Settings.OperationMode = next;
|
||||
RefreshOverlayModeButtons();
|
||||
}
|
||||
|
||||
private void BtnOverlayFolderDataUsage_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_folderDataUsage = _folderDataUsage switch
|
||||
{
|
||||
"none" => "passive",
|
||||
"passive" => "active",
|
||||
_ => "none",
|
||||
};
|
||||
RefreshOverlayModeButtons();
|
||||
}
|
||||
|
||||
private void BtnOverlaySave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var llm = _settings.Settings.Llm;
|
||||
var service = (llm.Service ?? "ollama").Trim().ToLowerInvariant();
|
||||
var endpoint = TxtOverlayServiceEndpoint?.Text.Trim() ?? "";
|
||||
var apiKey = TxtOverlayServiceApiKey?.Password ?? "";
|
||||
|
||||
_settings.Settings.AiEnabled = ChkOverlayAiEnabled?.IsChecked == true;
|
||||
llm.Model = TxtOverlayModelInput?.Text.Trim() ?? llm.Model;
|
||||
llm.VllmAllowInsecureTls = ChkOverlayVllmAllowInsecureTls?.IsChecked == true;
|
||||
llm.EnableProactiveContextCompact = ChkOverlayEnableProactiveCompact?.IsChecked == true;
|
||||
llm.ContextCompactTriggerPercent = ParseOverlayInt(TxtOverlayContextCompactTriggerPercent?.Text, 80, 10, 95);
|
||||
llm.MaxContextTokens = ParseOverlayInt(TxtOverlayMaxContextTokens?.Text, 4096, 1024, 200000);
|
||||
llm.MaxRetryOnError = ParseOverlayInt(TxtOverlayMaxRetryOnError?.Text, 3, 0, 10);
|
||||
llm.EnableSkillSystem = ChkOverlayEnableSkillSystem?.IsChecked == true;
|
||||
llm.EnableToolHooks = ChkOverlayEnableToolHooks?.IsChecked == true;
|
||||
llm.EnableHookInputMutation = ChkOverlayEnableHookInputMutation?.IsChecked == true;
|
||||
llm.EnableHookPermissionUpdate = ChkOverlayEnableHookPermissionUpdate?.IsChecked == true;
|
||||
llm.EnableCoworkVerification = ChkOverlayEnableCoworkVerification?.IsChecked == true;
|
||||
llm.Code.EnableCodeVerification = ChkOverlayEnableCodeVerification?.IsChecked == true;
|
||||
llm.EnableParallelTools = ChkOverlayEnableParallelTools?.IsChecked == true;
|
||||
llm.FolderDataUsage = _folderDataUsage;
|
||||
llm.AgentUiExpressionLevel = "rich";
|
||||
|
||||
switch (service)
|
||||
{
|
||||
case "ollama":
|
||||
llm.OllamaEndpoint = endpoint;
|
||||
llm.OllamaApiKey = apiKey;
|
||||
llm.Endpoint = endpoint;
|
||||
llm.ApiKey = apiKey;
|
||||
llm.OllamaModel = llm.Model;
|
||||
break;
|
||||
case "vllm":
|
||||
llm.VllmEndpoint = endpoint;
|
||||
llm.VllmApiKey = apiKey;
|
||||
llm.Endpoint = endpoint;
|
||||
llm.ApiKey = apiKey;
|
||||
llm.VllmModel = llm.Model;
|
||||
break;
|
||||
case "gemini":
|
||||
llm.Endpoint = endpoint;
|
||||
llm.GeminiApiKey = apiKey;
|
||||
llm.ApiKey = apiKey;
|
||||
llm.GeminiModel = llm.Model;
|
||||
break;
|
||||
case "claude":
|
||||
case "sigmoid":
|
||||
llm.Endpoint = endpoint;
|
||||
llm.ClaudeApiKey = apiKey;
|
||||
llm.ApiKey = apiKey;
|
||||
llm.ClaudeModel = llm.Model;
|
||||
break;
|
||||
}
|
||||
|
||||
_settings.Save();
|
||||
_appState.LoadFromSettings(_settings);
|
||||
ApplyAgentThemeResources();
|
||||
UpdatePermissionUI();
|
||||
UpdateDataUsageUI();
|
||||
SaveConversationSettings();
|
||||
RefreshInlineSettingsPanel();
|
||||
UpdateModelLabel();
|
||||
UpdateTabUI();
|
||||
AgentSettingsOverlay.Visibility = Visibility.Collapsed;
|
||||
ShowToast("AX Agent 설정이 저장되었습니다.");
|
||||
}
|
||||
|
||||
private void BtnOpenFullSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (System.Windows.Application.Current is App app)
|
||||
app.OpenSettingsFromChat();
|
||||
}
|
||||
|
||||
private bool PromptOverlayPasswordDialog(string title, string header, string message)
|
||||
{
|
||||
var bgBrush = TryFindResource("LauncherBackground") as Brush ?? Brushes.White;
|
||||
var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
|
||||
var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
var itemBg = TryFindResource("ItemBackground") as Brush ?? Brushes.WhiteSmoke;
|
||||
|
||||
var dlg = new Window
|
||||
{
|
||||
Title = title,
|
||||
Width = 340,
|
||||
SizeToContent = SizeToContent.Height,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
Owner = this,
|
||||
ResizeMode = ResizeMode.NoResize,
|
||||
WindowStyle = WindowStyle.None,
|
||||
AllowsTransparency = true,
|
||||
Background = Brushes.Transparent,
|
||||
ShowInTaskbar = false,
|
||||
};
|
||||
|
||||
var border = new Border
|
||||
{
|
||||
Background = bgBrush,
|
||||
CornerRadius = new CornerRadius(12),
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(20),
|
||||
};
|
||||
|
||||
var stack = new StackPanel();
|
||||
stack.Children.Add(new TextBlock { Text = header, FontSize = 15, FontWeight = FontWeights.SemiBold, Foreground = fgBrush, Margin = new Thickness(0, 0, 0, 12) });
|
||||
stack.Children.Add(new TextBlock { Text = message, FontSize = 12, Foreground = subFgBrush, Margin = new Thickness(0, 0, 0, 6) });
|
||||
|
||||
var pwBox = new PasswordBox
|
||||
{
|
||||
FontSize = 14,
|
||||
Padding = new Thickness(8, 6, 8, 6),
|
||||
Background = itemBg,
|
||||
Foreground = fgBrush,
|
||||
BorderBrush = borderBrush,
|
||||
PasswordChar = '*',
|
||||
};
|
||||
stack.Children.Add(pwBox);
|
||||
|
||||
var btnRow = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(0, 16, 0, 0) };
|
||||
var cancelBtn = new Button { Content = "취소", Padding = new Thickness(16, 6, 16, 6), Margin = new Thickness(0, 0, 8, 0) };
|
||||
cancelBtn.Click += (_, _) => dlg.DialogResult = false;
|
||||
btnRow.Children.Add(cancelBtn);
|
||||
|
||||
var okBtn = new Button { Content = "확인", Padding = new Thickness(16, 6, 16, 6), IsDefault = true };
|
||||
okBtn.Click += (_, _) =>
|
||||
{
|
||||
if (pwBox.Password == UnifiedAdminPassword)
|
||||
dlg.DialogResult = true;
|
||||
else
|
||||
{
|
||||
pwBox.Clear();
|
||||
pwBox.Focus();
|
||||
}
|
||||
};
|
||||
btnRow.Children.Add(okBtn);
|
||||
stack.Children.Add(btnRow);
|
||||
|
||||
border.Child = stack;
|
||||
dlg.Content = border;
|
||||
dlg.Loaded += (_, _) => pwBox.Focus();
|
||||
return dlg.ShowDialog() == true;
|
||||
}
|
||||
|
||||
private static int ParseOverlayInt(string? text, int fallback, int min, int max)
|
||||
{
|
||||
if (!int.TryParse(text, out var value))
|
||||
value = fallback;
|
||||
return Math.Clamp(value, min, max);
|
||||
}
|
||||
|
||||
private void BtnInlineSettingsClose_Click(object sender, RoutedEventArgs e)
|
||||
=> InlineSettingsPanel.Visibility = Visibility.Collapsed;
|
||||
|
||||
@@ -13108,6 +13603,7 @@ public partial class ChatWindow : Window
|
||||
_settings.Save();
|
||||
_appState.LoadFromSettings(_settings);
|
||||
RefreshInlineSettingsPanel();
|
||||
RefreshOverlaySettingsPanel();
|
||||
}
|
||||
|
||||
private void BtnInlineReasoning_Click(object sender, RoutedEventArgs e)
|
||||
@@ -13117,6 +13613,7 @@ public partial class ChatWindow : Window
|
||||
_settings.Save();
|
||||
_appState.LoadFromSettings(_settings);
|
||||
RefreshInlineSettingsPanel();
|
||||
RefreshOverlaySettingsPanel();
|
||||
}
|
||||
|
||||
private void BtnInlinePlanMode_Click(object sender, RoutedEventArgs e)
|
||||
@@ -13126,6 +13623,7 @@ public partial class ChatWindow : Window
|
||||
_settings.Save();
|
||||
_appState.LoadFromSettings(_settings);
|
||||
RefreshInlineSettingsPanel();
|
||||
RefreshOverlaySettingsPanel();
|
||||
}
|
||||
|
||||
private void BtnInlinePermission_Click(object sender, RoutedEventArgs e)
|
||||
@@ -13137,6 +13635,7 @@ public partial class ChatWindow : Window
|
||||
UpdatePermissionUI();
|
||||
SaveConversationSettings();
|
||||
RefreshInlineSettingsPanel();
|
||||
RefreshOverlaySettingsPanel();
|
||||
}
|
||||
|
||||
private void BtnInlineSkill_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -3,9 +3,12 @@ using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.Services;
|
||||
using FormsCursor = System.Windows.Forms.Cursor;
|
||||
using FormsScreen = System.Windows.Forms.Screen;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
@@ -48,14 +51,19 @@ public partial class DockBarWindow : Window
|
||||
private DispatcherTimer? _glowTimer;
|
||||
|
||||
/// <summary>설정 저장 콜백 (위치 저장용).</summary>
|
||||
public Action<double, double>? OnPositionChanged { get; set; }
|
||||
public Action<string, double, double>? OnPositionChanged { get; set; }
|
||||
|
||||
public DockBarWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MouseLeftButtonDown += (_, e) => { if (e.LeftButton == MouseButtonState.Pressed) try { DragMove(); } catch { } };
|
||||
LocationChanged += (_, _) => OnPositionChanged?.Invoke(Left, Top);
|
||||
LocationChanged += (_, _) =>
|
||||
{
|
||||
var deviceName = GetCurrentMonitorDeviceName();
|
||||
if (!string.IsNullOrWhiteSpace(deviceName))
|
||||
OnPositionChanged?.Invoke(deviceName, Left, Top);
|
||||
};
|
||||
Loaded += (_, _) => PositionDock();
|
||||
Closed += (_, _) => { _timer?.Stop(); _glowTimer?.Stop(); _cpuCounter?.Dispose(); };
|
||||
}
|
||||
@@ -65,7 +73,7 @@ public partial class DockBarWindow : Window
|
||||
{
|
||||
Opacity = Math.Clamp(opacity, 0.3, 1.0);
|
||||
|
||||
if (left >= 0 && top >= 0)
|
||||
if (left >= 0 && top >= 0 && IsWithinConnectedScreen(left, top))
|
||||
{
|
||||
Left = left;
|
||||
Top = top;
|
||||
@@ -269,11 +277,34 @@ public partial class DockBarWindow : Window
|
||||
|
||||
private void PositionDock()
|
||||
{
|
||||
var screen = SystemParameters.WorkArea;
|
||||
Left = (screen.Width - ActualWidth) / 2 + screen.Left;
|
||||
var screen = FormsScreen.FromPoint(FormsCursor.Position).WorkingArea;
|
||||
Left = screen.Left + (screen.Width - ActualWidth) / 2;
|
||||
Top = screen.Bottom - ActualHeight - 8;
|
||||
}
|
||||
|
||||
private string GetCurrentMonitorDeviceName()
|
||||
{
|
||||
var handle = new WindowInteropHelper(this).Handle;
|
||||
return handle != IntPtr.Zero
|
||||
? FormsScreen.FromHandle(handle).DeviceName
|
||||
: FormsScreen.FromPoint(FormsCursor.Position).DeviceName;
|
||||
}
|
||||
|
||||
private bool IsWithinConnectedScreen(double left, double top)
|
||||
{
|
||||
var width = ActualWidth > 0 ? ActualWidth : (Width > 0 ? Width : 420);
|
||||
var height = ActualHeight > 0 ? ActualHeight : (Height > 0 ? Height : 56);
|
||||
|
||||
foreach (var screen in FormsScreen.AllScreens)
|
||||
{
|
||||
var area = screen.WorkingArea;
|
||||
if (left >= area.Left && top >= area.Top && left + width <= area.Right && top + height <= area.Bottom)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnTick(object? sender, EventArgs e)
|
||||
{
|
||||
if (_clockText != null)
|
||||
|
||||
@@ -13,6 +13,7 @@ internal sealed class ModelRegistrationDialog : Window
|
||||
private readonly TextBox _modelBox;
|
||||
private readonly TextBox _endpointBox;
|
||||
private readonly TextBox _apiKeyBox;
|
||||
private readonly CheckBox _allowInsecureTlsCheck;
|
||||
|
||||
// CP4D 인증 필드
|
||||
private readonly ComboBox _authTypeBox;
|
||||
@@ -25,13 +26,14 @@ internal sealed class ModelRegistrationDialog : Window
|
||||
public string ModelName => _modelBox.Text.Trim();
|
||||
public string Endpoint => _endpointBox.Text.Trim();
|
||||
public string ApiKey => _apiKeyBox.Text.Trim();
|
||||
public bool AllowInsecureTls => _allowInsecureTlsCheck.IsChecked == true;
|
||||
public string AuthType => (_authTypeBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "bearer";
|
||||
public string Cp4dUrl => _cp4dUrlBox.Text.Trim();
|
||||
public string Cp4dUsername => _cp4dUsernameBox.Text.Trim();
|
||||
public string Cp4dPassword => _cp4dPasswordBox.Password.Trim();
|
||||
|
||||
public ModelRegistrationDialog(string service, string existingAlias = "", string existingModel = "",
|
||||
string existingEndpoint = "", string existingApiKey = "",
|
||||
string existingEndpoint = "", string existingApiKey = "", bool existingAllowInsecureTls = false,
|
||||
string existingAuthType = "bearer", string existingCp4dUrl = "",
|
||||
string existingCp4dUsername = "", string existingCp4dPassword = "")
|
||||
{
|
||||
@@ -211,6 +213,48 @@ internal sealed class ModelRegistrationDialog : Window
|
||||
};
|
||||
stack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _endpointBox });
|
||||
|
||||
var tlsPanel = new Border
|
||||
{
|
||||
Background = Brushes.Transparent,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Padding = new Thickness(10, 8, 10, 8),
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
};
|
||||
var tlsGrid = new Grid();
|
||||
tlsGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
tlsGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
var tlsLeft = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
|
||||
tlsLeft.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "SSL 인증서 검증 생략",
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
});
|
||||
tlsLeft.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "사내 자체 인증서 환경에서만 사용하세요.",
|
||||
FontSize = 11,
|
||||
Foreground = secondaryText,
|
||||
Margin = new Thickness(0, 2, 0, 0),
|
||||
});
|
||||
Grid.SetColumn(tlsLeft, 0);
|
||||
tlsGrid.Children.Add(tlsLeft);
|
||||
_allowInsecureTlsCheck = new CheckBox
|
||||
{
|
||||
IsChecked = existingAllowInsecureTls,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Thickness(12, 0, 0, 0),
|
||||
};
|
||||
_allowInsecureTlsCheck.Style = Application.Current.TryFindResource("ToggleSwitch") as Style ?? _allowInsecureTlsCheck.Style;
|
||||
Grid.SetColumn(_allowInsecureTlsCheck, 1);
|
||||
tlsGrid.Children.Add(_allowInsecureTlsCheck);
|
||||
tlsPanel.Child = tlsGrid;
|
||||
stack.Children.Add(tlsPanel);
|
||||
|
||||
// ── 인증 방식 선택 ──────────────────────────────────────────────────
|
||||
stack.Children.Add(new TextBlock
|
||||
{
|
||||
|
||||
@@ -47,9 +47,12 @@ internal sealed class PlanViewerWindow : Window
|
||||
private List<string> _steps = new();
|
||||
private int _currentStep = -1;
|
||||
private bool _isExecuting;
|
||||
private readonly string _uiExpressionLevel;
|
||||
|
||||
public PlanViewerWindow()
|
||||
{
|
||||
_uiExpressionLevel = ResolveUiExpressionLevel();
|
||||
|
||||
Width = 640;
|
||||
Height = 520;
|
||||
MinWidth = 480;
|
||||
@@ -156,6 +159,8 @@ internal sealed class PlanViewerWindow : Window
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Thickness(20, 6, 20, 0),
|
||||
};
|
||||
if (_uiExpressionLevel == "simple")
|
||||
toolBar.Visibility = Visibility.Collapsed;
|
||||
|
||||
var expandAllBtn = MakeToolbarButton("\uE70D", "모두 열기", secondaryText, hoverBgTb);
|
||||
expandAllBtn.MouseLeftButtonUp += (_, _) =>
|
||||
@@ -281,6 +286,11 @@ internal sealed class PlanViewerWindow : Window
|
||||
_currentStep = -1;
|
||||
_isExecuting = false;
|
||||
_expandedSteps.Clear(); // 새 계획 표시 시 모두 접힌 상태로 시작
|
||||
if (_uiExpressionLevel == "rich")
|
||||
{
|
||||
for (int i = 0; i < _steps.Count; i++)
|
||||
_expandedSteps.Add(i);
|
||||
}
|
||||
|
||||
RenderSteps();
|
||||
BuildApprovalButtons();
|
||||
@@ -361,6 +371,56 @@ internal sealed class PlanViewerWindow : Window
|
||||
|
||||
var canEdit = !_isExecuting && _currentStep < 0; // 승인 대기 중에만 편집/순서변경 가능
|
||||
|
||||
var summaryText = _uiExpressionLevel switch
|
||||
{
|
||||
"simple" => _isExecuting
|
||||
? "진행률을 간단히 표시합니다."
|
||||
: $"{_steps.Count}단계 계획입니다. 핵심 단계만 확인하고 승인하세요.",
|
||||
"rich" => _isExecuting
|
||||
? "현재 단계를 기준으로 진행률을 표시합니다. 필요 시 단계를 펼쳐 세부 내용을 확인할 수 있습니다."
|
||||
: $"총 {_steps.Count}단계입니다. 단계별 내용을 열어 우선순위/의존성을 검토한 뒤 승인 또는 수정 요청을 선택하세요.",
|
||||
_ => _isExecuting
|
||||
? "현재 단계를 기준으로 진행률을 표시합니다."
|
||||
: $"총 {_steps.Count}단계입니다. 단계를 펼쳐 검토한 후 승인 또는 수정 요청을 선택하세요.",
|
||||
};
|
||||
|
||||
_stepsPanel.Children.Add(new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x12,
|
||||
((SolidColorBrush)accentBrush).Color.R,
|
||||
((SolidColorBrush)accentBrush).Color.G,
|
||||
((SolidColorBrush)accentBrush).Color.B)),
|
||||
BorderBrush = new SolidColorBrush(Color.FromArgb(0x40,
|
||||
((SolidColorBrush)accentBrush).Color.R,
|
||||
((SolidColorBrush)accentBrush).Color.G,
|
||||
((SolidColorBrush)accentBrush).Color.B)),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(10),
|
||||
Padding = new Thickness(10, 8, 10, 8),
|
||||
Margin = new Thickness(0, 0, 0, 8),
|
||||
Child = new StackPanel
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new TextBlock
|
||||
{
|
||||
Text = _isExecuting ? "계획 실행 중" : "계획 승인 대기",
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = accentBrush,
|
||||
},
|
||||
new TextBlock
|
||||
{
|
||||
Text = summaryText,
|
||||
FontSize = 11.5,
|
||||
Foreground = secondaryText,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(0, 3, 0, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (int i = 0; i < _steps.Count; i++)
|
||||
{
|
||||
var step = _steps[i];
|
||||
@@ -768,7 +828,11 @@ internal sealed class PlanViewerWindow : Window
|
||||
var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
|
||||
|
||||
var approveBtn = CreateActionButton("\uE73E", "승인", accentBrush, Brushes.White, true);
|
||||
var approveLabel = _uiExpressionLevel == "simple" ? "승인" : "승인 후 실행";
|
||||
var editLabel = _uiExpressionLevel == "simple" ? "수정" : "수정 피드백";
|
||||
var rejectLabel = _uiExpressionLevel == "simple" ? "취소" : "거부";
|
||||
|
||||
var approveBtn = CreateActionButton("\uE73E", approveLabel, accentBrush, Brushes.White, true);
|
||||
approveBtn.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
_tcs?.TrySetResult(null);
|
||||
@@ -776,19 +840,12 @@ internal sealed class PlanViewerWindow : Window
|
||||
};
|
||||
_btnPanel.Children.Add(approveBtn);
|
||||
|
||||
var editBtn = CreateActionButton("\uE70F", "수정 요청", accentBrush, accentBrush, false);
|
||||
var editBtn = CreateActionButton("\uE70F", editLabel, accentBrush, accentBrush, false);
|
||||
editBtn.MouseLeftButtonUp += (_, _) => ShowEditInput();
|
||||
_btnPanel.Children.Add(editBtn);
|
||||
|
||||
var reconfirmBtn = CreateActionButton("\uE72C", "재확인",
|
||||
Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
|
||||
Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White, false);
|
||||
reconfirmBtn.MouseLeftButtonUp += (_, _) =>
|
||||
_tcs?.TrySetResult("계획을 다시 검토하고 더 구체적으로 수정해주세요.");
|
||||
_btnPanel.Children.Add(reconfirmBtn);
|
||||
|
||||
var cancelBrush = new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
|
||||
var cancelBtn = CreateActionButton("\uE711", "취소", cancelBrush, cancelBrush, false);
|
||||
var cancelBtn = CreateActionButton("\uE711", rejectLabel, cancelBrush, cancelBrush, false);
|
||||
cancelBtn.MouseLeftButtonUp += (_, _) => { _tcs?.TrySetResult("취소"); Hide(); };
|
||||
_btnPanel.Children.Add(cancelBtn);
|
||||
}
|
||||
@@ -948,4 +1005,22 @@ internal sealed class PlanViewerWindow : Window
|
||||
btn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0;
|
||||
return btn;
|
||||
}
|
||||
|
||||
private static string ResolveUiExpressionLevel()
|
||||
{
|
||||
if (Application.Current is App app)
|
||||
return NormalizeUiExpressionLevel(app.SettingsService?.Settings?.Llm.AgentUiExpressionLevel);
|
||||
|
||||
return "balanced";
|
||||
}
|
||||
|
||||
private static string NormalizeUiExpressionLevel(string? value)
|
||||
{
|
||||
return (value ?? "balanced").Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"rich" => "rich",
|
||||
"simple" => "simple",
|
||||
_ => "balanced",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,7 +538,7 @@
|
||||
<!-- ══════════════════════════════════════════════════════════════════ -->
|
||||
<!-- 탭 컨트롤 (좌측 사이드바) -->
|
||||
<!-- ══════════════════════════════════════════════════════════════════ -->
|
||||
<TabControl Grid.Row="0" TabStripPlacement="Left"
|
||||
<TabControl x:Name="MainSettingsTab" Grid.Row="0" TabStripPlacement="Left"
|
||||
Background="Transparent" BorderThickness="0">
|
||||
<TabControl.Template>
|
||||
<ControlTemplate TargetType="TabControl">
|
||||
@@ -574,6 +574,35 @@
|
||||
</Border>
|
||||
<Rectangle DockPanel.Dock="Top" Height="1"
|
||||
Fill="{DynamicResource BorderColor}" Margin="12,0,12,8"/>
|
||||
<Border DockPanel.Dock="Bottom" Margin="12,0,12,8"
|
||||
Visibility="Collapsed"
|
||||
Background="{DynamicResource ItemBackground}"
|
||||
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1"
|
||||
CornerRadius="10" Padding="8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="설정 표현"
|
||||
FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource SecondaryText}"
|
||||
Margin="2,0,0,6"/>
|
||||
<WrapPanel>
|
||||
<RadioButton x:Name="DisplayModeRich"
|
||||
Content="풍부하게"
|
||||
GroupName="DisplayMode"
|
||||
Style="{StaticResource AgentSubTabStyle}"
|
||||
Checked="DisplayMode_Checked"/>
|
||||
<RadioButton x:Name="DisplayModeBalanced"
|
||||
Content="적절하게"
|
||||
GroupName="DisplayMode"
|
||||
Style="{StaticResource AgentSubTabStyle}"
|
||||
Checked="DisplayMode_Checked"/>
|
||||
<RadioButton x:Name="DisplayModeSimple"
|
||||
Content="간단하게"
|
||||
GroupName="DisplayMode"
|
||||
Style="{StaticResource AgentSubTabStyle}"
|
||||
Checked="DisplayMode_Checked"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- 탭 목록 — 창 높이가 충분히 크므로 스크롤 없음 -->
|
||||
<ScrollViewer VerticalScrollBarVisibility="Disabled"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
@@ -621,7 +650,7 @@
|
||||
<StackPanel>
|
||||
|
||||
<TextBlock Text="AI 기능" Style="{StaticResource SectionHeader}"/>
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Border Style="{StaticResource SettingsRow}" Visibility="Collapsed">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -3258,7 +3287,7 @@
|
||||
<!-- ═════════════════════════════════════════════════════════════ -->
|
||||
<!-- AX Agent 탭 -->
|
||||
<!-- ═════════════════════════════════════════════════════════════ -->
|
||||
<TabItem x:Name="AgentTabItem" Header="AX Agent" Tag="" Style="{StaticResource SideNavItem}">
|
||||
<TabItem x:Name="AgentTabItem" Header="AX Agent" Tag="" Style="{StaticResource SideNavItem}" Visibility="Collapsed">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -3540,6 +3569,16 @@
|
||||
Text="{Binding VllmApiKey, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="SSL 인증서 검증 생략"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="사내 자체 인증서 환경에서만 사용하세요. vLLM HTTPS 인증서 오류를 무시합니다."/>
|
||||
</StackPanel>
|
||||
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
IsChecked="{Binding VllmAllowInsecureTls, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Gemini 패널 -->
|
||||
@@ -4019,6 +4058,7 @@
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="슬래시 팝업 표시 개수"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="/ 입력 시 한 번에 보여줄 명령어 개수입니다. (최소 3 ~ 최대 20)"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="저장 후 즉시 반영"/>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
@@ -4035,6 +4075,50 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="슬래시 핀 최대 개수"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="핀 고정 가능한 슬래시 명령어 최대 개수입니다. (최소 1 ~ 최대 30)"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="저장 후 즉시 반영"/>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding MaxFavoriteSlashCommands}"
|
||||
FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentColor}"
|
||||
VerticalAlignment="Center"
|
||||
MinWidth="24" TextAlignment="Right"
|
||||
Margin="0,0,8,0"/>
|
||||
<Slider Width="140" Minimum="1" Maximum="30" TickFrequency="1"
|
||||
IsSnapToTickEnabled="True"
|
||||
Value="{Binding MaxFavoriteSlashCommands, Mode=TwoWay}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="슬래시 최근 최대 개수"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="최근 사용(MRU)으로 기억할 슬래시 명령어 최대 개수입니다. (최소 5 ~ 최대 50)"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="저장 후 즉시 반영"/>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding MaxRecentSlashCommands}"
|
||||
FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentColor}"
|
||||
VerticalAlignment="Center"
|
||||
MinWidth="24" TextAlignment="Right"
|
||||
Margin="0,0,8,0"/>
|
||||
<Slider Width="140" Minimum="5" Maximum="50" TickFrequency="1"
|
||||
IsSnapToTickEnabled="True"
|
||||
Value="{Binding MaxRecentSlashCommands, Mode=TwoWay}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ── 드래그 앤 드롭 AI ── -->
|
||||
<TextBlock Style="{StaticResource SectionHeader}" Text="드래그 앤 드롭 AI"/>
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
@@ -4373,24 +4457,6 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="오류 시 최대 재시도"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="도구 실행 실패 시 자동으로 재시도할 횟수입니다. (0~10)"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Slider Width="100" Minimum="0" Maximum="10" TickFrequency="1"
|
||||
IsSnapToTickEnabled="True"
|
||||
Value="{Binding MaxRetryOnError, Mode=TwoWay}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding MaxRetryOnError}"
|
||||
FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource AccentColor}"
|
||||
Width="24" TextAlignment="Center" VerticalAlignment="Center"
|
||||
Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,180,0">
|
||||
@@ -4454,6 +4520,32 @@
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="사전 컨텍스트 압축"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="토큰 한도 전에 이전 대화를 요약/정리해 컨텍스트를 안정적으로 유지합니다."/>
|
||||
</StackPanel>
|
||||
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
IsChecked="{Binding EnableProactiveContextCompact, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="압축 시작 임계치"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="최대 컨텍스트 토큰 대비 사용률이 이 값에 도달하면 사전 압축을 수행합니다."/>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding ContextCompactTriggerPercent, StringFormat={}{0}%}"
|
||||
FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource AccentColor}"
|
||||
VerticalAlignment="Center" MinWidth="42" TextAlignment="Right" Margin="0,0,8,0"/>
|
||||
<Slider Width="140" Minimum="50" Maximum="95" TickFrequency="5"
|
||||
IsSnapToTickEnabled="True"
|
||||
Value="{Binding ContextCompactTriggerPercent, Mode=TwoWay}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
@@ -4698,9 +4790,9 @@
|
||||
<ComboBox HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Width="180" SelectedValue="{Binding FolderDataUsage, Mode=TwoWay}"
|
||||
SelectedValuePath="Tag">
|
||||
<ComboBoxItem Content="적극 활용 (자동 탐색)" Tag="active"/>
|
||||
<ComboBoxItem Content="소극 활용 (요청 시)" Tag="passive"/>
|
||||
<ComboBoxItem Content="활용하지 않음" Tag="none"/>
|
||||
<ComboBoxItem Content="소극 활용 (요청 시)" Tag="passive"/>
|
||||
<ComboBoxItem Content="적극 활용 (자동 탐색)" Tag="active"/>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -5211,6 +5303,24 @@
|
||||
HorizontalAlignment="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="오류 재시도 횟수"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="도구 실행 실패 시 자동 복구를 위해 재시도할 최대 횟수입니다. (0~10)"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Slider Width="100" Minimum="0" Maximum="10" TickFrequency="1"
|
||||
IsSnapToTickEnabled="True"
|
||||
Value="{Binding MaxRetryOnError, Mode=TwoWay}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding MaxRetryOnError}"
|
||||
FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource AccentColor}"
|
||||
Width="24" TextAlignment="Center" VerticalAlignment="Center"
|
||||
Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ── 모델 폴백 ── -->
|
||||
<TextBlock Style="{StaticResource SectionHeader}" Text="모델 폴백"/>
|
||||
|
||||
@@ -15,6 +15,7 @@ public partial class SettingsWindow : Window
|
||||
private readonly Action<string> _previewCallback;
|
||||
private readonly Action _revertCallback;
|
||||
private bool _saved;
|
||||
private bool _isDisplayModeSyncing;
|
||||
|
||||
/// <summary>
|
||||
/// 핫키 녹화 시작/종료를 외부(App.xaml.cs)에 알리는 콜백.
|
||||
@@ -81,9 +82,153 @@ public partial class SettingsWindow : Window
|
||||
// AI 기능 토글 초기화
|
||||
ApplyAiEnabledState(app?.SettingsService?.Settings.AiEnabled ?? false, init: true);
|
||||
ApplyOperationModeState(app?.SettingsService?.Settings.OperationMode);
|
||||
InitializeDisplayModeUi();
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeDisplayModeUi()
|
||||
{
|
||||
var app = System.Windows.Application.Current as App;
|
||||
if (app?.SettingsService?.Settings?.Llm != null)
|
||||
app.SettingsService.Settings.Llm.AgentUiExpressionLevel = "rich";
|
||||
SetDisplayMode("rich", persist: false);
|
||||
}
|
||||
|
||||
private void DisplayMode_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isDisplayModeSyncing) return;
|
||||
var rb = sender as RadioButton;
|
||||
var next = rb?.Name switch
|
||||
{
|
||||
"DisplayModeRich" => "rich",
|
||||
"DisplayModeSimple" => "simple",
|
||||
_ => "balanced",
|
||||
};
|
||||
SetDisplayMode(next, persist: true);
|
||||
}
|
||||
|
||||
private void SetDisplayMode(string mode, bool persist)
|
||||
{
|
||||
mode = NormalizeDisplayMode(mode);
|
||||
|
||||
_isDisplayModeSyncing = true;
|
||||
try
|
||||
{
|
||||
var rich = GetDisplayModeRadio("DisplayModeRich");
|
||||
var balanced = GetDisplayModeRadio("DisplayModeBalanced");
|
||||
var simple = GetDisplayModeRadio("DisplayModeSimple");
|
||||
if (rich != null) rich.IsChecked = mode == "rich";
|
||||
if (balanced != null) balanced.IsChecked = mode == "balanced";
|
||||
if (simple != null) simple.IsChecked = mode == "simple";
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isDisplayModeSyncing = false;
|
||||
}
|
||||
|
||||
ApplyMainTabVisibility(mode);
|
||||
ApplyAgentSubTabVisibility(mode);
|
||||
|
||||
if (!persist) return;
|
||||
var app = System.Windows.Application.Current as App;
|
||||
if (app?.SettingsService?.Settings?.Llm == null) return;
|
||||
app.SettingsService.Settings.Llm.AgentUiExpressionLevel = mode;
|
||||
app.SettingsService.Save();
|
||||
}
|
||||
|
||||
private void ApplyMainTabVisibility(string mode)
|
||||
{
|
||||
if (MainSettingsTab == null) return;
|
||||
|
||||
var simpleKeep = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"일반", "테마", "기능", "AX Agent"
|
||||
};
|
||||
var balancedKeep = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"일반", "테마", "클립보드", "캡처", "시스템", "기능", "AX Agent"
|
||||
};
|
||||
|
||||
foreach (var item in MainSettingsTab.Items.OfType<TabItem>())
|
||||
{
|
||||
var header = item.Header?.ToString() ?? "";
|
||||
var visible = mode switch
|
||||
{
|
||||
"simple" => simpleKeep.Contains(header),
|
||||
"balanced" => balancedKeep.Contains(header),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if (string.Equals(header, "알림", StringComparison.OrdinalIgnoreCase))
|
||||
visible = false;
|
||||
if (string.Equals(header, "AX Agent", StringComparison.OrdinalIgnoreCase)
|
||||
&& !((System.Windows.Application.Current as App)?.SettingsService?.Settings.AiEnabled ?? false))
|
||||
{
|
||||
visible = false;
|
||||
}
|
||||
|
||||
item.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (MainSettingsTab.SelectedItem is TabItem selected && selected.Visibility == Visibility.Visible)
|
||||
return;
|
||||
|
||||
var firstVisible = MainSettingsTab.Items.OfType<TabItem>().FirstOrDefault(t => t.Visibility == Visibility.Visible);
|
||||
if (firstVisible != null)
|
||||
MainSettingsTab.SelectedItem = firstVisible;
|
||||
}
|
||||
|
||||
private void ApplyAgentSubTabVisibility(string mode)
|
||||
{
|
||||
if (AgentTabCommon == null) return;
|
||||
|
||||
SetSubTabVisible(AgentTabCommon, true);
|
||||
SetSubTabVisible(AgentTabChat, mode != "simple");
|
||||
SetSubTabVisible(AgentTabCoworkCode, true);
|
||||
SetSubTabVisible(AgentTabCowork, mode == "rich");
|
||||
SetSubTabVisible(AgentTabCode, mode == "rich");
|
||||
SetSubTabVisible(AgentTabTools, mode != "simple");
|
||||
SetSubTabVisible(AgentTabEtc, mode != "simple");
|
||||
SetSubTabVisible(AgentTabDev, mode == "rich");
|
||||
|
||||
if (AgentTabCommon.IsChecked != true
|
||||
&& AgentTabChat.IsChecked != true
|
||||
&& AgentTabCoworkCode.IsChecked != true
|
||||
&& AgentTabCowork.IsChecked != true
|
||||
&& AgentTabCode.IsChecked != true
|
||||
&& AgentTabTools.IsChecked != true
|
||||
&& AgentTabEtc.IsChecked != true
|
||||
&& AgentTabDev.IsChecked != true)
|
||||
{
|
||||
AgentTabCommon.IsChecked = true;
|
||||
}
|
||||
|
||||
AgentSubTab_Checked(this, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
private static void SetSubTabVisible(RadioButton? tab, bool visible)
|
||||
{
|
||||
if (tab == null) return;
|
||||
tab.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (!visible) tab.IsChecked = false;
|
||||
}
|
||||
|
||||
private static string NormalizeDisplayMode(string? mode)
|
||||
{
|
||||
return (mode ?? "balanced").Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"rich" => "rich",
|
||||
"simple" => "simple",
|
||||
_ => "balanced",
|
||||
};
|
||||
}
|
||||
|
||||
private RadioButton? GetDisplayModeRadio(string name)
|
||||
{
|
||||
if (MainSettingsTab?.Template == null) return null;
|
||||
return MainSettingsTab.Template.FindName(name, MainSettingsTab) as RadioButton;
|
||||
}
|
||||
|
||||
// ─── 에이전트 차단 섹션 → 기타 탭 이동 ──────────────────────────────────────
|
||||
|
||||
private void MoveBlockSectionToEtc()
|
||||
@@ -1558,6 +1703,7 @@ public partial class SettingsWindow : Window
|
||||
Service = currentService,
|
||||
Endpoint = dlg.Endpoint,
|
||||
ApiKey = dlg.ApiKey,
|
||||
AllowInsecureTls = dlg.AllowInsecureTls,
|
||||
AuthType = dlg.AuthType,
|
||||
Cp4dUrl = dlg.Cp4dUrl,
|
||||
Cp4dUsername = dlg.Cp4dUsername,
|
||||
@@ -1576,7 +1722,7 @@ public partial class SettingsWindow : Window
|
||||
|
||||
var currentService = GetCurrentServiceSubTab();
|
||||
var dlg = new ModelRegistrationDialog(currentService, row.Alias, currentModel,
|
||||
row.Endpoint, row.ApiKey,
|
||||
row.Endpoint, row.ApiKey, row.AllowInsecureTls,
|
||||
row.AuthType ?? "bearer", row.Cp4dUrl ?? "", row.Cp4dUsername ?? "", cp4dPw);
|
||||
dlg.Owner = this;
|
||||
if (dlg.ShowDialog() == true)
|
||||
@@ -1586,6 +1732,7 @@ public partial class SettingsWindow : Window
|
||||
row.Service = currentService;
|
||||
row.Endpoint = dlg.Endpoint;
|
||||
row.ApiKey = dlg.ApiKey;
|
||||
row.AllowInsecureTls = dlg.AllowInsecureTls;
|
||||
row.AuthType = dlg.AuthType;
|
||||
row.Cp4dUrl = dlg.Cp4dUrl;
|
||||
row.Cp4dUsername = dlg.Cp4dUsername;
|
||||
@@ -2051,9 +2198,9 @@ public partial class SettingsWindow : Window
|
||||
{
|
||||
AiEnabledToggle.IsChecked = enabled;
|
||||
}
|
||||
// AX Agent 탭 가시성
|
||||
// AX Agent 상세 설정은 전용 창으로 분리 (탭은 숨김)
|
||||
if (AgentTabItem != null)
|
||||
AgentTabItem.Visibility = enabled ? Visibility.Visible : Visibility.Collapsed;
|
||||
AgentTabItem.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ApplyOperationModeState(string? mode)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,7 +7,7 @@
|
||||
"targets": {
|
||||
".NETCoreApp,Version=v8.0": {},
|
||||
".NETCoreApp,Version=v8.0/win-x64": {
|
||||
"AxCopilot/1.7.2": {
|
||||
"AxCopilot/0.7.3": {
|
||||
"dependencies": {
|
||||
"AxCopilot.SDK": "1.0.0",
|
||||
"DocumentFormat.OpenXml": "3.2.0",
|
||||
@@ -236,7 +236,7 @@
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"AxCopilot/1.7.2": {
|
||||
"AxCopilot/0.7.3": {
|
||||
"type": "project",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net8.0",
|
||||
"includedFrameworks": [
|
||||
"frameworks": [
|
||||
{
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "8.0.25"
|
||||
"version": "8.0.0"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.WindowsDesktop.App",
|
||||
"version": "8.0.25"
|
||||
"version": "8.0.0"
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user