Compare commits
2 Commits
6bd8d5bb2c
...
36a9af8210
| Author | SHA1 | Date | |
|---|---|---|---|
| 36a9af8210 | |||
| d24c1f9edc |
15
README.md
15
README.md
@@ -7,6 +7,21 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
|
||||
개발 참고: Claw Code 동등성 작업 추적 문서
|
||||
`docs/claw-code-parity-plan.md`
|
||||
|
||||
- 업데이트: 2026-04-10 08:47 (KST)
|
||||
- 계획 확인 UI를 AX Agent 테마에 맞춰 다시 정리했습니다. 채팅 안 승인 카드는 `LauncherBackground`/`ItemBackground`/`BorderColor`/`AccentColor`를 기준으로 표면, 버튼 모서리, 입력 패널 간격을 통일해 기본 컨트롤 느낌을 줄였습니다.
|
||||
- 플랜 뷰어 창의 하단 승인 영역도 같은 시각 언어로 손봤습니다. 승인 전 안내 카드를 추가하고, 수정 입력 패널은 테마 배경과 테두리를 쓰도록 바꿨으며, 액션 버튼 호버도 `ItemHoverBackground` 중심으로 정리했습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0
|
||||
|
||||
- 업데이트: 2026-04-10 00:08 (KST)
|
||||
- `claude-code`와 비교했을 때 AX에 남아 있던 계획 선행과 후속 권유 톤을 더 줄였습니다. 기본 Cowork/Code 루프 안에 남아 있던 plan prelude/승인용 죽은 코드를 제거해, 별도 계획 생성 단계를 끼우지 않고 바로 모델+도구 실행으로 들어갑니다.
|
||||
- Code 최종 보고 재강제도 review 작업과 고영향 변경으로 좁혔습니다. 일반 수정은 변경 내용과 검증 근거가 충분하면 후속 계획이나 남은 리스크를 덧붙이도록 다시 유도하지 않고 마무리할 수 있습니다.
|
||||
- Cowork/Code 시스템 프롬프트도 같은 방향으로 정리했습니다. Cowork는 새 문서 생성 시 `document_plan`을 기본 선행 단계처럼 밀지 않고 필요할 때만 쓰게 했고, Code는 마지막 보고에서 미해결 사항이 있을 때만 리스크를 언급하게 바꿨습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0
|
||||
|
||||
- 업데이트: 2026-04-10 00:15 (KST)
|
||||
- Virtual DOM Diff 렌더 구현 완료: React reconciliation 방식의 키 기반 diff를 WPF에 적용. 렌더 체인이 StreamingAppend → Incremental → **DiffRender** → FullRender 4단계로 확장되어, prefix-match 실패 시에도 전체 재빌드 없이 변경분만 반영합니다.
|
||||
- 검증: `dotnet build` 경고 0 / 오류 0
|
||||
|
||||
- 업데이트: 2026-04-09 23:02 (KST)
|
||||
- IBM/Qwen 후속 호환을 한 단계 더 보강했습니다. 이제 IBM 배포형 응답에서 `generated_text`, `output_text`, `message.content`, `reasoning_content`가 문자열뿐 아니라 배열/블록 형태로 와도 텍스트를 추출하고, `content` 배열 안의 `tool_use/tool_call` 블록도 직접 읽어 도구 호출로 복구합니다.
|
||||
- 활성 도구 노출 순서도 다시 정리했습니다. `file_read/file_edit/glob/grep/lsp_code_intel/build_run/document_plan` 같은 기본 도구를 먼저 보여주고, `document_review/format_convert/tool_search/code_search`는 그 다음, `mcp_*`, `spawn_agent`, `wait_agents`, `task_*`는 더 뒤로 미뤄 `claude-code`처럼 기본 작업 도구가 먼저 선택되도록 했습니다.
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
# AX Copilot - 媛쒕컻 臾몄꽌
|
||||
|
||||
|
||||
## 계획 승인 UI 테마 정렬
|
||||
|
||||
- 업데이트: 2026-04-10 08:47 (KST)
|
||||
- `ChatWindow.AgentStatusPresentation`의 계획 승인 카드를 AX Agent 테마 리소스 기반으로 다시 정리했습니다. 승인/수정/취소 버튼의 라운드, 간격, 입력 패널 배경을 `LauncherBackground`, `ItemBackground`, `BorderColor`, `AccentColor` 축으로 통일해 채팅 본문과 더 자연스럽게 이어지도록 조정했습니다.
|
||||
- `PlanViewerWindow`의 승인 단계 UI도 같은 방향으로 손봤습니다. 승인 버튼 영역 앞에 검토 안내 카드를 추가하고, 수정 피드백 입력 패널은 테마 배경/테두리를 사용하며, 액션 버튼 호버는 단순 opacity 대신 `ItemHoverBackground` 계열로 반응하도록 바꿨습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0
|
||||
|
||||
## claude-code식 계획/후속 권유 최소화
|
||||
|
||||
- 업데이트: 2026-04-10 00:08 (KST)
|
||||
- `AgentLoopService`에서 과거 plan mode 잔재로 남아 있던 prelude/승인용 실행 계획 블록을 제거했습니다. 기본 Cowork/Code 루프는 이제 별도 “계획만 먼저 생성” 단계 없이 바로 메인 모델+도구 반복으로 들어갑니다. 사용자가 명시적으로 계획을 원할 때만 응답 텍스트 수준에서 계획을 제시하고, 루프 자체는 `claude-code`처럼 실행 중심으로 유지합니다.
|
||||
- `AgentLoopTransitions.Verification`의 `FinalReportGate`는 review 작업 또는 고영향 변경일 때만 구조화된 재정리를 요구합니다. 일반 수정은 변경 내용과 검증 근거만 충분하면 “remaining risk / next action”을 추가로 다시 쓰게 하지 않습니다.
|
||||
- `BuildFinalReportQualityPrompt`도 같은 기준으로 완화했습니다. 남은 리스크/추가 확인은 실제로 미해결 사항이 남아 있을 때만 적도록 바꾸고, 후속 권유는 기본이 아니라 조건부 정보로 낮췄습니다.
|
||||
- `ChatWindow.SystemPromptBuilder`의 Cowork/Code 프롬프트도 조정했습니다. Cowork는 새 문서 생성 시 `document_plan`을 기본 선행 단계처럼 밀지 않고 “필요할 때만” 사용하게 했고, Code의 REPORT 단계는 “변경 내용과 검증 요약”을 기본으로 하며 미해결 리스크만 선택적으로 언급하게 바꿨습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0
|
||||
|
||||
## claude-code식 후속 호환/선택성 보강
|
||||
|
||||
- 업데이트: 2026-04-09 23:02 (KST)
|
||||
|
||||
@@ -239,9 +239,6 @@ public partial class AgentLoopService
|
||||
var recentTaskRetryQuality = TryGetRecentTaskRetryQuality(taskPolicy.TaskType);
|
||||
maxRetry = ComputeQualityAwareMaxRetry(maxRetry, recentTaskRetryQuality, taskPolicy.TaskType);
|
||||
|
||||
// 플랜 prelude는 현재 정책상 비활성
|
||||
var shouldGeneratePlanPrelude = false;
|
||||
|
||||
var context = BuildContext();
|
||||
InjectTaskTypeGuidance(messages, taskPolicy);
|
||||
InjectExplorationScopeGuidance(messages, explorationState.Scope);
|
||||
@@ -340,148 +337,6 @@ public partial class AgentLoopService
|
||||
workFolder = context.WorkFolder
|
||||
}));
|
||||
|
||||
// ── 과거 plan mode 잔재. 현재 정책상 비활성 ──
|
||||
if (shouldGeneratePlanPrelude)
|
||||
{
|
||||
iteration++;
|
||||
EmitEvent(AgentEventType.Thinking, "", "실행 계획 생성 중...");
|
||||
|
||||
// 계획 생성 전용 시스템 지시를 임시 추가
|
||||
var planInstruction = new ChatMessage
|
||||
{
|
||||
Role = "user",
|
||||
Content = "[System] 도구를 호출하지 마세요. 먼저 실행 계획을 번호 매긴 단계로 작성하세요. " +
|
||||
"각 단계에 사용할 도구와 대상을 구체적으로 명시하세요. " +
|
||||
"계획만 제시하고 실행은 하지 마세요."
|
||||
};
|
||||
messages.Add(planInstruction);
|
||||
|
||||
// 도구 없이 텍스트만 요청
|
||||
string planText;
|
||||
try
|
||||
{
|
||||
planText = await _llm.SendAsync(messages, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
EmitEvent(AgentEventType.Error, "", $"LLM 오류: {ex.Message}");
|
||||
return $"⚠ LLM 오류: {ex.Message}";
|
||||
}
|
||||
|
||||
// 계획 지시 메시지 제거 (실제 실행 시 혼란 방지)
|
||||
messages.Remove(planInstruction);
|
||||
|
||||
// 계획 추출
|
||||
planSteps = TaskDecomposer.ExtractSteps(planText);
|
||||
planExtracted = true;
|
||||
|
||||
if (planSteps.Count > 0)
|
||||
{
|
||||
EmitEvent(AgentEventType.Planning, "", $"작업 계획: {planSteps.Count}단계",
|
||||
steps: planSteps);
|
||||
|
||||
// 사용자 승인 대기
|
||||
if (UserDecisionCallback != null)
|
||||
{
|
||||
EmitEvent(
|
||||
AgentEventType.Decision,
|
||||
"",
|
||||
$"계획 확인 대기 · {planSteps.Count}단계",
|
||||
steps: planSteps);
|
||||
|
||||
var decision = await UserDecisionCallback(
|
||||
planText,
|
||||
new List<string> { "승인", "수정 요청", "취소" });
|
||||
EmitPlanDecisionResultEvent(decision, planSteps);
|
||||
|
||||
if (decision == "취소")
|
||||
{
|
||||
EmitEvent(AgentEventType.Complete, "", "작업이 중단되었습니다");
|
||||
return "작업이 중단되었습니다.";
|
||||
}
|
||||
else if (TryParseApprovedPlanDecision(decision, out var approvedPlanText, out var approvedPlanSteps))
|
||||
{
|
||||
planText = approvedPlanText;
|
||||
planSteps = approvedPlanSteps;
|
||||
}
|
||||
else if (decision != null && decision != "승인")
|
||||
{
|
||||
// 수정 요청 — 피드백으로 계획 재생성
|
||||
messages.Add(new ChatMessage { Role = "assistant", Content = planText });
|
||||
messages.Add(new ChatMessage { Role = "user", Content = decision + "\n위 피드백을 반영하여 실행 계획을 다시 작성하세요." });
|
||||
|
||||
// 재생성 루프 (최대 3회)
|
||||
for (int retry = 0; retry < 3; retry++)
|
||||
{
|
||||
try { planText = await _llm.SendAsync(messages, ct); }
|
||||
catch { break; }
|
||||
|
||||
planSteps = TaskDecomposer.ExtractSteps(planText);
|
||||
if (planSteps.Count > 0)
|
||||
{
|
||||
EmitEvent(AgentEventType.Planning, "", $"수정된 계획: {planSteps.Count}단계",
|
||||
steps: planSteps);
|
||||
}
|
||||
|
||||
EmitEvent(
|
||||
AgentEventType.Decision,
|
||||
"",
|
||||
$"수정 계획 확인 대기 · {planSteps.Count}단계",
|
||||
steps: planSteps);
|
||||
|
||||
decision = await UserDecisionCallback(
|
||||
planText,
|
||||
new List<string> { "승인", "수정 요청", "취소" });
|
||||
EmitPlanDecisionResultEvent(decision, planSteps);
|
||||
|
||||
if (decision == "취소")
|
||||
{
|
||||
EmitEvent(AgentEventType.Complete, "", "작업이 중단되었습니다");
|
||||
return "작업이 중단되었습니다.";
|
||||
}
|
||||
if (TryParseApprovedPlanDecision(decision, out var revisedPlanText, out var revisedPlanSteps))
|
||||
{
|
||||
planText = revisedPlanText;
|
||||
planSteps = revisedPlanSteps;
|
||||
break;
|
||||
}
|
||||
if (decision == null || decision == "승인") break;
|
||||
|
||||
// 재수정
|
||||
messages.Add(new ChatMessage { Role = "assistant", Content = planText });
|
||||
messages.Add(new ChatMessage { Role = "user", Content = decision + "\n위 피드백을 반영하여 실행 계획을 다시 작성하세요." });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 승인된 계획을 컨텍스트에 포함하여 실행 유도
|
||||
// 도구 호출을 명확히 강제하여 텍스트 응답만 반환하는 경우 방지
|
||||
messages.Add(new ChatMessage { Role = "assistant", Content = planText });
|
||||
|
||||
// 1차 계획의 단계들을 document_plan의 sections_hint로 전달하도록 지시
|
||||
// → BuildSections() 하드코딩 대신 LLM이 잡은 섹션 구조가 문서에 반영됨
|
||||
var planSectionsHint = planSteps.Count > 0
|
||||
? string.Join(", ", planSteps)
|
||||
: "";
|
||||
var sectionInstruction = !string.IsNullOrEmpty(planSectionsHint)
|
||||
? $"document_plan 도구를 호출할 때 sections_hint 파라미터에 위 계획의 섹션/단계를 그대로 넣으세요: \"{planSectionsHint}\""
|
||||
: "";
|
||||
|
||||
messages.Add(new ChatMessage { Role = "user",
|
||||
Content = "계획이 승인되었습니다. 지금 즉시 1단계부터 도구(tool)를 호출하여 실행을 시작하세요. " +
|
||||
"텍스트로 설명하지 말고 반드시 도구를 호출하세요." +
|
||||
(string.IsNullOrEmpty(sectionInstruction) ? "" : "\n" + sectionInstruction) });
|
||||
}
|
||||
else
|
||||
{
|
||||
// 계획 추출 실패 — assistant 응답으로 추가하고 일반 모드로 진행
|
||||
if (!string.IsNullOrEmpty(planText))
|
||||
messages.Add(new ChatMessage { Role = "assistant", Content = planText });
|
||||
}
|
||||
}
|
||||
|
||||
while (iteration < maxIterations && !ct.IsCancellationRequested)
|
||||
{
|
||||
iteration++;
|
||||
@@ -3538,8 +3393,8 @@ public partial class AgentLoopService
|
||||
private static string BuildFinalReportQualityPrompt(TaskTypePolicy taskPolicy, bool highImpact)
|
||||
{
|
||||
var taskLine = taskPolicy.FinalReportTaskLine;
|
||||
var riskLine = highImpact
|
||||
? "고영향 변경이므로 남은 리스크나 추가 확인 필요 사항도 반드시 적으세요.\n"
|
||||
var riskLine = taskPolicy.IsReviewTask || highImpact
|
||||
? "남은 리스크나 추가 확인 필요 사항이 실제로 남아 있을 때만 짧게 적으세요.\n"
|
||||
: "";
|
||||
|
||||
return "[System:FinalReportQuality] 최종 답변을 더 구조적으로 정리하세요.\n" +
|
||||
@@ -3551,7 +3406,7 @@ public partial class AgentLoopService
|
||||
"6. review 작업이면 이슈별로 수정 완료/미수정 상태를 분리해 적으세요\n" +
|
||||
taskLine +
|
||||
riskLine +
|
||||
"가능하면 짧고 명확하게 요약하세요.";
|
||||
"후속 권유는 실제 미해결 위험이나 추가 확인이 남았을 때만 포함하세요. 가능하면 짧고 명확하게 요약하세요.";
|
||||
}
|
||||
|
||||
private static string BuildFinalReportQualityPrompt(string taskType, bool highImpact)
|
||||
|
||||
@@ -55,9 +55,11 @@ public partial class AgentLoopService
|
||||
var hasDiffEvidence = HasDiffEvidenceAfterLastModification(messages);
|
||||
var hasRecentBuildOrTestEvidence = HasBuildOrTestEvidenceAfterLastModification(messages);
|
||||
var hasSuccessfulBuildAndTestEvidence = HasSuccessfulBuildAndTestAfterLastModification(messages);
|
||||
var hasLightweightCompletionEvidence = hasCodeVerificationEvidence
|
||||
|| hasDiffEvidence
|
||||
|| hasRecentBuildOrTestEvidence;
|
||||
if (executionPolicy.CodeVerificationGateMaxRetries > 0
|
||||
&& !hasCodeVerificationEvidence
|
||||
&& !(hasDiffEvidence && hasRecentBuildOrTestEvidence && !requireHighImpactCodeVerification)
|
||||
&& !(requireHighImpactCodeVerification ? hasCodeVerificationEvidence : hasLightweightCompletionEvidence)
|
||||
&& runState.CodeVerificationGateRetry < executionPolicy.CodeVerificationGateMaxRetries)
|
||||
{
|
||||
runState.CodeVerificationGateRetry++;
|
||||
@@ -93,9 +95,12 @@ public partial class AgentLoopService
|
||||
return true;
|
||||
}
|
||||
|
||||
var hasBlockingCodeEvidenceGap = !hasCodeVerificationEvidence
|
||||
var hasBlockingCodeEvidenceGap = !(requireHighImpactCodeVerification ? hasCodeVerificationEvidence : hasLightweightCompletionEvidence)
|
||||
|| (requireHighImpactCodeVerification && !hasSuccessfulBuildAndTestEvidence);
|
||||
var shouldRequestStructuredFinalReport =
|
||||
taskPolicy.IsReviewTask || requireHighImpactCodeVerification;
|
||||
if (executionPolicy.FinalReportGateMaxRetries > 0
|
||||
&& shouldRequestStructuredFinalReport
|
||||
&& !hasBlockingCodeEvidenceGap
|
||||
&& !HasSufficientFinalReportEvidence(textResponse, taskPolicy, requireHighImpactCodeVerification, messages)
|
||||
&& runState.FinalReportGateRetry < executionPolicy.FinalReportGateMaxRetries)
|
||||
|
||||
1586
src/AxCopilot/Views/ChatWindow.AgentStatusPresentation.cs
Normal file
1586
src/AxCopilot/Views/ChatWindow.AgentStatusPresentation.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -61,10 +61,10 @@ public partial class ChatWindow
|
||||
sb.AppendLine($"Today's date: {DateTime.Now:yyyy년 M월 d일} ({DateTime.Now:yyyy-MM-dd}, {DateTime.Now:dddd}).");
|
||||
sb.AppendLine("Available skills: excel_create (.xlsx), docx_create (.docx), csv_create (.csv), markdown_create (.md), html_create (.html), script_create (.bat/.ps1), document_review (품질 검증), format_convert (포맷 변환).");
|
||||
|
||||
sb.AppendLine("\nOnly present a step-by-step plan when the user explicitly asks for a plan or when the task is too ambiguous to execute safely.");
|
||||
sb.AppendLine("\nOnly present a step-by-step plan when the user explicitly asks for a plan or a short execution outline is required to unblock the task safely.");
|
||||
sb.AppendLine("For ordinary Cowork requests, proceed directly with the work and focus on producing the real artifact.");
|
||||
sb.AppendLine("If the user asks for a brand-new report, proposal, analysis, manual, or other document and does not explicitly ask to reference workspace files, do NOT start with glob, grep, document_read, or folder_map.");
|
||||
sb.AppendLine("In that case, go straight to document_plan when helpful, then immediately call the creation tool such as docx_create, html_create, markdown_create, excel_create, or document_assemble.");
|
||||
sb.AppendLine("In that case, go straight to the creation tool. Use document_plan only when it materially improves a multi-section document.");
|
||||
sb.AppendLine("After creating files, summarize what was created and include the actual output path.");
|
||||
sb.AppendLine("Do not stop after a single step. Continue autonomously until the request is completed or a concrete blocker (permission denial, missing dependency, hard error) is encountered.");
|
||||
sb.AppendLine("When adapting external references, rewrite names/structure/comments to AX Copilot style. Avoid clone-like outputs.");
|
||||
@@ -239,7 +239,7 @@ public partial class ChatWindow
|
||||
sb.AppendLine("3. IMPLEMENT: Apply the smallest safe edit. Use file_edit for existing files and file_write for new files.");
|
||||
sb.AppendLine("4. VERIFY: Run build_run/test_loop when the change affects buildable or testable behavior, or when the user explicitly asks for verification.");
|
||||
sb.AppendLine(" - Use git_tool(diff) when it helps confirm the final change set or explain what changed.");
|
||||
sb.AppendLine("5. REPORT: Summarize what changed, what was verified, and any remaining risk.");
|
||||
sb.AppendLine("5. REPORT: Summarize what changed and what was verified. Mention remaining risk only when something is actually unresolved.");
|
||||
|
||||
sb.AppendLine("\n## Development Environment");
|
||||
sb.AppendLine("Use dev_env_detect to check installed IDEs, runtimes, and build tools before running commands.");
|
||||
|
||||
@@ -859,11 +859,30 @@ internal sealed class PlanViewerWindow : Window
|
||||
_btnPanel.Children.Clear();
|
||||
var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
|
||||
var secondaryText = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var accentColor = accentBrush is SolidColorBrush accentSolid ? accentSolid.Color : Color.FromRgb(0x4B, 0x5E, 0xFC);
|
||||
|
||||
var approveLabel = _uiExpressionLevel == "simple" ? "승인" : "승인 후 실행";
|
||||
var editLabel = _uiExpressionLevel == "simple" ? "수정" : "수정 피드백";
|
||||
var rejectLabel = _uiExpressionLevel == "simple" ? "취소" : "거부";
|
||||
|
||||
_btnPanel.Children.Add(new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x16, accentColor.R, accentColor.G, accentColor.B)),
|
||||
BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, accentColor.R, accentColor.G, accentColor.B)),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(10),
|
||||
Padding = new Thickness(12, 8, 12, 8),
|
||||
Margin = new Thickness(0, 0, 12, 0),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = "검토가 끝나면 바로 실행하거나 방향만 짧게 남길 수 있습니다.",
|
||||
FontSize = 11.5,
|
||||
Foreground = secondaryText,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
}
|
||||
});
|
||||
|
||||
var approveBtn = CreateActionButton("\uE73E", approveLabel, accentBrush, Brushes.White, true);
|
||||
approveBtn.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
@@ -904,20 +923,31 @@ internal sealed class PlanViewerWindow : Window
|
||||
|
||||
private void ShowEditInput()
|
||||
{
|
||||
var itemBackground = Application.Current.TryFindResource("ItemBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40));
|
||||
var launcherBackground = Application.Current.TryFindResource("LauncherBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E));
|
||||
var primaryText = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var secondaryText = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var borderBrush = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
|
||||
|
||||
var editPanel = new Border
|
||||
{
|
||||
Margin = new Thickness(20, 0, 20, 12),
|
||||
Padding = new Thickness(12, 8, 12, 8),
|
||||
CornerRadius = new CornerRadius(10),
|
||||
Background = Application.Current.TryFindResource("ItemBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40)),
|
||||
Padding = new Thickness(14, 12, 14, 12),
|
||||
CornerRadius = new CornerRadius(12),
|
||||
Background = itemBackground,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
};
|
||||
var editStack = new StackPanel();
|
||||
editStack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "수정 사항을 입력하세요:",
|
||||
Text = "어떤 방향으로 바꾸면 좋을지 짧게 남겨주세요.",
|
||||
FontSize = 11.5,
|
||||
Foreground = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
|
||||
Foreground = secondaryText,
|
||||
Margin = new Thickness(0, 0, 0, 6),
|
||||
});
|
||||
var textBox = new TextBox
|
||||
@@ -927,29 +957,32 @@ internal sealed class PlanViewerWindow : Window
|
||||
AcceptsReturn = true,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
FontSize = 13,
|
||||
Background = Application.Current.TryFindResource("LauncherBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E)),
|
||||
Foreground = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White,
|
||||
CaretBrush = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White,
|
||||
BorderBrush = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray,
|
||||
Background = launcherBackground,
|
||||
Foreground = primaryText,
|
||||
CaretBrush = primaryText,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(10, 8, 10, 8),
|
||||
};
|
||||
editStack.Children.Add(textBox);
|
||||
|
||||
var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
|
||||
var actionRow = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Thickness(0, 8, 0, 0),
|
||||
};
|
||||
|
||||
var sendBtn = new Border
|
||||
{
|
||||
Background = accentBrush,
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Padding = new Thickness(14, 6, 14, 6),
|
||||
Margin = new Thickness(0, 8, 0, 0),
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
Cursor = Cursors.Hand,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = "전송", FontSize = 12.5, FontWeight = FontWeights.SemiBold, Foreground = Brushes.White,
|
||||
Text = "수정 요청 보내기", FontSize = 12.5, FontWeight = FontWeights.SemiBold, Foreground = Brushes.White,
|
||||
},
|
||||
};
|
||||
sendBtn.MouseEnter += (s, _) => ((Border)s).Opacity = 0.85;
|
||||
@@ -960,7 +993,36 @@ internal sealed class PlanViewerWindow : Window
|
||||
if (string.IsNullOrEmpty(feedback)) return;
|
||||
_tcs?.TrySetResult(feedback);
|
||||
};
|
||||
editStack.Children.Add(sendBtn);
|
||||
actionRow.Children.Add(sendBtn);
|
||||
|
||||
var closeBtn = new Border
|
||||
{
|
||||
Background = Brushes.Transparent,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Padding = new Thickness(14, 6, 14, 6),
|
||||
Cursor = Cursors.Hand,
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = "닫기",
|
||||
FontSize = 12.5,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = secondaryText,
|
||||
},
|
||||
};
|
||||
closeBtn.MouseEnter += (s, _) => ((Border)s).Background = itemBackground;
|
||||
closeBtn.MouseLeave += (s, _) => ((Border)s).Background = Brushes.Transparent;
|
||||
closeBtn.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
if (editPanel.Parent is Grid grid)
|
||||
{
|
||||
grid.Children.Remove(editPanel);
|
||||
_btnPanel.Margin = new Thickness(20, 12, 20, 16);
|
||||
}
|
||||
};
|
||||
actionRow.Children.Add(closeBtn);
|
||||
editStack.Children.Add(actionRow);
|
||||
editPanel.Child = editStack;
|
||||
|
||||
if (_btnPanel.Parent is Grid parentGrid)
|
||||
@@ -1008,10 +1070,12 @@ internal sealed class PlanViewerWindow : Window
|
||||
Brush textColor, bool filled)
|
||||
{
|
||||
var color = ((SolidColorBrush)borderColor).Color;
|
||||
var hoverBg = Application.Current?.TryFindResource("ItemHoverBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF));
|
||||
var btn = new Border
|
||||
{
|
||||
CornerRadius = new CornerRadius(12),
|
||||
Padding = new Thickness(16, 8, 16, 8),
|
||||
CornerRadius = new CornerRadius(14),
|
||||
Padding = new Thickness(16, 9, 16, 9),
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
Cursor = Cursors.Hand,
|
||||
Background = filled ? borderColor
|
||||
@@ -1033,8 +1097,20 @@ internal sealed class PlanViewerWindow : Window
|
||||
Foreground = filled ? Brushes.White : textColor,
|
||||
});
|
||||
btn.Child = sp;
|
||||
btn.MouseEnter += (s, _) => ((Border)s).Opacity = 0.85;
|
||||
btn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0;
|
||||
btn.MouseEnter += (s, _) =>
|
||||
{
|
||||
var border = (Border)s;
|
||||
border.Opacity = 0.96;
|
||||
if (!filled)
|
||||
border.Background = hoverBg;
|
||||
};
|
||||
btn.MouseLeave += (s, _) =>
|
||||
{
|
||||
var border = (Border)s;
|
||||
border.Opacity = 1.0;
|
||||
if (!filled)
|
||||
border.Background = new SolidColorBrush(Color.FromArgb(0x18, color.R, color.G, color.B));
|
||||
};
|
||||
return btn;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user