|
|
|
|
@@ -65,6 +65,7 @@ public partial class ChatWindow : Window
|
|
|
|
|
private bool _userScrolled; // 사용자가 위로 스크롤했는지
|
|
|
|
|
private readonly HashSet<string> _sessionPermissionRules = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
private readonly Dictionary<string, bool> _sessionMcpEnabledOverrides = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
private readonly Dictionary<string, string> _sessionMcpAuthTokens = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
|
|
|
|
|
// 경과 시간 표시
|
|
|
|
|
private readonly DispatcherTimer _elapsedTimer;
|
|
|
|
|
@@ -1955,6 +1956,48 @@ public partial class ChatWindow : Window
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryApplyPermissionModeFromAction(string action, out string appliedMode)
|
|
|
|
|
{
|
|
|
|
|
appliedMode = PermissionModeCatalog.NormalizeGlobalMode(_settings.Settings.Llm.FilePermission);
|
|
|
|
|
var next = action switch
|
|
|
|
|
{
|
|
|
|
|
"ask" => PermissionModeCatalog.Ask,
|
|
|
|
|
"auto" => PermissionModeCatalog.Auto,
|
|
|
|
|
"deny" => PermissionModeCatalog.Deny,
|
|
|
|
|
_ => null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(next))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
_settings.Settings.Llm.FilePermission = next!;
|
|
|
|
|
_settings.Save();
|
|
|
|
|
_appState.LoadFromSettings(_settings);
|
|
|
|
|
UpdatePermissionUI();
|
|
|
|
|
SaveConversationSettings();
|
|
|
|
|
RefreshInlineSettingsPanel();
|
|
|
|
|
appliedMode = next!;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string BuildPermissionStatusText()
|
|
|
|
|
{
|
|
|
|
|
ChatConversation? currentConversation;
|
|
|
|
|
lock (_convLock) currentConversation = _currentConversation;
|
|
|
|
|
var summary = _appState.GetPermissionSummary(currentConversation);
|
|
|
|
|
var mode = PermissionModeCatalog.NormalizeGlobalMode(summary.EffectiveMode);
|
|
|
|
|
var overrides = summary.TopOverrides.Count > 0
|
|
|
|
|
? string.Join(", ", summary.TopOverrides.Select(x => $"{x.Key}:{x.Value}"))
|
|
|
|
|
: "없음";
|
|
|
|
|
return $"현재 권한 모드: {mode}\n설명: {summary.Description}\n기본값: {summary.DefaultMode} · override: {summary.OverrideCount}개\n상위 override: {overrides}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OpenPermissionPanelFromSlash(string command, string usageText)
|
|
|
|
|
{
|
|
|
|
|
BtnPermission_Click(this, new RoutedEventArgs());
|
|
|
|
|
AppendLocalSlashResult(_activeTab, command, $"권한 설정 팝업을 열었습니다. ({usageText})");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ──── 데이터 활용 수준 메뉴 ────
|
|
|
|
|
|
|
|
|
|
private void BtnDataUsage_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
|
|
|
@@ -5529,7 +5572,7 @@ public partial class ChatWindow : Window
|
|
|
|
|
("/doctor", "프로젝트/환경 점검 체크를 수행합니다."));
|
|
|
|
|
|
|
|
|
|
AddHelpSection(contentPanel, "연결/확장 명령어", "환경 연결, 플러그인, 에이전트 관련", fg, fg2, accent, itemBg, hoverBg,
|
|
|
|
|
("/mcp", "외부 도구 연결 상태 점검 및 add/remove/reset 관리"),
|
|
|
|
|
("/mcp", "외부 도구 연결 상태 점검 및 add/remove/reset/login/logout 관리"),
|
|
|
|
|
("/agents", "에이전트 분담 전략 제시"),
|
|
|
|
|
("/plugin", "플러그인 구성 점검"),
|
|
|
|
|
("/reload-plugins", "플러그인 재로드 점검"),
|
|
|
|
|
@@ -5838,7 +5881,7 @@ public partial class ChatWindow : Window
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using var client = new McpClientService(server);
|
|
|
|
|
using var client = new McpClientService(BuildEffectiveMcpServer(server));
|
|
|
|
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
|
|
|
timeoutCts.CancelAfter(TimeSpan.FromSeconds(8));
|
|
|
|
|
|
|
|
|
|
@@ -5890,7 +5933,7 @@ public partial class ChatWindow : Window
|
|
|
|
|
if (!string.Equals(transport, "stdio", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
using var client = new McpClientService(server);
|
|
|
|
|
using var client = new McpClientService(BuildEffectiveMcpServer(server));
|
|
|
|
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
|
|
|
timeoutCts.CancelAfter(TimeSpan.FromSeconds(8));
|
|
|
|
|
var connected = await client.ConnectAsync(timeoutCts.Token).ConfigureAwait(false);
|
|
|
|
|
@@ -6186,11 +6229,33 @@ public partial class ChatWindow : Window
|
|
|
|
|
"add" => ("add", target),
|
|
|
|
|
"remove" => ("remove", target),
|
|
|
|
|
"reset" => ("reset", target),
|
|
|
|
|
"login" => ("login", target),
|
|
|
|
|
"logout" => ("logout", target),
|
|
|
|
|
"status" => ("status", target),
|
|
|
|
|
_ => ("help", text),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static (bool success, string serverTarget, string token, string error) ParseMcpLoginTarget(string target)
|
|
|
|
|
{
|
|
|
|
|
var raw = (target ?? "").Trim();
|
|
|
|
|
if (string.IsNullOrWhiteSpace(raw))
|
|
|
|
|
return (false, "", "", "사용법: /mcp login <서버명> <토큰>");
|
|
|
|
|
|
|
|
|
|
var firstSpace = raw.IndexOf(' ');
|
|
|
|
|
if (firstSpace <= 0 || firstSpace >= raw.Length - 1)
|
|
|
|
|
return (false, "", "", "토큰이 누락되었습니다. 사용법: /mcp login <서버명> <토큰>");
|
|
|
|
|
|
|
|
|
|
var serverTarget = raw[..firstSpace].Trim();
|
|
|
|
|
var token = raw[(firstSpace + 1)..].Trim();
|
|
|
|
|
if (string.IsNullOrWhiteSpace(serverTarget))
|
|
|
|
|
return (false, "", "", "서버명이 비어 있습니다.");
|
|
|
|
|
if (string.IsNullOrWhiteSpace(token))
|
|
|
|
|
return (false, "", "", "토큰이 비어 있습니다.");
|
|
|
|
|
|
|
|
|
|
return (true, serverTarget, token, "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static (bool success, McpServerEntry? entry, string error) ParseMcpAddTarget(string target)
|
|
|
|
|
{
|
|
|
|
|
var raw = (target ?? "").Trim();
|
|
|
|
|
@@ -6303,40 +6368,41 @@ public partial class ChatWindow : Window
|
|
|
|
|
{
|
|
|
|
|
var name = string.IsNullOrWhiteSpace(server.Name) ? "(이름 없음)" : server.Name;
|
|
|
|
|
var transport = string.IsNullOrWhiteSpace(server.Transport) ? "stdio" : server.Transport.Trim().ToLowerInvariant();
|
|
|
|
|
var authSuffix = _sessionMcpAuthTokens.ContainsKey(server.Name ?? "") ? " · Auth(Session)" : "";
|
|
|
|
|
if (!IsMcpServerEnabled(server))
|
|
|
|
|
{
|
|
|
|
|
lines.Add($"- {name} [{transport}] : {ResolveMcpDisplayStatus(isEnabled: false, transport, runtimeCheck, connected: false, toolCount: null)}");
|
|
|
|
|
lines.Add($"- {name} [{transport}] : {ResolveMcpDisplayStatus(isEnabled: false, transport, runtimeCheck, connected: false, toolCount: null)}{authSuffix}");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!runtimeCheck)
|
|
|
|
|
{
|
|
|
|
|
lines.Add($"- {name} [{transport}] : {ResolveMcpDisplayStatus(isEnabled: true, transport, runtimeCheck, connected: false, toolCount: null)}");
|
|
|
|
|
lines.Add($"- {name} [{transport}] : {ResolveMcpDisplayStatus(isEnabled: true, transport, runtimeCheck, connected: false, toolCount: null)}{authSuffix}");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.Equals(transport, "stdio", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
lines.Add($"- {name} [{transport}] : {ResolveMcpDisplayStatus(isEnabled: true, transport, runtimeCheck, connected: false, toolCount: null)}");
|
|
|
|
|
lines.Add($"- {name} [{transport}] : {ResolveMcpDisplayStatus(isEnabled: true, transport, runtimeCheck, connected: false, toolCount: null)}{authSuffix}");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using var client = new McpClientService(server);
|
|
|
|
|
using var client = new McpClientService(BuildEffectiveMcpServer(server));
|
|
|
|
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
|
|
|
timeoutCts.CancelAfter(TimeSpan.FromSeconds(8));
|
|
|
|
|
var connected = await client.ConnectAsync(timeoutCts.Token).ConfigureAwait(false);
|
|
|
|
|
if (!connected)
|
|
|
|
|
{
|
|
|
|
|
lines.Add($"- {name} [{transport}] : {ResolveMcpDisplayStatus(isEnabled: true, transport, runtimeCheck, connected: false, toolCount: null)}");
|
|
|
|
|
lines.Add($"- {name} [{transport}] : {ResolveMcpDisplayStatus(isEnabled: true, transport, runtimeCheck, connected: false, toolCount: null)}{authSuffix}");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var toolCount = client.Tools.Count;
|
|
|
|
|
var statusLabel = ResolveMcpDisplayStatus(isEnabled: true, transport, runtimeCheck, connected: true, toolCount);
|
|
|
|
|
lines.Add($"- {name} [{transport}] : {statusLabel}");
|
|
|
|
|
lines.Add($"- {name} [{transport}] : {statusLabel}{authSuffix}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lines.Add("명령: /mcp status | enable|disable <서버명|all> | reconnect <서버명|all> | add <서버명> :: stdio|sse ... | remove <서버명|all> | reset");
|
|
|
|
|
lines.Add("명령: /mcp status | enable|disable <서버명|all> | reconnect <서버명|all> | add <서버명> :: stdio|sse ... | remove <서버명|all> | reset | login <서버명> <토큰> | logout <서버명|all>");
|
|
|
|
|
return string.Join("\n", lines);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -6373,6 +6439,26 @@ public partial class ChatWindow : Window
|
|
|
|
|
return partial?.Name ?? "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private McpServerEntry BuildEffectiveMcpServer(McpServerEntry server)
|
|
|
|
|
{
|
|
|
|
|
var clone = new McpServerEntry
|
|
|
|
|
{
|
|
|
|
|
Name = server.Name,
|
|
|
|
|
Command = server.Command,
|
|
|
|
|
Args = server.Args?.ToList() ?? new List<string>(),
|
|
|
|
|
Env = new Dictionary<string, string>(server.Env ?? new Dictionary<string, string>(), StringComparer.OrdinalIgnoreCase),
|
|
|
|
|
Enabled = server.Enabled,
|
|
|
|
|
Transport = server.Transport,
|
|
|
|
|
Url = server.Url,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var key = clone.Name ?? "";
|
|
|
|
|
if (_sessionMcpAuthTokens.TryGetValue(key, out var token) && !string.IsNullOrWhiteSpace(token))
|
|
|
|
|
clone.Env["MCP_AUTH_TOKEN"] = token;
|
|
|
|
|
|
|
|
|
|
return clone;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<string> HandleMcpSlashAsync(string displayText, CancellationToken ct = default)
|
|
|
|
|
{
|
|
|
|
|
var llm = _settings.Settings.Llm;
|
|
|
|
|
@@ -6380,7 +6466,7 @@ public partial class ChatWindow : Window
|
|
|
|
|
var servers = llm.McpServers;
|
|
|
|
|
var (action, target) = ParseMcpAction(displayText);
|
|
|
|
|
if (action == "help")
|
|
|
|
|
return "사용법: /mcp, /mcp status, /mcp enable|disable <서버명|all>, /mcp reconnect <서버명|all>, /mcp add <서버명> :: stdio <명령> [인자...] | sse <URL>, /mcp remove <서버명|all>, /mcp reset";
|
|
|
|
|
return "사용법: /mcp, /mcp status, /mcp enable|disable <서버명|all>, /mcp reconnect <서버명|all>, /mcp add <서버명> :: stdio <명령> [인자...] | sse <URL>, /mcp remove <서버명|all>, /mcp reset, /mcp login <서버명> <토큰>, /mcp logout <서버명|all>";
|
|
|
|
|
|
|
|
|
|
if (action == "status")
|
|
|
|
|
return await BuildMcpRuntimeStatusTextAsync(servers, runtimeCheck: true, ct).ConfigureAwait(false);
|
|
|
|
|
@@ -6389,10 +6475,45 @@ public partial class ChatWindow : Window
|
|
|
|
|
{
|
|
|
|
|
var changed = _sessionMcpEnabledOverrides.Count;
|
|
|
|
|
_sessionMcpEnabledOverrides.Clear();
|
|
|
|
|
_sessionMcpAuthTokens.Clear();
|
|
|
|
|
return $"세션 MCP 오버라이드를 초기화했습니다. ({changed}개 해제)\n" +
|
|
|
|
|
await BuildMcpRuntimeStatusTextAsync(servers, runtimeCheck: false, ct).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (action == "login")
|
|
|
|
|
{
|
|
|
|
|
var (ok, serverTarget, token, error) = ParseMcpLoginTarget(target);
|
|
|
|
|
if (!ok)
|
|
|
|
|
return error;
|
|
|
|
|
|
|
|
|
|
var resolved = ResolveMcpServerName(servers, serverTarget);
|
|
|
|
|
if (string.IsNullOrWhiteSpace(resolved))
|
|
|
|
|
return $"로그인 대상 서버를 찾지 못했습니다: {serverTarget}";
|
|
|
|
|
|
|
|
|
|
_sessionMcpAuthTokens[resolved] = token;
|
|
|
|
|
return $"MCP 세션 토큰을 설정했습니다: {resolved}\n" +
|
|
|
|
|
await BuildMcpRuntimeStatusTextAsync(servers, runtimeCheck: true, ct).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (action == "logout")
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(target) || string.Equals(target, "all", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
var removed = _sessionMcpAuthTokens.Count;
|
|
|
|
|
_sessionMcpAuthTokens.Clear();
|
|
|
|
|
return $"모든 MCP 세션 토큰을 제거했습니다. ({removed}개)\n" +
|
|
|
|
|
await BuildMcpRuntimeStatusTextAsync(servers, runtimeCheck: false, ct).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var resolved = ResolveMcpServerName(servers, target);
|
|
|
|
|
if (string.IsNullOrWhiteSpace(resolved))
|
|
|
|
|
return $"로그아웃 대상 서버를 찾지 못했습니다: {target}";
|
|
|
|
|
|
|
|
|
|
var removedOne = _sessionMcpAuthTokens.Remove(resolved);
|
|
|
|
|
return $"MCP 세션 토큰 제거: {resolved} ({(removedOne ? 1 : 0)}개)\n" +
|
|
|
|
|
await BuildMcpRuntimeStatusTextAsync(servers, runtimeCheck: false, ct).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (action == "add")
|
|
|
|
|
{
|
|
|
|
|
var (ok, entry, error) = ParseMcpAddTarget(target);
|
|
|
|
|
@@ -6421,6 +6542,7 @@ public partial class ChatWindow : Window
|
|
|
|
|
var removed = servers.Count;
|
|
|
|
|
servers.Clear();
|
|
|
|
|
_sessionMcpEnabledOverrides.Clear();
|
|
|
|
|
_sessionMcpAuthTokens.Clear();
|
|
|
|
|
_settings.Save();
|
|
|
|
|
return $"MCP 서버를 모두 제거했습니다. ({removed}개)";
|
|
|
|
|
}
|
|
|
|
|
@@ -6431,6 +6553,7 @@ public partial class ChatWindow : Window
|
|
|
|
|
|
|
|
|
|
var removedCount = servers.RemoveAll(s => string.Equals(s.Name, resolved, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
_sessionMcpEnabledOverrides.Remove(resolved);
|
|
|
|
|
_sessionMcpAuthTokens.Remove(resolved);
|
|
|
|
|
_settings.Save();
|
|
|
|
|
return $"MCP 서버 제거 완료: {resolved} ({removedCount}개)\n" +
|
|
|
|
|
await BuildMcpRuntimeStatusTextAsync(servers, runtimeCheck: false, ct).ConfigureAwait(false);
|
|
|
|
|
@@ -6485,7 +6608,7 @@ public partial class ChatWindow : Window
|
|
|
|
|
return await BuildMcpRuntimeStatusTextAsync(targetServers, runtimeCheck: true, ct).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "사용법: /mcp, /mcp status, /mcp enable|disable <서버명|all>, /mcp reconnect <서버명|all>, /mcp add <서버명> :: stdio <명령> [인자...] | sse <URL>, /mcp remove <서버명|all>, /mcp reset";
|
|
|
|
|
return "사용법: /mcp, /mcp status, /mcp enable|disable <서버명|all>, /mcp reconnect <서버명|all>, /mcp add <서버명> :: stdio <명령> [인자...] | sse <URL>, /mcp remove <서버명|all>, /mcp reset, /mcp login <서버명> <토큰>, /mcp logout <서버명|all>";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string BuildSlashHeapDumpText()
|
|
|
|
|
@@ -6676,39 +6799,37 @@ public partial class ChatWindow : Window
|
|
|
|
|
if (string.Equals(slashSystem, "__PERMISSIONS__", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
var (permAction, _) = ParseGenericAction(displayText ?? "", "/permissions");
|
|
|
|
|
if (permAction is "ask" or "auto" or "deny")
|
|
|
|
|
if (TryApplyPermissionModeFromAction(permAction, out var appliedMode))
|
|
|
|
|
{
|
|
|
|
|
var next = permAction switch
|
|
|
|
|
{
|
|
|
|
|
"ask" => PermissionModeCatalog.Ask,
|
|
|
|
|
"auto" => PermissionModeCatalog.Auto,
|
|
|
|
|
_ => PermissionModeCatalog.Deny,
|
|
|
|
|
};
|
|
|
|
|
_settings.Settings.Llm.FilePermission = next;
|
|
|
|
|
_settings.Save();
|
|
|
|
|
_appState.LoadFromSettings(_settings);
|
|
|
|
|
UpdatePermissionUI();
|
|
|
|
|
SaveConversationSettings();
|
|
|
|
|
RefreshInlineSettingsPanel();
|
|
|
|
|
AppendLocalSlashResult(_activeTab, "/permissions", $"권한 모드를 {next}로 변경했습니다.");
|
|
|
|
|
AppendLocalSlashResult(_activeTab, "/permissions", $"권한 모드를 {appliedMode}로 변경했습니다.\n{BuildPermissionStatusText()}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (permAction == "status")
|
|
|
|
|
{
|
|
|
|
|
var mode = PermissionModeCatalog.NormalizeGlobalMode(_settings.Settings.Llm.FilePermission);
|
|
|
|
|
AppendLocalSlashResult(_activeTab, "/permissions", $"현재 권한 모드: {mode}\n사용법: /permissions ask|auto|deny|status");
|
|
|
|
|
AppendLocalSlashResult(_activeTab, "/permissions", $"{BuildPermissionStatusText()}\n사용법: /permissions ask|auto|deny|status");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BtnPermission_Click(this, new RoutedEventArgs());
|
|
|
|
|
AppendLocalSlashResult(_activeTab, "/permissions", "권한 설정 팝업을 열었습니다. (사용법: /permissions ask|auto|deny|status)");
|
|
|
|
|
OpenPermissionPanelFromSlash("/permissions", "사용법: /permissions ask|auto|deny|status");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (string.Equals(slashSystem, "__ALLOWED_TOOLS__", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
BtnPermission_Click(this, new RoutedEventArgs());
|
|
|
|
|
AppendLocalSlashResult(_activeTab, "/allowed-tools", "허용 도구(권한) 설정 팝업을 열었습니다.");
|
|
|
|
|
var (toolAction, _) = ParseGenericAction(displayText ?? "", "/allowed-tools");
|
|
|
|
|
if (TryApplyPermissionModeFromAction(toolAction, out var allowedMode))
|
|
|
|
|
{
|
|
|
|
|
AppendLocalSlashResult(_activeTab, "/allowed-tools", $"권한 모드를 {allowedMode}로 변경했습니다.\n{BuildPermissionStatusText()}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (toolAction == "status")
|
|
|
|
|
{
|
|
|
|
|
AppendLocalSlashResult(_activeTab, "/allowed-tools", $"{BuildPermissionStatusText()}\n사용법: /allowed-tools ask|auto|deny|status");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OpenPermissionPanelFromSlash("/allowed-tools", "사용법: /allowed-tools ask|auto|deny|status");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (string.Equals(slashSystem, "__MODEL__", StringComparison.Ordinal))
|
|
|
|
|
@@ -6729,8 +6850,7 @@ public partial class ChatWindow : Window
|
|
|
|
|
|
|
|
|
|
if (settingsAction == "permissions")
|
|
|
|
|
{
|
|
|
|
|
BtnPermission_Click(this, new RoutedEventArgs());
|
|
|
|
|
AppendLocalSlashResult(_activeTab, "/settings", "권한 설정 팝업을 열었습니다.");
|
|
|
|
|
OpenPermissionPanelFromSlash("/settings", "사용법: /settings permissions");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -7979,7 +8099,15 @@ public partial class ChatWindow : Window
|
|
|
|
|
|| BtnQuickRunningFilter == null || BtnQuickFailedFilter == null || BtnQuickHotSort == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ConversationQuickStrip.Visibility = (_runningConversationCount > 0 || _failedConversationCount > 0 || _spotlightConversationCount > 0)
|
|
|
|
|
var showFailureFilter = GetAgentUiExpressionLevel() == "rich";
|
|
|
|
|
if (BtnQuickFailedFilter != null)
|
|
|
|
|
BtnQuickFailedFilter.Visibility = showFailureFilter ? Visibility.Visible : Visibility.Collapsed;
|
|
|
|
|
|
|
|
|
|
var hasQuickSignal = _runningConversationCount > 0
|
|
|
|
|
|| _spotlightConversationCount > 0
|
|
|
|
|
|| (showFailureFilter && _failedConversationCount > 0);
|
|
|
|
|
|
|
|
|
|
ConversationQuickStrip.Visibility = hasQuickSignal
|
|
|
|
|
? Visibility.Visible
|
|
|
|
|
: Visibility.Collapsed;
|
|
|
|
|
|
|
|
|
|
@@ -7992,9 +8120,12 @@ public partial class ChatWindow : Window
|
|
|
|
|
BtnQuickRunningFilter.BorderThickness = new Thickness(1);
|
|
|
|
|
QuickRunningLabel.Foreground = _runningOnlyFilter ? BrushFromHex("#1D4ED8") : (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray);
|
|
|
|
|
|
|
|
|
|
BtnQuickFailedFilter.Background = _failedOnlyFilter ? BrushFromHex("#FEF2F2") : BrushFromHex("#F8FAFC");
|
|
|
|
|
BtnQuickFailedFilter.BorderBrush = _failedOnlyFilter ? BrushFromHex("#FCA5A5") : BrushFromHex("#E5E7EB");
|
|
|
|
|
BtnQuickFailedFilter.BorderThickness = new Thickness(1);
|
|
|
|
|
if (BtnQuickFailedFilter != null)
|
|
|
|
|
{
|
|
|
|
|
BtnQuickFailedFilter.Background = _failedOnlyFilter ? BrushFromHex("#FEF2F2") : BrushFromHex("#F8FAFC");
|
|
|
|
|
BtnQuickFailedFilter.BorderBrush = _failedOnlyFilter ? BrushFromHex("#FCA5A5") : BrushFromHex("#E5E7EB");
|
|
|
|
|
BtnQuickFailedFilter.BorderThickness = new Thickness(1);
|
|
|
|
|
}
|
|
|
|
|
QuickFailedLabel.Foreground = _failedOnlyFilter ? BrushFromHex("#991B1B") : (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray);
|
|
|
|
|
|
|
|
|
|
BtnQuickHotSort.Background = !_sortConversationsByRecent ? BrushFromHex("#F5F3FF") : BrushFromHex("#F8FAFC");
|
|
|
|
|
|