변경 목적: - AX Agent의 도구 이름, 내부 설정, 스킬 정책, 실행 루프 사이의 불일치를 줄이고 전체 동작 품질을 높인다. - claw-code 수준의 일관된 동작 품질을 참고하되 AX 구조에 맞는 고유한 카탈로그·정규화 레이어로 재구성한다. 핵심 수정사항: - 도구 canonical id, legacy alias, 탭 노출, 설정 카테고리, read-only 분류를 중앙 카탈로그로 통합했다. - ToolRegistry, AgentLoopService, 병렬 실행 분류, 권한 처리, 훅 처리, 스킬 allowed-tools 해석이 같은 이름 체계를 사용하도록 정리했다. - Agent 설정/일반 설정/도움말의 도구 카드와 훅 편집기, 스킬 설명을 현재 런타임 구조에 맞게 갱신했다. - 컨텍스트 압축, intent gate, spawn agents, session learning, model prompt adapter, workspace context 관련 변경과 테스트 추가를 함께 반영했다. - 문서 이력과 비교/로드맵 문서를 최신 상태로 갱신했다. 검증 결과: - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_toolcat\ -p:IntermediateOutputPath=obj\verify_toolcat\ : 경고 0 / 오류 0 - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter AgentToolCatalogTests -p:OutputPath=bin\verify_toolcat_tests\ -p:IntermediateOutputPath=obj\verify_toolcat_tests\ : 통과 8
1338 lines
62 KiB
HTML
1338 lines
62 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko" data-theme="dark">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AX Copilot - 내부 기술 세미나</title>
|
|
<style>
|
|
/* ═══════════════════════════════════════════════════════════
|
|
THEME VARIABLES
|
|
═══════════════════════════════════════════════════════════ */
|
|
:root {
|
|
--accent: #6C8EEF;
|
|
--accent2: #A78BFA;
|
|
--green: #34D399;
|
|
--amber: #FBBF24;
|
|
--red: #F87171;
|
|
--cyan: #22D3EE;
|
|
--transition: 0.3s ease;
|
|
}
|
|
|
|
[data-theme="dark"] {
|
|
--bg: #0f1117;
|
|
--bg2: #0b0d13;
|
|
--surface: #161822;
|
|
--surface2: #1c1f2e;
|
|
--surface3: #22253a;
|
|
--border: #2a2d3e;
|
|
--text: #e2e8f0;
|
|
--text-dim: #8892a8;
|
|
--text-inv: #1a1a2e;
|
|
--hero-grad1: #161822;
|
|
--hero-grad2: #0f1117;
|
|
--hero-glow: rgba(108,142,239,0.12);
|
|
--shadow: rgba(0,0,0,0.3);
|
|
--code-bg: rgba(108,142,239,0.1);
|
|
--code-border: rgba(108,142,239,0.15);
|
|
--scroll-track: #161822;
|
|
--scroll-thumb: #2a2d3e;
|
|
}
|
|
|
|
[data-theme="light"] {
|
|
--bg: #f8fafc;
|
|
--bg2: #f1f5f9;
|
|
--surface: #ffffff;
|
|
--surface2: #f1f5f9;
|
|
--surface3: #e8ecf2;
|
|
--border: #e2e8f0;
|
|
--text: #1e293b;
|
|
--text-dim: #64748b;
|
|
--text-inv: #ffffff;
|
|
--hero-grad1: #eef2ff;
|
|
--hero-grad2: #f8fafc;
|
|
--hero-glow: rgba(108,142,239,0.08);
|
|
--shadow: rgba(0,0,0,0.06);
|
|
--code-bg: rgba(108,142,239,0.06);
|
|
--code-border: rgba(108,142,239,0.12);
|
|
--scroll-track: #f1f5f9;
|
|
--scroll-thumb: #cbd5e1;
|
|
--accent: #4f6fd9;
|
|
--accent2: #8b6fc0;
|
|
--green: #16a34a;
|
|
--amber: #d97706;
|
|
--red: #dc2626;
|
|
--cyan: #0891b2;
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
html { scroll-behavior: smooth; }
|
|
|
|
body {
|
|
font-family: 'Pretendard', 'Segoe UI', -apple-system, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
line-height: 1.75;
|
|
-webkit-font-smoothing: antialiased;
|
|
transition: background var(--transition), color var(--transition);
|
|
}
|
|
|
|
::-webkit-scrollbar { width: 8px; }
|
|
::-webkit-scrollbar-track { background: var(--scroll-track); }
|
|
::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 4px; }
|
|
|
|
/* ═══════ Theme Toggle Button ═══════ */
|
|
.theme-toggle {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
z-index: 1000;
|
|
width: 44px; height: 44px;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--border);
|
|
background: var(--surface);
|
|
color: var(--text);
|
|
font-size: 20px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 2px 12px var(--shadow);
|
|
transition: all var(--transition);
|
|
}
|
|
.theme-toggle:hover {
|
|
transform: scale(1.1);
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 16px rgba(108,142,239,0.3);
|
|
}
|
|
|
|
/* ═══════ Hero ═══════ */
|
|
.hero {
|
|
position: relative;
|
|
padding: 80px 0 60px;
|
|
text-align: center;
|
|
overflow: hidden;
|
|
background: linear-gradient(180deg, var(--hero-grad1) 0%, var(--hero-grad2) 100%);
|
|
transition: background var(--transition);
|
|
}
|
|
.hero::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -120px; left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 600px; height: 600px;
|
|
background: radial-gradient(circle, var(--hero-glow) 0%, transparent 70%);
|
|
pointer-events: none;
|
|
}
|
|
.hero-badge {
|
|
display: inline-block;
|
|
padding: 4px 14px;
|
|
border-radius: 20px;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
letter-spacing: 2px;
|
|
text-transform: uppercase;
|
|
color: var(--accent);
|
|
border: 1px solid rgba(108,142,239,0.3);
|
|
background: rgba(108,142,239,0.08);
|
|
margin-bottom: 20px;
|
|
}
|
|
.hero h1 {
|
|
font-size: 44px;
|
|
font-weight: 800;
|
|
letter-spacing: -0.5px;
|
|
background: linear-gradient(135deg, var(--text), var(--accent), var(--accent2));
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
margin-bottom: 12px;
|
|
}
|
|
.hero p { font-size: 16px; color: var(--text-dim); max-width: 560px; margin: 0 auto; }
|
|
.hero-chips {
|
|
margin-top: 28px;
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.hero-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 5px 14px;
|
|
border-radius: 20px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
color: var(--text-dim);
|
|
transition: all var(--transition);
|
|
}
|
|
.hero-chip .dot { width: 6px; height: 6px; border-radius: 50%; }
|
|
|
|
/* ═══════ Layout — Sidebar + Content ═══════ */
|
|
.page-wrapper { display: flex; min-height: 100vh; }
|
|
.container { max-width: 980px; padding: 0 28px; flex: 1; margin-left: 280px; margin-right: auto; }
|
|
@supports (margin-left: max(0px, 0px)) {
|
|
.container { margin-left: max(280px, calc((100vw + 280px - 980px) / 2)); }
|
|
}
|
|
|
|
/* ═══════ Sidebar TOC ═══════ */
|
|
.toc {
|
|
position: fixed;
|
|
top: 0; left: 0;
|
|
width: 280px;
|
|
height: 100vh;
|
|
overflow-y: auto;
|
|
background: var(--surface);
|
|
border-right: 1px solid var(--border);
|
|
padding: 24px 16px;
|
|
z-index: 100;
|
|
box-shadow: 2px 0 16px var(--shadow);
|
|
transition: all var(--transition);
|
|
}
|
|
.toc h3 {
|
|
font-size: 11px; text-transform: uppercase; letter-spacing: 2px;
|
|
color: var(--text-dim); margin-bottom: 14px; font-weight: 700;
|
|
padding: 0 8px;
|
|
}
|
|
.toc-grid {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
.toc a {
|
|
display: flex; align-items: center; gap: 8px;
|
|
text-decoration: none; color: var(--text);
|
|
padding: 6px 8px;
|
|
border-radius: 6px;
|
|
font-size: 12.5px;
|
|
transition: all 0.15s;
|
|
}
|
|
.toc a:hover { background: var(--surface2); color: var(--accent); }
|
|
.toc a.active { background: rgba(108,142,239,0.1); color: var(--accent); font-weight: 600; }
|
|
.toc-num {
|
|
width: 20px; height: 20px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
border-radius: 5px;
|
|
font-size: 10px; font-weight: 700;
|
|
background: rgba(108,142,239,0.1);
|
|
color: var(--accent);
|
|
flex-shrink: 0;
|
|
}
|
|
.toc-sub { padding-left: 28px; font-size: 11.5px; color: var(--text-dim); }
|
|
.toc-sub:hover { color: var(--accent); }
|
|
.toc-divider {
|
|
height: 1px;
|
|
background: var(--border);
|
|
margin: 6px 0;
|
|
}
|
|
.toc::-webkit-scrollbar { width: 4px; }
|
|
.toc::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
|
|
/* ═══════ Sections ═══════ */
|
|
.section { margin-bottom: 64px; scroll-margin-top: 24px; }
|
|
.section-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
margin-bottom: 24px;
|
|
padding-bottom: 16px;
|
|
border-bottom: 2px solid var(--border);
|
|
}
|
|
.section-num {
|
|
width: 38px; height: 38px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
border-radius: 10px;
|
|
font-size: 16px; font-weight: 800;
|
|
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
color: #fff;
|
|
flex-shrink: 0;
|
|
}
|
|
.section-header h2 { font-size: 24px; font-weight: 700; }
|
|
|
|
/* ═══════ Sub-sections ═══════ */
|
|
h3 {
|
|
font-size: 17px; font-weight: 700;
|
|
color: var(--accent);
|
|
margin: 32px 0 12px;
|
|
display: flex; align-items: center; gap: 8px;
|
|
scroll-margin-top: 24px;
|
|
}
|
|
h3 .icon { font-size: 14px; opacity: 0.7; }
|
|
h4 {
|
|
font-size: 14px; font-weight: 600;
|
|
color: var(--text);
|
|
margin: 18px 0 8px;
|
|
border-left: 3px solid var(--accent);
|
|
padding-left: 10px;
|
|
}
|
|
p, li { font-size: 14px; color: var(--text); }
|
|
p { margin-bottom: 10px; }
|
|
ul { padding-left: 20px; margin-bottom: 12px; }
|
|
li { margin-bottom: 4px; }
|
|
li::marker { color: var(--text-dim); }
|
|
strong { color: var(--text); font-weight: 700; }
|
|
em { color: var(--accent); font-style: normal; font-weight: 600; }
|
|
code {
|
|
font-family: 'Cascadia Code', 'Fira Code', monospace;
|
|
background: var(--code-bg);
|
|
border: 1px solid var(--code-border);
|
|
border-radius: 4px;
|
|
padding: 1px 6px;
|
|
font-size: 12.5px;
|
|
color: var(--cyan);
|
|
}
|
|
|
|
/* ═══════ Cards ═══════ */
|
|
.card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 20px 24px;
|
|
margin-bottom: 16px;
|
|
transition: all var(--transition);
|
|
}
|
|
.card:hover { box-shadow: 0 4px 16px var(--shadow); }
|
|
.card-title {
|
|
font-size: 14px; font-weight: 700;
|
|
margin-bottom: 8px;
|
|
display: flex; align-items: center; gap: 8px;
|
|
}
|
|
.badge {
|
|
font-size: 10px; font-weight: 700;
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
.badge-green { background: rgba(52,211,153,0.15); color: var(--green); }
|
|
.badge-amber { background: rgba(251,191,36,0.15); color: var(--amber); }
|
|
.badge-blue { background: rgba(108,142,239,0.15); color: var(--accent); }
|
|
.badge-purple { background: rgba(167,139,250,0.15); color: var(--accent2); }
|
|
.badge-red { background: rgba(248,113,113,0.15); color: var(--red); }
|
|
.badge-cyan { background: rgba(34,211,238,0.15); color: var(--cyan); }
|
|
|
|
/* ═══════ Info boxes ═══════ */
|
|
.info-box {
|
|
border-radius: 10px;
|
|
padding: 16px 20px;
|
|
margin: 16px 0;
|
|
font-size: 13.5px;
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: flex-start;
|
|
transition: all var(--transition);
|
|
}
|
|
.info-box .ib-icon { font-size: 18px; flex-shrink: 0; margin-top: 1px; }
|
|
.info-box.insight { background: rgba(108,142,239,0.07); border: 1px solid rgba(108,142,239,0.18); }
|
|
.info-box.key { background: rgba(52,211,153,0.07); border: 1px solid rgba(52,211,153,0.18); }
|
|
.info-box.warn { background: rgba(251,191,36,0.07); border: 1px solid rgba(251,191,36,0.18); }
|
|
|
|
/* ═══════ Flow ═══════ */
|
|
.flow {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0;
|
|
margin: 20px 0;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
.flow-step {
|
|
background: var(--surface2);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 12px 18px;
|
|
text-align: center;
|
|
min-width: 120px;
|
|
transition: all var(--transition);
|
|
}
|
|
.flow-step .num { font-size: 10px; font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 4px; }
|
|
.flow-step .label { font-size: 13px; font-weight: 600; }
|
|
.flow-step .desc { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
|
|
.flow-arrow { color: var(--text-dim); font-size: 18px; margin: 0 6px; flex-shrink: 0; }
|
|
|
|
/* ═══════ Tables ═══════ */
|
|
.comparison {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 16px 0;
|
|
font-size: 13px;
|
|
}
|
|
.comparison th {
|
|
background: var(--surface2);
|
|
padding: 10px 14px;
|
|
text-align: left;
|
|
font-weight: 700;
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
color: var(--text-dim);
|
|
border-bottom: 2px solid var(--border);
|
|
transition: all var(--transition);
|
|
}
|
|
.comparison td {
|
|
padding: 10px 14px;
|
|
border-bottom: 1px solid var(--border);
|
|
vertical-align: top;
|
|
transition: all var(--transition);
|
|
}
|
|
.comparison tr:last-child td { border-bottom: none; }
|
|
.comparison tr:hover td { background: var(--surface2); }
|
|
.comparison .feature { font-weight: 600; white-space: nowrap; }
|
|
.check { color: var(--green); font-weight: 700; }
|
|
.cross { color: var(--red); opacity: 0.5; }
|
|
.partial { color: var(--amber); }
|
|
|
|
/* ═══════ Arch diagram ═══════ */
|
|
.arch-diagram {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 14px;
|
|
padding: 24px 28px;
|
|
margin: 20px 0;
|
|
font-family: 'Cascadia Code', monospace;
|
|
font-size: 11.5px;
|
|
line-height: 1.55;
|
|
overflow-x: auto;
|
|
white-space: pre;
|
|
color: var(--text-dim);
|
|
transition: all var(--transition);
|
|
}
|
|
.arch-diagram .hl { color: var(--accent); font-weight: 600; }
|
|
.arch-diagram .g { color: var(--green); }
|
|
.arch-diagram .a { color: var(--amber); }
|
|
.arch-diagram .r { color: var(--red); }
|
|
.arch-diagram .p { color: var(--accent2); }
|
|
.arch-diagram .c { color: var(--cyan); }
|
|
|
|
/* ═══════ Grids ═══════ */
|
|
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 16px 0; }
|
|
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin: 16px 0; }
|
|
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 16px 0; }
|
|
.stat-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 18px 14px;
|
|
text-align: center;
|
|
transition: all var(--transition);
|
|
}
|
|
.stat-card .number { font-size: 30px; font-weight: 800; color: var(--accent); }
|
|
.stat-card .label { font-size: 11px; color: var(--text-dim); margin-top: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
|
|
/* ═══════ Tags ═══════ */
|
|
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0; }
|
|
.tag {
|
|
display: inline-block;
|
|
padding: 3px 10px;
|
|
border-radius: 6px;
|
|
font-size: 11.5px;
|
|
font-weight: 500;
|
|
background: var(--surface2);
|
|
border: 1px solid var(--border);
|
|
color: var(--text-dim);
|
|
transition: all var(--transition);
|
|
}
|
|
.tag.accent { border-color: rgba(108,142,239,0.3); color: var(--accent); background: rgba(108,142,239,0.06); }
|
|
|
|
/* ═══════ Profile cards ═══════ */
|
|
.profile-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 16px 0; }
|
|
.profile-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 18px 20px;
|
|
transition: all var(--transition);
|
|
}
|
|
.profile-card:hover { box-shadow: 0 4px 16px var(--shadow); }
|
|
.profile-card .name { font-size: 14px; font-weight: 700; margin-bottom: 4px; display: flex; align-items: center; gap: 8px; }
|
|
.profile-card .desc { font-size: 12px; color: var(--text-dim); margin-bottom: 10px; }
|
|
.profile-card .props { font-size: 12px; }
|
|
.profile-card .props dt { color: var(--text-dim); float: left; width: 110px; }
|
|
.profile-card .props dd { margin-bottom: 3px; }
|
|
|
|
/* ═══════ Pipeline ═══════ */
|
|
.pipeline { margin: 20px 0; }
|
|
.pipeline-stage {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 16px;
|
|
padding: 14px 0;
|
|
border-left: 2px solid var(--border);
|
|
margin-left: 14px;
|
|
padding-left: 24px;
|
|
position: relative;
|
|
}
|
|
.pipeline-stage::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: -7px; top: 18px;
|
|
width: 12px; height: 12px;
|
|
border-radius: 50%;
|
|
background: var(--surface);
|
|
border: 2px solid var(--accent);
|
|
transition: all var(--transition);
|
|
}
|
|
.pipeline-stage:last-child { border-left-color: transparent; }
|
|
.pipeline-stage .stage-num { font-size: 10px; font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 1px; min-width: 60px; padding-top: 2px; }
|
|
.pipeline-stage .stage-body { flex: 1; }
|
|
.pipeline-stage .stage-title { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
|
|
.pipeline-stage .stage-desc { font-size: 12.5px; color: var(--text-dim); }
|
|
|
|
/* ═══════ Separator ═══════ */
|
|
.section-divider {
|
|
border: none;
|
|
height: 1px;
|
|
background: linear-gradient(90deg, transparent, var(--border), transparent);
|
|
margin: 56px 0;
|
|
}
|
|
|
|
/* ═══════ Footer ═══════ */
|
|
.footer {
|
|
text-align: center;
|
|
padding: 40px 0;
|
|
border-top: 1px solid var(--border);
|
|
margin-top: 40px;
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
transition: all var(--transition);
|
|
}
|
|
|
|
@media (max-width: 960px) {
|
|
.toc { position: static; width: 100%; height: auto; max-height: none;
|
|
border-right: none; border-bottom: 1px solid var(--border);
|
|
box-shadow: 0 4px 16px var(--shadow); }
|
|
.container { margin-left: 0; }
|
|
.page-wrapper { flex-direction: column; }
|
|
}
|
|
@media (max-width: 720px) {
|
|
.grid-2, .profile-grid { grid-template-columns: 1fr; }
|
|
.grid-3, .grid-4 { grid-template-columns: 1fr 1fr; }
|
|
.flow { flex-direction: column; }
|
|
.flow-arrow { transform: rotate(90deg); }
|
|
.hero h1 { font-size: 28px; }
|
|
.arch-diagram { font-size: 10px; padding: 16px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- ═══════ Theme Toggle ═══════ -->
|
|
<button class="theme-toggle" onclick="toggleTheme()" id="themeBtn" title="테마 전환">🌙</button>
|
|
|
|
<!-- ═══════ Sidebar TOC ═══════ -->
|
|
<div class="page-wrapper">
|
|
<div class="toc" id="toc">
|
|
<h3>Table of Contents</h3>
|
|
<div class="toc-grid">
|
|
<!-- Col 1 -->
|
|
<a href="#s1"><span class="toc-num">1</span> 오픈소스 기반 핵심 엔진 구성</a>
|
|
<a href="#s5"><span class="toc-num">5</span> AX Commander 런처</a>
|
|
<a href="#s1-1" class="toc-sub">1.1 OpenHands 반영 사항</a>
|
|
<a href="#s5-1" class="toc-sub">5.1 글로벌 핫키 + 시스템 통합</a>
|
|
<a href="#s1-2" class="toc-sub">1.2 OpenCode 반영 사항</a>
|
|
<a href="#s5-2" class="toc-sub">5.2 위젯 및 Quick Actions</a>
|
|
|
|
<div class="toc-divider"></div>
|
|
|
|
<a href="#s2"><span class="toc-num">2</span> Agentic Loop 동작 구조</a>
|
|
<a href="#s6"><span class="toc-num">6</span> 모델별 실행 프로파일</a>
|
|
<a href="#s2-1" class="toc-sub">2.1 4단계 실행 사이클</a>
|
|
<a href="#s6-1" class="toc-sub">6.1 4가지 프로파일 상세</a>
|
|
<a href="#s2-2" class="toc-sub">2.2 전체 아키텍처 다이어그램</a>
|
|
<a href="#s6-2" class="toc-sub">6.2 모델별 컨텍스트 한도</a>
|
|
<a href="#s2-3" class="toc-sub">2.3 도구 시스템 (91+ Tools)</a>
|
|
<a href="#s6-3" class="toc-sub">6.3 자동 모델 라우팅</a>
|
|
|
|
<div class="toc-divider"></div>
|
|
|
|
<a href="#s3"><span class="toc-num">3</span> 컨텍스트 관리 + 보안</a>
|
|
<a href="#s7"><span class="toc-num">7</span> 경쟁 서비스 비교</a>
|
|
<a href="#s3-1" class="toc-sub">3.1 5단계 압축 파이프라인</a>
|
|
<a href="#s7-1" class="toc-sub">7.1 기능 비교 매트릭스</a>
|
|
<a href="#s3-2" class="toc-sub">3.2 권한 모드 + 사내/외부 모드</a>
|
|
<a href="#s7-2" class="toc-sub">7.2 핵심 차별화 요약</a>
|
|
|
|
<div class="toc-divider"></div>
|
|
|
|
<a href="#s4"><span class="toc-num">4</span> 코워크 / 코드 탭 특장점</a>
|
|
<a href="#s8"><span class="toc-num">8</span> 기술 스택 요약</a>
|
|
<a href="#s4-1" class="toc-sub">4.1 Cowork 탭 (문서 생성)</a>
|
|
<a href="#s4-2" class="toc-sub">4.2 Code 탭 (코드 개발)</a>
|
|
<a href="#s4-3" class="toc-sub">4.3 탭별 비교표</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
|
|
<!-- ═══════ Hero ═══════ -->
|
|
<div class="hero" style="margin: 0 -28px; padding: 60px 28px 40px;">
|
|
<span class="hero-badge">Internal Tech Seminar</span>
|
|
<h1>AX Copilot</h1>
|
|
<p>Agentic AI 데스크톱 코파일럿 — 아키텍처 & 핵심 기술 분석</p>
|
|
<div class="hero-chips">
|
|
<span class="hero-chip"><span class="dot" style="background:var(--accent)"></span>WPF .NET 8</span>
|
|
<span class="hero-chip"><span class="dot" style="background:var(--green)"></span>91+ Tools</span>
|
|
<span class="hero-chip"><span class="dot" style="background:var(--amber)"></span>Multi-Model</span>
|
|
<span class="hero-chip"><span class="dot" style="background:var(--cyan)"></span>Agentic Loop</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════════════════
|
|
SECTION 1
|
|
═══════════════════════════════════════════════════════════════ -->
|
|
<div class="section" id="s1">
|
|
<div class="section-header">
|
|
<span class="section-num">1</span>
|
|
<h2>오픈소스 기반 핵심 엔진 구성</h2>
|
|
</div>
|
|
<p>AX Copilot의 에이전트 엔진은 <strong>OpenHands</strong>(전 OpenDevin)와 <strong>OpenCode</strong> 두 프로젝트의 아키텍처 패턴을 분석하여, WPF 데스크톱 환경에 맞게 재설계한 결과물입니다.</p>
|
|
|
|
<h3 id="s1-1"><span class="icon">◆</span> 1.1 OpenHands 반영 사항</h3>
|
|
|
|
<div class="card">
|
|
<div class="card-title">MemGPT 스타일 다단계 컨텍스트 압축 <span class="badge badge-green">ContextCondenser</span></div>
|
|
<p>OpenHands의 <em>AgentController</em>가 수행하는 메모리 관리 전략을 5단계 파이프라인으로 구현. 단계 1~4는 <em>LLM 호출 없이</em> 수행하여 비용을 절감하고, 5단계에서만 LLM 1회 호출.</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">Plan-and-Solve 분해 전략 <span class="badge badge-blue">TaskDecomposer</span></div>
|
|
<p>OpenHands <em>CodeActAgent</em>의 태스크 분해 패턴 반영. LLM 응답에서 번호 매긴 단계를 추출 (<code>TaskDecomposer.ExtractSteps()</code>), 현재 도구 호출을 계획 단계에 매칭하여 <strong>실시간 진행률을 UI에 표시</strong>합니다.</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">도구 병렬 실행 + 프리페치 <span class="badge badge-amber">AgentLoopParallelExecution</span></div>
|
|
<p>읽기 전용 도구를 자동 분류하여 <strong>최대 12개까지 병렬 실행</strong>. 스트리밍 응답 도중에도 읽기 도구를 선행 실행(<em>Prefetch</em>)하여 대기 시간 최소화.</p>
|
|
</div>
|
|
|
|
<h3 id="s1-2"><span class="icon">◆</span> 1.2 OpenCode 반영 사항</h3>
|
|
|
|
<div class="card">
|
|
<div class="card-title">실행 엔진 3단계 분리 <span class="badge badge-purple">AxAgentExecutionEngine</span></div>
|
|
<p>OpenCode의 <em>Prepare → Execute → Commit</em> 분리 패턴을 답습합니다.</p>
|
|
<ul>
|
|
<li><code>ResolveExecutionMode()</code> — 탭/설정에 따라 에이전트 루프 vs 단순 LLM 호출 결정</li>
|
|
<li><code>PrepareExecution()</code> — 시스템 프롬프트 스택 + 메시지 조립</li>
|
|
<li><code>ExecutePreparedAsync()</code> — agentLoopRunner 또는 llmRunner로 분기</li>
|
|
<li><code>CommitAssistantMessage()</code> — 결과 메시지 영속화</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">스트리밍 도구 실행 + 자동 복구 <span class="badge badge-cyan">StreamingToolExecutionCoordinator</span></div>
|
|
<p>SSE 스트리밍 중 도구 블록 도착 즉시 실행. 컨텍스트 오버플로우 자동 감지 → 강제 압축, 일시적 네트워크 오류 → 지수 백오프 재시도 포함.</p>
|
|
</div>
|
|
|
|
<div class="info-box insight">
|
|
<span class="ib-icon">💡</span>
|
|
<div>
|
|
<strong>설계 철학</strong><br>
|
|
OpenHands의 <em>자율적 다단계 태스크 수행</em> + OpenCode의 <em>경량 실행 분리</em>를 결합하되,
|
|
WPF 데스크톱 특성(UI 스레드 분리, 로컬 파일 시스템, 시스템 트레이 통합)에 맞춰 재설계.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════════════════
|
|
SECTION 2
|
|
═══════════════════════════════════════════════════════════════ -->
|
|
<div class="section" id="s2">
|
|
<div class="section-header">
|
|
<span class="section-num">2</span>
|
|
<h2>Agentic Loop 동작 구조</h2>
|
|
</div>
|
|
|
|
<h3 id="s2-1"><span class="icon">◆</span> 2.1 4단계 실행 사이클</h3>
|
|
<p>에이전트 루프는 <strong>Plan → Execute → Observe → Evaluate</strong> 사이클을 작업 완료까지 자율 반복합니다.</p>
|
|
|
|
<div class="flow">
|
|
<div class="flow-step">
|
|
<div class="num">Phase 1</div>
|
|
<div class="label">Planning</div>
|
|
<div class="desc">LLM이 도구 호출 계획 생성</div>
|
|
</div>
|
|
<span class="flow-arrow">➔</span>
|
|
<div class="flow-step">
|
|
<div class="num">Phase 2</div>
|
|
<div class="label">Execute</div>
|
|
<div class="desc">읽기 병렬 + 쓰기 순차 실행</div>
|
|
</div>
|
|
<span class="flow-arrow">➔</span>
|
|
<div class="flow-step">
|
|
<div class="num">Phase 3</div>
|
|
<div class="label">Observe</div>
|
|
<div class="desc">도구 결과 수집 + 이벤트 발행</div>
|
|
</div>
|
|
<span class="flow-arrow">➔</span>
|
|
<div class="flow-step">
|
|
<div class="num">Phase 4</div>
|
|
<div class="label">Evaluate</div>
|
|
<div class="desc">LLM이 결과 판단 + 다음 행동</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h4>AgentLoopService 핵심 기능</h4>
|
|
<ul>
|
|
<li><strong>실행 중 사용자 개입</strong> — <code>ConcurrentQueue</code>로 실행 중에도 사용자 메시지 수신 (Claude Code 스타일 <em>mid-execution steering</em>)</li>
|
|
<li><strong>일시정지/재개</strong> — <code>SemaphoreSlim</code> 기반 Pause/Resume 제어</li>
|
|
<li><strong>반복 제한</strong> — <code>MaxAgentIterations</code> (기본 25, 최대 200) 초과 시 자동 중단</li>
|
|
<li><strong>이벤트 스트림</strong> — ToolCall, ToolResult, Thinking, Planning, Complete, Error 이벤트를 UI에 실시간 전달</li>
|
|
</ul>
|
|
|
|
<h3 id="s2-2"><span class="icon">◆</span> 2.2 전체 아키텍처 다이어그램</h3>
|
|
|
|
<div class="arch-diagram"><span class="hl">+------------------------------------------------------------------+</span>
|
|
<span class="hl">| AX Copilot UI Layer |</span>
|
|
<span class="hl">|</span> <span class="c">ChatWindow</span> -- <span class="c">TranscriptHost</span> -- <span class="c">AgentEventProcessor</span> <span class="hl">|</span>
|
|
<span class="hl">|</span> | | | <span class="hl">|</span>
|
|
<span class="hl">|</span> v v v <span class="hl">|</span>
|
|
<span class="hl">|</span> <span class="g">V1 Render</span> <span class="g">V2 Render</span> <span class="a">AgentEvent</span> stream <span class="hl">|</span>
|
|
<span class="hl">+----------+----------------------------+---------------------------+</span>
|
|
| |
|
|
<span class="p">+----------v----------------------------v---------------------------+</span>
|
|
<span class="p">| AxAgentExecutionEngine |</span>
|
|
<span class="p">|</span> <span class="a">ResolveMode</span> -> <span class="a">PrepareExecution</span> -> <span class="a">ExecutePrepared</span> -> <span class="a">Commit</span> <span class="p">|</span>
|
|
<span class="p">+----------+----------------------------+---------------------------+</span>
|
|
| |
|
|
<span class="g">+----------v-----------+ +------------v----------------------------+</span>
|
|
<span class="g">| AgentLoopService | | LlmService (Streaming) |</span>
|
|
<span class="g">|</span> <span class="g">| |</span> Ollama / vLLM / Gemini / Claude <span class="g">|</span>
|
|
<span class="g">|</span> Plan -> Execute -> <span class="g">| |</span> Override Stack (Push/Pop) <span class="g">|</span>
|
|
<span class="g">|</span> Observe -> Evaluate <span class="g">| |</span> Token Tracking per call <span class="g">|</span>
|
|
<span class="g">+-----------+-----------+ +---------------------------------------------+</span>
|
|
|
|
|
<span class="c">+-----------v---------------------------------------------------------+</span>
|
|
<span class="c">| ToolRegistry (91+ Tools) |</span>
|
|
<span class="c">|</span> <span class="g">File Ops</span> | <span class="a">Document</span> | <span class="p">Code/Git</span> | <span class="r">System</span> | <span class="c">MCP Dynamic</span> <span class="c">|</span>
|
|
<span class="c">|</span> Parallel | Plan+ | Worktree | Notify | JSON-RPC stdio <span class="c">|</span>
|
|
<span class="c">|</span> Batch | Assemble | Branch | Env | mcp_{srv}_{fn} <span class="c">|</span>
|
|
<span class="c">+-----------+---------------------------------------------------------+</span>
|
|
|
|
|
<span class="a">+-----------v---------------------------------------------------------+</span>
|
|
<span class="a">| Permission / Security Layer |</span>
|
|
<span class="a">|</span> <span class="g">OperationModePolicy</span> (Internal/External) <span class="a">|</span>
|
|
<span class="a">|</span> <span class="p">PermissionModeCatalog</span> (Default/AcceptEdits/Plan/Bypass/Deny) <span class="a">|</span>
|
|
<span class="a">|</span> Tool-level overrides with wildcard/pattern matching <span class="a">|</span>
|
|
<span class="a">+---------------------------------------------------------------------+</span></div>
|
|
|
|
<h3 id="s2-3"><span class="icon">◆</span> 2.3 도구 시스템 (91+ Tools)</h3>
|
|
|
|
<div class="grid-3">
|
|
<div class="stat-card"><div class="number">91+</div><div class="label">내장 도구</div></div>
|
|
<div class="stat-card"><div class="number">3</div><div class="label">노출 버킷</div></div>
|
|
<div class="stat-card"><div class="number">12</div><div class="label">최대 병렬 실행</div></div>
|
|
</div>
|
|
|
|
<h4>도구 카테고리</h4>
|
|
<table class="comparison">
|
|
<thead><tr><th>카테고리</th><th>주요 도구</th><th>설명</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="feature">파일 조작</td><td><code>file_read</code> <code>file_write</code> <code>file_edit</code> <code>glob</code> <code>grep</code></td><td>파일 읽기/쓰기/검색 + folder_map</td></tr>
|
|
<tr><td class="feature">문서 생성</td><td><code>html_create</code> <code>docx_create</code> <code>xlsx_create</code> <code>pptx_create</code></td><td>8가지 Mood 템플릿 + Multi-pass 조립</td></tr>
|
|
<tr><td class="feature">코드/Git</td><td><code>git_tool</code> <code>code_search</code> <code>build_run</code> <code>test_loop</code></td><td>브랜치 격리, 코드 리뷰, CI 연동</td></tr>
|
|
<tr><td class="feature">시스템</td><td><code>process</code> <code>env_tool</code> <code>clipboard</code> <code>notify</code></td><td>프로세스 실행, 환경 변수, 알림</td></tr>
|
|
<tr><td class="feature">데이터</td><td><code>json_tool</code> <code>sql_tool</code> <code>math_tool</code> <code>data_pivot</code></td><td>구조화 데이터 처리 + 피벗</td></tr>
|
|
<tr><td class="feature">워크플로우</td><td><code>spawn_agent</code> <code>enter_worktree</code> <code>checkpoint</code> <code>playbook</code></td><td>에이전트 분기, 체크포인트, 자동화</td></tr>
|
|
<tr><td class="feature">MCP 동적</td><td><code>mcp_{server}_{fn}</code></td><td>JSON-RPC 2.0 over stdio 외부 도구 서버</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<hr class="section-divider">
|
|
|
|
<!-- ═══════════════════════════════════════════════════════════════
|
|
SECTION 3
|
|
═══════════════════════════════════════════════════════════════ -->
|
|
<div class="section" id="s3">
|
|
<div class="section-header">
|
|
<span class="section-num">3</span>
|
|
<h2>컨텍스트 관리 + 보안</h2>
|
|
</div>
|
|
|
|
<h3 id="s3-1"><span class="icon">◆</span> 3.1 5단계 압축 파이프라인</h3>
|
|
<p>모델 입력 한도의 <strong>80%</strong>에 도달하면 자동으로 트리거. 단계 1~4는 LLM 호출 없이 수행하여 비용 절감.</p>
|
|
|
|
<div class="pipeline">
|
|
<div class="pipeline-stage">
|
|
<span class="stage-num">Stage 1</span>
|
|
<div class="stage-body">
|
|
<div class="stage-title">Tool Result 절삭</div>
|
|
<div class="stage-desc">LLM 호출 없음. 도구 결과를 1,500자 제한 (head 220자 + tail 140자 보존). 가장 빈번하게 적용.</div>
|
|
</div>
|
|
</div>
|
|
<div class="pipeline-stage">
|
|
<span class="stage-num">Stage 2</span>
|
|
<div class="stage-body">
|
|
<div class="stage-title">Session Memory 통합</div>
|
|
<div class="stage-desc">LLM 호출 없음. 이전 압축 경계의 요약들을 단일 세션 메모리 메시지로 병합.</div>
|
|
</div>
|
|
</div>
|
|
<div class="pipeline-stage">
|
|
<span class="stage-num">Stage 3</span>
|
|
<div class="stage-body">
|
|
<div class="stage-title">Microcompact</div>
|
|
<div class="stage-desc">LLM 호출 없음. 오래된 실행 로그/도구 묶음을 한 줄 요약으로 교체. 단일 결과 480자 제한.</div>
|
|
</div>
|
|
</div>
|
|
<div class="pipeline-stage">
|
|
<span class="stage-num">Stage 4</span>
|
|
<div class="stage-body">
|
|
<div class="stage-title">Collapse / Snip</div>
|
|
<div class="stage-desc">LLM 호출 없음. 남은 긴 로그를 head+tail 스니펫으로 절삭. 증거 보존 + 크기 축소.</div>
|
|
</div>
|
|
</div>
|
|
<div class="pipeline-stage">
|
|
<span class="stage-num">Stage 5</span>
|
|
<div class="stage-body">
|
|
<div class="stage-title">LLM 요약 (Historical Summarization)</div>
|
|
<div class="stage-desc"><strong>1회 LLM 호출</strong>. 가장 오래된 대화 구간을 LLM으로 요약. 최근 6개 메시지는 원본 유지.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 id="s3-2"><span class="icon">◆</span> 3.2 권한 모드 + 사내/외부 모드</h3>
|
|
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<div class="card-title">5가지 권한 모드 <span class="badge badge-purple">PermissionModeCatalog</span></div>
|
|
<ul>
|
|
<li><strong>Default</strong> — 모든 도구 실행 전 사용자 승인 요청</li>
|
|
<li><strong>AcceptEdits</strong> — 쓰기 도구 자동 승인, 위험 도구만 확인</li>
|
|
<li><strong>Plan</strong> — 계획만 표시, 쓰기 도구 차단</li>
|
|
<li><strong>Bypass</strong> — 모든 도구 자동 실행 (완전 자동)</li>
|
|
<li><strong>Deny</strong> — 읽기 전용, 쓰기 완전 차단</li>
|
|
</ul>
|
|
<p>도구별 패턴 매칭 오버라이드: <code>tool_name@pattern</code> 구문 지원</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">운영 모드 <span class="badge badge-red">OperationModePolicy</span></div>
|
|
<h4>사내 모드 (Internal)</h4>
|
|
<ul>
|
|
<li>외부 LLM (Gemini, Claude) 완전 차단</li>
|
|
<li><code>http_tool</code> 차단</li>
|
|
<li>워크스페이스 외부 경로 접근 시 강제 승인</li>
|
|
<li>Ollama / vLLM 로컬 백엔드만 허용</li>
|
|
<li>API 키 DPAPI+AES 암호화 저장</li>
|
|
</ul>
|
|
<h4>외부 모드 (External)</h4>
|
|
<ul><li>모든 서비스/도구 사용 가능</li></ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="section-divider">
|
|
|
|
<!-- ═══════════════════════════════════════════════════════════════
|
|
SECTION 4
|
|
═══════════════════════════════════════════════════════════════ -->
|
|
<div class="section" id="s4">
|
|
<div class="section-header">
|
|
<span class="section-num">4</span>
|
|
<h2>코워크 / 코드 탭 특장점</h2>
|
|
</div>
|
|
<p>Chat / Cowork / Code 3개 탭으로 분리된 워크스페이스. 각 탭은 고유한 시스템 프롬프트, 도구 집합, 권한 정책을 가집니다.</p>
|
|
|
|
<h3 id="s4-1"><span class="icon">◆</span> 4.1 Cowork 탭 — 문서 생성 특화</h3>
|
|
<div class="card" style="border-left: 3px solid var(--green);">
|
|
<div class="card-title" style="color: var(--green);">핵심 원칙: "Tools First When Needed" <span class="badge badge-green">Cowork</span></div>
|
|
<p>응답 시 텍스트보다 도구 호출을 우선. 독립적인 문서 읽기/생성을 병렬 배치 처리합니다.</p>
|
|
<h4>문서 생성 워크플로우</h4>
|
|
<ul>
|
|
<li><strong>단일 섹션</strong> → <code>html_create</code>, <code>docx_create</code> 직접 생성</li>
|
|
<li><strong>3페이지 이상</strong> → <code>document_plan</code> → 섹션별 작성 → <code>document_assemble</code></li>
|
|
<li><strong>Multi-pass 모드</strong> — 아웃라인 → LLM 섹션별 집필 → 조립 (최고 품질)</li>
|
|
</ul>
|
|
<h4>지원 출력 포맷 + HTML Mood 템플릿</h4>
|
|
<div class="tags">
|
|
<span class="tag accent">HTML</span> <span class="tag accent">DOCX</span> <span class="tag accent">XLSX</span>
|
|
<span class="tag accent">PPTX</span> <span class="tag accent">CSV</span> <span class="tag accent">Markdown</span>
|
|
</div>
|
|
<div class="tags" style="margin-top:6px">
|
|
<span class="tag">modern</span> <span class="tag">professional</span> <span class="tag">creative</span> <span class="tag">elegant</span>
|
|
<span class="tag">dark</span> <span class="tag">corporate</span> <span class="tag">magazine</span> <span class="tag">dashboard</span>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 id="s4-2"><span class="icon">◆</span> 4.2 Code 탭 — 코드 개발 특화</h3>
|
|
<div class="card" style="border-left: 3px solid var(--accent2);">
|
|
<div class="card-title" style="color: var(--accent2);">핵심 원칙: 코드 검증 게이트 + 브랜치 격리 <span class="badge badge-purple">Code</span></div>
|
|
<h4>코드 전용 도구</h4>
|
|
<ul>
|
|
<li><code>git_tool</code> — Git 전체 워크플로우</li>
|
|
<li><code>file_edit</code> — 정밀 라인 기반 코드 편집</li>
|
|
<li><code>build_run</code> / <code>test_loop</code> — 빌드 + 테스트 반복</li>
|
|
<li><code>enter_worktree</code> / <code>exit_worktree</code> — Git 워크트리 격리</li>
|
|
<li><code>code_review</code> — Git diff 기반 자동 코드 리뷰</li>
|
|
</ul>
|
|
<h4>슬래시 커맨드 (Code 전용)</h4>
|
|
<div class="tags">
|
|
<span class="tag accent">/review</span> <span class="tag accent">/commit</span> <span class="tag accent">/test</span>
|
|
<span class="tag accent">/build</span> <span class="tag accent">/diff</span> <span class="tag accent">/branch</span>
|
|
<span class="tag accent">/structure</span> <span class="tag accent">/doctor</span>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 id="s4-3"><span class="icon">◆</span> 4.3 탭별 비교표</h3>
|
|
<table class="comparison">
|
|
<thead><tr><th>특성</th><th>Chat</th><th>Cowork</th><th>Code</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="feature">에이전트 루프</td><td><span class="cross">✕</span> 단순 LLM</td><td><span class="check">✓</span> 자율 루프</td><td><span class="check">✓</span> 자율 루프</td></tr>
|
|
<tr><td class="feature">도구 실행</td><td><span class="cross">✕</span></td><td><span class="check">✓</span> 문서 중심</td><td><span class="check">✓</span> 코드 중심</td></tr>
|
|
<tr><td class="feature">병렬 실행</td><td>N/A</td><td><span class="check">✓</span></td><td><span class="check">✓</span></td></tr>
|
|
<tr><td class="feature">컨텍스트 압축</td><td><span class="cross">✕</span></td><td><span class="check">✓</span> 5단계</td><td><span class="check">✓</span> 5단계</td></tr>
|
|
<tr><td class="feature">시스템 프롬프트</td><td>기본 대화</td><td>문서 생성 특화</td><td>코드 개발 특화</td></tr>
|
|
<tr><td class="feature">워크스페이스</td><td><span class="cross">✕</span></td><td><span class="check">✓</span> 폴더 기반</td><td><span class="check">✓</span> 폴더 기반</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<hr class="section-divider">
|
|
|
|
<!-- ═══════════════════════════════════════════════════════════════
|
|
SECTION 5
|
|
═══════════════════════════════════════════════════════════════ -->
|
|
<div class="section" id="s5">
|
|
<div class="section-header">
|
|
<span class="section-num">5</span>
|
|
<h2>AX Commander 런처</h2>
|
|
</div>
|
|
|
|
<h3 id="s5-1"><span class="icon">◆</span> 5.1 글로벌 핫키 + 시스템 통합</h3>
|
|
<div class="card" style="border-left: 3px solid var(--amber);">
|
|
<div class="card-title" style="color: var(--amber);">Alt+Space 글로벌 런처 <span class="badge badge-amber">LauncherWindow</span></div>
|
|
<p>macOS Spotlight / Raycast 스타일이지만, AI 에이전트와 완전 통합된 시스템 전역 런처.</p>
|
|
<div class="grid-2" style="margin-top:12px;">
|
|
<div>
|
|
<h4>런처 핵심 기능</h4>
|
|
<ul>
|
|
<li>파일/폴더/앱 검색 + 실행</li>
|
|
<li>클립보드 히스토리 (이미지 포함)</li>
|
|
<li>웹 검색 (Google, Naver, DuckDuckGo, Yahoo, Wikipedia)</li>
|
|
<li>커스텀 핫키 바인딩</li>
|
|
<li>번호 배지 Ctrl+1~9 단축키</li>
|
|
<li>즐겨찾기 / 최근 항목</li>
|
|
<li>파일 액션 모드 (복사/이동/삭제)</li>
|
|
<li>스니펫 자동 확장</li>
|
|
<li>대형 텍스트 표시 (Shift+Enter)</li>
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h4>시스템 통합</h4>
|
|
<ul>
|
|
<li>WH_KEYBOARD_LL 로우레벨 키보드 후킹</li>
|
|
<li>시스템 트레이 아이콘 + 커스텀 메뉴</li>
|
|
<li>Windows 시작 시 자동 실행 등록</li>
|
|
<li>고해상도 DPI 스케일링 대응</li>
|
|
<li>포커스 해제 시 자동 닫기</li>
|
|
<li>위치 설정: center-top / center / bottom</li>
|
|
<li>투명도 조절 (기본 96%)</li>
|
|
</ul>
|
|
<h4>테마 시스템</h4>
|
|
<div class="tags">
|
|
<span class="tag">System</span> <span class="tag">Dark</span> <span class="tag">Light</span> <span class="tag">OLED</span>
|
|
<span class="tag">Nord</span> <span class="tag">Monokai</span> <span class="tag">Catppuccin</span> <span class="tag">Sepia</span>
|
|
<span class="tag accent">+ Custom</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 id="s5-2"><span class="icon">◆</span> 5.2 위젯 및 Quick Actions</h3>
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<div class="card-title">위젯 시스템</div>
|
|
<p>런처 하단에 선택적으로 표시되는 미니 위젯:</p>
|
|
<div class="tags">
|
|
<span class="tag accent">성능 모니터</span> <span class="tag accent">뽀모도로</span> <span class="tag accent">퀵 노트</span>
|
|
<span class="tag accent">날씨</span> <span class="tag accent">캘린더</span> <span class="tag accent">배터리</span>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">Quick Actions</div>
|
|
<p>입력창이 비어있을 때 최근 사용 파일/폴더 <strong>상위 8개</strong>를 칩으로 표시. 색상 구분:</p>
|
|
<ul>
|
|
<li style="color:var(--green)">■ 폴더 (초록)</li>
|
|
<li style="color:var(--accent)">■ 실행파일/바로가기 (파랑)</li>
|
|
<li style="color:var(--accent2)">■ 기타 파일 (보라)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">슬래시 커맨드 <span class="badge badge-blue">70+ Commands</span></div>
|
|
<div class="tags">
|
|
<span class="tag accent">/clear</span> <span class="tag accent">/model</span> <span class="tag accent">/permissions</span>
|
|
<span class="tag accent">/theme</span> <span class="tag accent">/summary</span> <span class="tag accent">/translate</span>
|
|
<span class="tag accent">/explain</span> <span class="tag accent">/fix</span> <span class="tag accent">/review</span>
|
|
<span class="tag accent">/commit</span> <span class="tag accent">/test</span> <span class="tag accent">/build</span>
|
|
<span class="tag accent">/structure</span> <span class="tag accent">/doctor</span> <span class="tag accent">/tasks</span>
|
|
<span class="tag accent">/init</span> <span class="tag accent">/search</span> <span class="tag accent">/diff</span>
|
|
<span class="tag">+50 more</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="section-divider">
|
|
|
|
<!-- ═══════════════════════════════════════════════════════════════
|
|
SECTION 6
|
|
═══════════════════════════════════════════════════════════════ -->
|
|
<div class="section" id="s6">
|
|
<div class="section-header">
|
|
<span class="section-num">6</span>
|
|
<h2>모델별 실행 프로파일</h2>
|
|
</div>
|
|
<p>LLM 모델마다 다른 특성에 맞춰 <strong>4가지 실행 프로파일</strong>을 제공. 프로파일에 따라 온도 제한, 재시도 횟수, 병렬 규모, 검증 게이트가 자동 조정됩니다.</p>
|
|
|
|
<h3 id="s6-1"><span class="icon">◆</span> 6.1 4가지 프로파일 상세</h3>
|
|
|
|
<div class="profile-grid">
|
|
<div class="profile-card">
|
|
<div class="name"><span class="badge badge-amber">Strict</span> tool_call_strict</div>
|
|
<div class="desc">IBM Granite, Qwen 등 시스템 프롬프트 무시 경향 모델용</div>
|
|
<dl class="props">
|
|
<dt>온도 제한</dt><dd><strong>0.2</strong> (매우 낮음)</dd>
|
|
<dt>재시도</dt><dd>4회 (비도구 응답 시)</dd>
|
|
<dt>병렬 읽기</dt><dd>최대 8개</dd>
|
|
<dt>특수 기능</dt><dd>사용자 메시지에 도구 호출 리마인더 주입</dd>
|
|
</dl>
|
|
</div>
|
|
<div class="profile-card">
|
|
<div class="name"><span class="badge badge-blue">Balanced</span> reasoning_first</div>
|
|
<div class="desc">Claude, GPT-4 등 고성능 추론 모델용</div>
|
|
<dl class="props">
|
|
<dt>온도 제한</dt><dd><strong>0.45</strong> (균형)</dd>
|
|
<dt>재시도</dt><dd>2회</dd>
|
|
<dt>병렬 읽기</dt><dd>최대 6개</dd>
|
|
<dt>코드 게이트</dt><dd>활성 (품질 검증)</dd>
|
|
</dl>
|
|
</div>
|
|
<div class="profile-card">
|
|
<div class="name"><span class="badge badge-green">Fast</span> fast_readonly</div>
|
|
<div class="desc">빠른 조회/분석 작업 특화</div>
|
|
<dl class="props">
|
|
<dt>온도 제한</dt><dd><strong>0.25</strong> (예측 가능)</dd>
|
|
<dt>재시도</dt><dd>1회 (최소)</dd>
|
|
<dt>병렬 읽기</dt><dd>최대 10개 (최대 공격적)</dd>
|
|
<dt>검증 게이트</dt><dd>비활성 (속도 최우선)</dd>
|
|
</dl>
|
|
</div>
|
|
<div class="profile-card">
|
|
<div class="name"><span class="badge badge-purple">Document</span> document_heavy</div>
|
|
<div class="desc">보고서/프레젠테이션 생성 집중 모드</div>
|
|
<dl class="props">
|
|
<dt>온도 제한</dt><dd><strong>0.35</strong></dd>
|
|
<dt>계획 재시도</dt><dd>0 (계획 생략, 생성 집중)</dd>
|
|
<dt>병렬 읽기</dt><dd>최대 6개</dd>
|
|
<dt>메모리 압박</dt><dd>조기 해소 활성</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 id="s6-2"><span class="icon">◆</span> 6.2 모델별 컨텍스트 한도</h3>
|
|
<table class="comparison">
|
|
<thead><tr><th>모델 계열</th><th>모델 한도</th><th>적용 한도 (90%)</th><th>압축 트리거</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="feature">Claude (Opus/Sonnet/Haiku)</td><td>200K</td><td><strong>180K</strong></td><td>144K (80%)</td></tr>
|
|
<tr><td class="feature">Gemini 2.5 Pro</td><td>1M</td><td><strong>900K</strong></td><td>720K</td></tr>
|
|
<tr><td class="feature">GPT-4 / GPT-4o</td><td>128K</td><td><strong>120K</strong></td><td>96K</td></tr>
|
|
<tr><td class="feature">DeepSeek</td><td>128K</td><td><strong>128K</strong></td><td>102K</td></tr>
|
|
<tr><td class="feature">Qwen / LLaMA</td><td>32K</td><td><strong>32K</strong></td><td>25.6K</td></tr>
|
|
<tr><td class="feature">vLLM / Ollama (미확인)</td><td>-</td><td><strong>32K</strong> (보수적)</td><td>25.6K</td></tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3 id="s6-3"><span class="icon">◆</span> 6.3 자동 모델 라우팅</h3>
|
|
<div class="info-box warn">
|
|
<span class="ib-icon">⚠</span>
|
|
<div>
|
|
<strong>현재 비활성 (EnableAutoRouter = false)</strong><br>
|
|
사용자 메시지 의도(coding, translation, analysis, creative, document, math)를 분석하여 최적 모델로 자동 전환하는 기능. 신뢰도 임계값 초과 시에만 전환하며, 기존 모델보다 0.1점 이상 높을 때만 적용. 향후 사내 서버 확정 후 활성화 예정.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="section-divider">
|
|
|
|
<!-- ═══════════════════════════════════════════════════════════════
|
|
SECTION 7
|
|
═══════════════════════════════════════════════════════════════ -->
|
|
<div class="section" id="s7">
|
|
<div class="section-header">
|
|
<span class="section-num">7</span>
|
|
<h2>경쟁 서비스 비교</h2>
|
|
</div>
|
|
|
|
<h3 id="s7-1"><span class="icon">◆</span> 7.1 기능 비교 매트릭스</h3>
|
|
|
|
<div style="overflow-x:auto;">
|
|
<table class="comparison">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:140px">기능</th>
|
|
<th>AX Copilot</th>
|
|
<th>Claude Code</th>
|
|
<th>Claude Desktop</th>
|
|
<th>OpenAI Codex</th>
|
|
<th>Cursor / Windsurf</th>
|
|
<th>ChatGPT Desktop</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="feature">플랫폼</td>
|
|
<td>WPF 독립 앱</td>
|
|
<td>터미널 CLI</td>
|
|
<td>Electron 데스크톱</td>
|
|
<td>클라우드 샌드박스</td>
|
|
<td>VS Code Fork</td>
|
|
<td>Electron 데스크톱</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">에이전트 루프</td>
|
|
<td><span class="check">✓</span> 91+ 도구</td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="partial">●</span> MCP 기반</td>
|
|
<td><span class="check">✓</span> 샌드박스</td>
|
|
<td><span class="partial">●</span> 편집 특화</td>
|
|
<td><span class="cross">✕</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">멀티 모델</td>
|
|
<td><span class="check">✓</span> 4종 백엔드</td>
|
|
<td><span class="cross">✕</span> Claude 전용</td>
|
|
<td><span class="cross">✕</span> Claude 전용</td>
|
|
<td><span class="cross">✕</span> GPT 전용</td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="cross">✕</span> GPT 전용</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">사내 LLM</td>
|
|
<td><span class="check">✓</span> Ollama/vLLM</td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">문서 생성</td>
|
|
<td><span class="check">✓</span> 7종 포맷</td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">런처 (핫키)</td>
|
|
<td><span class="check">✓</span> Alt+Space</td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="partial">●</span> 기본</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">권한 세분화</td>
|
|
<td><span class="check">✓</span> 5모드+패턴</td>
|
|
<td><span class="check">✓</span> 3단계</td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="partial">●</span> 샌드박스 격리</td>
|
|
<td><span class="partial">●</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">컨텍스트 관리</td>
|
|
<td><span class="check">✓</span> 5단계 압축</td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="partial">●</span> 기본</td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="partial">●</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">MCP 서버</td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="partial">●</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">모델별 프로파일</td>
|
|
<td><span class="check">✓</span> 4종</td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">사내/외부 모드</td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">오프라인 동작</td>
|
|
<td><span class="check">✓</span> 로컬 모델</td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
<td><span class="cross">✕</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">로컬 파일 접근</td>
|
|
<td><span class="check">✓</span> 직접 접근</td>
|
|
<td><span class="check">✓</span> 직접 접근</td>
|
|
<td><span class="partial">●</span> MCP 경유</td>
|
|
<td><span class="cross">✕</span> 클라우드만</td>
|
|
<td><span class="check">✓</span> 프로젝트 내</td>
|
|
<td><span class="cross">✕</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="feature">비용 구조</td>
|
|
<td>API 종량제</td>
|
|
<td>API 종량제</td>
|
|
<td>구독 (Pro/Max)</td>
|
|
<td>구독 (Pro)</td>
|
|
<td>구독 ($20/mo~)</td>
|
|
<td>구독 (Plus)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<h3 id="s7-2"><span class="icon">◆</span> 7.2 핵심 차별화 요약</h3>
|
|
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<div class="card-title">① 완전 독립 데스크톱 에이전트</div>
|
|
<p>IDE 종속 없는 <strong>독립 실행형 에이전트</strong>. 코드 편집 + 문서 생성 + 시스템 관리 + 워크플로우 자동화를 단일 앱에서 수행. 런처로 어디서든 즉시 접근.</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">② 사내 인프라 네이티브</div>
|
|
<p><strong>Ollama / vLLM</strong> 직접 연결. DPAPI+AES 암호화 API 키. 사내 모드에서 외부 완전 차단. <strong>오프라인에서도 에이전트 루프 동작</strong>.</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">③ 모델 불문 최적 실행</div>
|
|
<p>4가지 프로파일이 <strong>모델 특성에 맞춰 자동 조정</strong>. "chatty" 모델에는 도구 호출 강제, 고성능 모델에는 추론 우선. 온도/재시도/병렬 규모 모두 자동 최적화.</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title">④ 문서+코드 통합 워크스페이스</div>
|
|
<p>단일 앱에서 <strong>보고서/PPTX 자동 생성</strong>과 <strong>코드 개발/리뷰/테스트</strong>를 동시 수행. 같은 대화 컨텍스트에서 문서와 코드를 넘나드는 작업.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="section-divider">
|
|
|
|
<!-- ═══════════════════════════════════════════════════════════════
|
|
SECTION 8
|
|
═══════════════════════════════════════════════════════════════ -->
|
|
<div class="section" id="s8">
|
|
<div class="section-header">
|
|
<span class="section-num">8</span>
|
|
<h2>기술 스택 요약</h2>
|
|
</div>
|
|
|
|
<div class="grid-4">
|
|
<div class="stat-card"><div class="number" style="font-size:22px">.NET 8</div><div class="label">런타임</div></div>
|
|
<div class="stat-card"><div class="number" style="font-size:22px">WPF</div><div class="label">UI 프레임워크</div></div>
|
|
<div class="stat-card"><div class="number" style="font-size:22px">C#</div><div class="label">언어</div></div>
|
|
<div class="stat-card"><div class="number" style="font-size:22px">MCP</div><div class="label">확장 프로토콜</div></div>
|
|
</div>
|
|
|
|
<table class="comparison">
|
|
<thead><tr><th>레이어</th><th>기술 / 패턴</th><th>핵심 파일</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="feature">UI 렌더링</td><td>VirtualizingStackPanel + TranscriptVisualItem 가상화, V1/V2 분기 렌더</td><td>ChatWindow.*.cs (40+ partial files)</td></tr>
|
|
<tr><td class="feature">실행 엔진</td><td>Prepare → Execute → Commit 3단계 분리</td><td>AxAgentExecutionEngine.cs</td></tr>
|
|
<tr><td class="feature">에이전트 루프</td><td>Plan → Execute → Observe → Evaluate + Parallel Batch</td><td>AgentLoopService.cs</td></tr>
|
|
<tr><td class="feature">LLM 통신</td><td>SSE Streaming + Override Stack + Token Tracking</td><td>LlmService.cs</td></tr>
|
|
<tr><td class="feature">도구 시스템</td><td>IAgentTool + ToolRegistry + MCP 동적 래핑</td><td>ToolRegistry.cs, IAgentTool.cs</td></tr>
|
|
<tr><td class="feature">컨텍스트</td><td>5단계 MemGPT 스타일 압축 파이프라인</td><td>ContextCondenser.cs</td></tr>
|
|
<tr><td class="feature">보안</td><td>5모드 권한 + Internal/External 운영 + DPAPI 암호화</td><td>PermissionModeCatalog.cs</td></tr>
|
|
<tr><td class="feature">런처</td><td>WH_KEYBOARD_LL 글로벌 핫키 + Spotlight 스타일 UI</td><td>LauncherWindow.xaml.cs</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- ═══════ Footer ═══════ -->
|
|
<div class="footer">
|
|
<p>AX Copilot Internal Tech Seminar · Confidential · 2026</p>
|
|
</div>
|
|
|
|
</div><!-- container -->
|
|
</div><!-- page-wrapper -->
|
|
|
|
<!-- ═══════════════════════════════════════════════════════════════
|
|
SCRIPTS
|
|
═══════════════════════════════════════════════════════════════ -->
|
|
<script>
|
|
// ── Theme Toggle ──
|
|
function toggleTheme() {
|
|
const html = document.documentElement;
|
|
const btn = document.getElementById('themeBtn');
|
|
if (html.getAttribute('data-theme') === 'dark') {
|
|
html.setAttribute('data-theme', 'light');
|
|
btn.textContent = '\u2600'; // sun
|
|
localStorage.setItem('ax-seminar-theme', 'light');
|
|
} else {
|
|
html.setAttribute('data-theme', 'dark');
|
|
btn.textContent = '\uD83C\uDF19'; // moon
|
|
localStorage.setItem('ax-seminar-theme', 'dark');
|
|
}
|
|
}
|
|
|
|
// Restore saved theme
|
|
(function() {
|
|
const saved = localStorage.getItem('ax-seminar-theme');
|
|
if (saved === 'light') {
|
|
document.documentElement.setAttribute('data-theme', 'light');
|
|
document.getElementById('themeBtn').textContent = '\u2600';
|
|
}
|
|
})();
|
|
|
|
// ── Scroll Spy — highlight active TOC item ──
|
|
(function() {
|
|
const tocLinks = document.querySelectorAll('.toc a[href^="#"]');
|
|
const sections = [];
|
|
tocLinks.forEach(function(link) {
|
|
const id = link.getAttribute('href').slice(1);
|
|
const el = document.getElementById(id);
|
|
if (el) sections.push({ id: id, el: el, link: link });
|
|
});
|
|
|
|
function updateActive() {
|
|
let current = null;
|
|
for (let i = sections.length - 1; i >= 0; i--) {
|
|
const rect = sections[i].el.getBoundingClientRect();
|
|
if (rect.top <= 80) { current = sections[i]; break; }
|
|
}
|
|
tocLinks.forEach(function(l) { l.classList.remove('active'); });
|
|
if (current) {
|
|
current.link.classList.add('active');
|
|
// scroll TOC to keep active item visible
|
|
const tocEl = document.getElementById('toc');
|
|
const linkRect = current.link.getBoundingClientRect();
|
|
const tocRect = tocEl.getBoundingClientRect();
|
|
if (linkRect.top < tocRect.top + 60 || linkRect.bottom > tocRect.bottom - 20) {
|
|
current.link.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
}
|
|
}
|
|
}
|
|
|
|
window.addEventListener('scroll', updateActive, { passive: true });
|
|
updateActive();
|
|
})();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|