commit-ai-agent 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commit-ai-agent",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "AI-powered git commit & working status analyzer with web UI",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
package/public/app.js CHANGED
@@ -5,6 +5,58 @@ let selectedProject = null;
5
5
  let isAnalyzing = false;
6
6
  let analyzeMode = 'commit'; // 'commit' | 'status'
7
7
 
8
+ // ── Aria State Machine ──
9
+ function setAriaState(state, opts = {}) {
10
+ const robotWrap = document.getElementById('aria-robot-wrap');
11
+ const bubbleText = document.getElementById('aria-bubble-text');
12
+ const typingDots = document.getElementById('aria-typing-dots');
13
+ const chipDot = document.getElementById('aria-chip-dot');
14
+ const chipText = document.getElementById('aria-chip-text');
15
+ if (!robotWrap || !bubbleText) return;
16
+
17
+ // Base state (strip -commit / -status suffix for robot/chip)
18
+ const baseState = state.startsWith('ready') ? 'ready' : state;
19
+
20
+ // Robot animation class
21
+ robotWrap.className = `aria-robot-wrap ${baseState}`;
22
+
23
+ // Header chip
24
+ const chipMap = {
25
+ idle: { cls: 'idle', label: 'Aria · 대기 중' },
26
+ ready: { cls: 'ready', label: 'Aria · 준비됨' },
27
+ thinking: { cls: 'thinking', label: 'Aria · 분석 중...' },
28
+ done: { cls: 'done', label: 'Aria · 완료' },
29
+ error: { cls: 'error', label: 'Aria · 오류' },
30
+ };
31
+ const cm = chipMap[baseState] || chipMap.idle;
32
+ if (chipDot) chipDot.className = `aria-chip-dot ${cm.cls}`;
33
+ if (chipText) chipText.textContent = cm.label;
34
+
35
+ // Bubble messages
36
+ const p = opts.project ? `<strong>${opts.project}</strong>` : '';
37
+ const msgMap = {
38
+ idle: '어떤 프로젝트의 커밋을 분석해드릴까요?',
39
+ 'ready-commit': `${p} 최근 커밋을 확인했어요. 분석을 시작할까요? 👀`,
40
+ 'ready-status': opts.n > 0
41
+ ? `${p}에서 변경된 파일 <strong>${opts.n}개</strong>를 발견했어요. 리뷰해드릴까요?`
42
+ : `${p}에 현재 변경사항이 없어요.`,
43
+ thinking: '코드를 꼼꼼히 살펴보고 있어요',
44
+ done: '분석 완료! 리포트를 확인해보세요. 😊',
45
+ error: '앗, 문제가 발생했어요. 다시 시도해볼까요?',
46
+ };
47
+ const newMsg = msgMap[state] || msgMap.idle;
48
+
49
+ // Fade transition
50
+ bubbleText.style.opacity = '0';
51
+ bubbleText.style.transform = 'translateY(4px)';
52
+ setTimeout(() => {
53
+ bubbleText.innerHTML = newMsg;
54
+ if (typingDots) typingDots.style.display = state === 'thinking' ? 'inline-flex' : 'none';
55
+ bubbleText.style.opacity = '1';
56
+ bubbleText.style.transform = 'translateY(0)';
57
+ }, 180);
58
+ }
59
+
8
60
  // ── Boot ──
9
61
  document.addEventListener('DOMContentLoaded', () => {
10
62
  init();
@@ -29,6 +81,8 @@ async function init() {
29
81
  document.getElementById('status-diff-toggle-btn').addEventListener('click', () => {
30
82
  togglePre('status-diff-content', 'status-diff-toggle-btn');
31
83
  });
84
+
85
+ setAriaState('idle');
32
86
  }
33
87
 
34
88
  // ── Config check ──
@@ -106,8 +160,10 @@ async function fetchCommitPreview() {
106
160
  if (error) throw new Error(error);
107
161
  renderCommitCard(commit);
108
162
  document.getElementById('commit-card').style.display = 'block';
163
+ setAriaState('ready-commit', { project: selectedProject });
109
164
  } catch (e) {
110
165
  console.warn('commit preview failed:', e.message);
166
+ setAriaState('ready-commit', { project: selectedProject });
111
167
  }
112
168
  }
113
169
 
@@ -119,11 +175,14 @@ async function fetchStatusPreview() {
119
175
  if (status) {
120
176
  renderStatusCard(status);
121
177
  document.getElementById('status-card').style.display = 'block';
178
+ setAriaState('ready-status', { project: selectedProject, n: status.totalFiles });
122
179
  } else {
123
180
  document.getElementById('selected-hint').textContent = `${selectedProject} — 변경사항 없음`;
181
+ setAriaState('ready-status', { project: selectedProject, n: 0 });
124
182
  }
125
183
  } catch (e) {
126
184
  console.warn('status preview failed:', e.message);
185
+ setAriaState('ready-status', { project: selectedProject, n: 0 });
127
186
  }
128
187
  }
129
188
 
@@ -176,6 +235,9 @@ function switchMode(mode) {
176
235
  document.getElementById('status-card').style.display = 'none';
177
236
  document.getElementById('result-card').style.display = 'none';
178
237
 
238
+ const btnText = document.getElementById('analyze-btn-text');
239
+ if (btnText) btnText.textContent = mode === 'commit' ? 'Aria에게 분석 요청' : 'Aria에게 리뷰 요청';
240
+
179
241
  if (!selectedProject) return;
180
242
  if (mode === 'commit') fetchCommitPreview();
181
243
  else fetchStatusPreview();
@@ -202,7 +264,8 @@ async function startAnalysis(endpoint) {
202
264
  analysisBody.innerHTML = '';
203
265
  reportSaved.textContent = '';
204
266
  document.getElementById('copy-btn').style.display = 'none'; // 분석 시작 시 숨김
205
- setStatus('loading', '분석 준비 중...');
267
+ setStatus('loading', 'Aria가 코드를 살펴보고 있어요...');
268
+ setAriaState('thinking');
206
269
  analyzeBtn.disabled = true;
207
270
  btnIcon.textContent = '⏳';
208
271
 
@@ -252,19 +315,22 @@ async function startAnalysis(endpoint) {
252
315
  }
253
316
  } else if (data.type === 'done') {
254
317
  setStatus('done', '✅ 분석 완료!');
318
+ setAriaState('done');
255
319
  document.getElementById('copy-btn').style.display = 'inline-flex'; // 완료 시에만 표시
256
320
  } else if (data.type === 'error') {
257
321
  setStatus('error', `오류: ${data.message}`);
322
+ setAriaState('error');
258
323
  }
259
324
  } catch {}
260
325
  }
261
326
  }
262
327
  } catch (err) {
263
328
  setStatus('error', `네트워크 오류: ${err.message}`);
329
+ setAriaState('error');
264
330
  } finally {
265
331
  isAnalyzing = false;
266
332
  analyzeBtn.disabled = false;
267
- btnIcon.textContent = '🔍';
333
+ btnIcon.textContent = '🤖';
268
334
  document.getElementById('copy-btn')._text = fullText;
269
335
  resultCard.scrollIntoView({ behavior: 'smooth', block: 'start' });
270
336
  }
package/public/index.html CHANGED
@@ -37,6 +37,10 @@
37
37
  리포트
38
38
  </button>
39
39
  </nav>
40
+ <div class="aria-chip" id="aria-chip">
41
+ <span class="aria-chip-dot" id="aria-chip-dot"></span>
42
+ <span id="aria-chip-text">Aria · 대기 중</span>
43
+ </div>
40
44
  </div>
41
45
  </header>
42
46
 
@@ -44,6 +48,59 @@
44
48
  <main class="main">
45
49
  <!-- ── Tab: Analyze ── -->
46
50
  <section class="tab-content active" id="tab-analyze">
51
+
52
+ <!-- ── Aria Hero ── -->
53
+ <div class="aria-hero">
54
+ <div class="aria-robot-wrap idle" id="aria-robot-wrap">
55
+ <svg class="aria-robot" viewBox="0 0 90 115" width="90" height="115" xmlns="http://www.w3.org/2000/svg">
56
+ <defs>
57
+ <linearGradient id="rg-head" x1="0%" y1="0%" x2="100%" y2="100%">
58
+ <stop offset="0%" stop-color="#6366f1"/>
59
+ <stop offset="100%" stop-color="#22d3ee"/>
60
+ </linearGradient>
61
+ <linearGradient id="rg-body" x1="0%" y1="0%" x2="100%" y2="100%">
62
+ <stop offset="0%" stop-color="#4f46e5"/>
63
+ <stop offset="100%" stop-color="#0e7490"/>
64
+ </linearGradient>
65
+ </defs>
66
+ <!-- Antenna -->
67
+ <line x1="45" y1="7" x2="45" y2="20" stroke="#818cf8" stroke-width="2.5" stroke-linecap="round"/>
68
+ <circle class="aria-antenna-dot" cx="45" cy="5" r="5" fill="#818cf8"/>
69
+ <!-- Ears -->
70
+ <rect x="1" y="34" width="9" height="18" rx="4.5" fill="#4f46e5"/>
71
+ <rect x="80" y="34" width="9" height="18" rx="4.5" fill="#4f46e5"/>
72
+ <!-- Head -->
73
+ <rect x="9" y="18" width="72" height="62" rx="20" fill="url(#rg-head)"/>
74
+ <!-- Eyes white -->
75
+ <circle cx="30" cy="48" r="13" fill="white" opacity="0.92"/>
76
+ <circle cx="60" cy="48" r="13" fill="white" opacity="0.92"/>
77
+ <!-- Pupils -->
78
+ <circle cx="32" cy="49" r="7.5" fill="#1e1b4b"/>
79
+ <circle cx="62" cy="49" r="7.5" fill="#1e1b4b"/>
80
+ <!-- Eye shine -->
81
+ <circle cx="35" cy="45" r="2.5" fill="white"/>
82
+ <circle cx="65" cy="45" r="2.5" fill="white"/>
83
+ <!-- Smile -->
84
+ <path d="M 32 63 Q 45 73 58 63" stroke="white" stroke-width="2.8" fill="none" stroke-linecap="round"/>
85
+ <!-- Body -->
86
+ <rect x="20" y="84" width="50" height="28" rx="12" fill="url(#rg-body)"/>
87
+ <!-- Body buttons -->
88
+ <circle cx="34" cy="98" r="5" fill="rgba(255,255,255,0.35)"/>
89
+ <circle cx="45" cy="98" r="5" fill="rgba(255,255,255,0.35)"/>
90
+ <circle cx="56" cy="98" r="5" fill="rgba(255,255,255,0.35)"/>
91
+ </svg>
92
+ </div>
93
+ <div class="aria-bubble-wrap">
94
+ <div class="aria-bubble">
95
+ <span class="aria-bubble-text" id="aria-bubble-text">어떤 프로젝트의 커밋을 분석해드릴까요?</span>
96
+ <span class="aria-typing-dots" id="aria-typing-dots" style="display:none">
97
+ <span></span><span></span><span></span>
98
+ </span>
99
+ </div>
100
+ <div class="aria-name-badge">Aria</div>
101
+ </div>
102
+ </div>
103
+
47
104
  <!-- API Key Warning -->
48
105
  <div class="alert alert-warn" id="api-key-warn" style="display: none">
49
106
  ⚠️ <strong>.env</strong> 파일에 <code>GEMINI_API_KEY</code>가 설정되지
@@ -80,7 +137,7 @@
80
137
  >프로젝트를 선택해 주세요</span
81
138
  >
82
139
  <button class="btn-primary" id="analyze-btn" disabled>
83
- <span class="btn-icon">🔍</span> 커밋 분석하기
140
+ <span class="btn-icon">🤖</span> <span id="analyze-btn-text">Aria에게 분석 요청</span>
84
141
  </button>
85
142
  </div>
86
143
  </div>
@@ -118,7 +175,7 @@
118
175
  <!-- Analysis Output -->
119
176
  <div class="card result-card" id="result-card" style="display: none">
120
177
  <div class="result-header">
121
- <h2 class="card-title">📝 AI 분석 결과</h2>
178
+ <h2 class="card-title">🤖 Aria의 분석 리포트</h2>
122
179
  <div class="result-actions">
123
180
  <button class="btn-ghost" id="copy-btn" style="display: none">
124
181
  📋 복사
package/public/style.css CHANGED
@@ -283,11 +283,122 @@ body {
283
283
  .report-item-date { font-size: 12px; color: var(--text3); font-family: 'JetBrains Mono', monospace; }
284
284
  .empty-state { color: var(--text3); font-size: 14px; text-align: center; padding: 32px; }
285
285
 
286
+ /* ── Aria: Header Chip ── */
287
+ .aria-chip {
288
+ display: flex; align-items: center; gap: 7px;
289
+ background: var(--bg3); border: 1px solid var(--border);
290
+ border-radius: 99px; padding: 5px 13px;
291
+ font-size: 12px; color: var(--text2);
292
+ margin-right: 4px; white-space: nowrap;
293
+ }
294
+ .aria-chip-dot {
295
+ width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
296
+ background: #818cf8;
297
+ }
298
+ .aria-chip-dot.idle { background: #818cf8; }
299
+ .aria-chip-dot.ready { background: #34d399; animation: pulse 1.2s infinite; }
300
+ .aria-chip-dot.thinking { background: #a78bfa; animation: pulse 0.7s infinite; }
301
+ .aria-chip-dot.done { background: #34d399; }
302
+ .aria-chip-dot.error { background: #f87171; }
303
+
304
+ /* ── Aria: Hero Section ── */
305
+ .aria-hero {
306
+ display: flex; align-items: center; gap: 24px;
307
+ background: var(--bg2); border: 1px solid var(--border);
308
+ border-radius: var(--radius); padding: 20px 24px;
309
+ margin-bottom: 20px;
310
+ }
311
+
312
+ /* ── Aria: Robot ── */
313
+ .aria-robot-wrap { flex-shrink: 0; }
314
+ .aria-robot { display: block; }
315
+
316
+ .aria-robot-wrap.idle .aria-robot,
317
+ .aria-robot-wrap.ready .aria-robot { animation: aria-float 3s ease-in-out infinite alternate; }
318
+ .aria-robot-wrap.thinking .aria-robot { animation: aria-float 1.4s ease-in-out infinite alternate; }
319
+ .aria-robot-wrap.done .aria-robot { animation: aria-bounce 0.55s ease forwards; }
320
+ .aria-robot-wrap.error .aria-robot { animation: aria-shake 0.45s ease; }
321
+
322
+ @keyframes aria-float { 0%{transform:translateY(0)} 100%{transform:translateY(-8px)} }
323
+ @keyframes aria-bounce { 0%,100%{transform:scale(1)} 45%{transform:scale(1.13)} }
324
+ @keyframes aria-shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-5px)} 75%{transform:translateX(5px)} }
325
+
326
+ /* Antenna dot by state */
327
+ .aria-robot-wrap.idle .aria-antenna-dot { fill: #818cf8; }
328
+ .aria-robot-wrap.ready .aria-antenna-dot { fill: #34d399; animation: aria-blink 1s ease-in-out infinite; }
329
+ .aria-robot-wrap.thinking .aria-antenna-dot { fill: #a78bfa; animation: aria-glow 0.7s ease-in-out infinite; }
330
+ .aria-robot-wrap.done .aria-antenna-dot { fill: #34d399; }
331
+ .aria-robot-wrap.error .aria-antenna-dot { fill: #f87171; }
332
+
333
+ @keyframes aria-blink { 0%,100%{opacity:1} 50%{opacity:0.35} }
334
+ @keyframes aria-glow {
335
+ 0%,100%{ filter: drop-shadow(0 0 3px #a78bfa); }
336
+ 50% { filter: drop-shadow(0 0 9px #a78bfa); }
337
+ }
338
+
339
+ /* ── Aria: Bubble ── */
340
+ .aria-bubble-wrap {
341
+ flex: 1; display: flex; flex-direction: column;
342
+ align-items: flex-start; gap: 10px;
343
+ }
344
+ .aria-bubble {
345
+ position: relative;
346
+ background: var(--bg3);
347
+ border: 1.5px solid var(--primary);
348
+ border-radius: 16px; padding: 14px 18px;
349
+ font-size: 15px; line-height: 1.7; color: var(--text);
350
+ max-width: 440px;
351
+ }
352
+ /* Speech bubble tail — points left toward robot */
353
+ .aria-bubble::before {
354
+ content: ''; position: absolute;
355
+ left: -11px; top: 50%; transform: translateY(-50%);
356
+ border: 7px solid transparent; border-right-color: var(--primary);
357
+ }
358
+ .aria-bubble::after {
359
+ content: ''; position: absolute;
360
+ left: -8px; top: 50%; transform: translateY(-50%);
361
+ border: 5.5px solid transparent; border-right-color: var(--bg3);
362
+ }
363
+ .aria-bubble-text { display: inline; transition: opacity 0.18s ease, transform 0.18s ease; }
364
+
365
+ /* Typing dots (thinking state) */
366
+ .aria-typing-dots {
367
+ display: inline-flex; gap: 3px;
368
+ align-items: center; margin-left: 4px; vertical-align: middle;
369
+ }
370
+ .aria-typing-dots span {
371
+ width: 5px; height: 5px; border-radius: 50%;
372
+ background: var(--primary); display: inline-block;
373
+ animation: aria-dot-bounce 1.2s ease-in-out infinite;
374
+ }
375
+ .aria-typing-dots span:nth-child(2) { animation-delay: 0.2s; }
376
+ .aria-typing-dots span:nth-child(3) { animation-delay: 0.4s; }
377
+ @keyframes aria-dot-bounce {
378
+ 0%,80%,100%{ transform: translateY(0); opacity: 0.4; }
379
+ 40% { transform: translateY(-5px); opacity: 1; }
380
+ }
381
+
382
+ /* Aria name badge */
383
+ .aria-name-badge {
384
+ font-size: 11px; font-weight: 700; letter-spacing: 0.5px;
385
+ color: var(--primary);
386
+ background: rgba(99,102,241,0.12); border: 1px solid rgba(99,102,241,0.25);
387
+ padding: 2px 11px; border-radius: 99px;
388
+ }
389
+
286
390
  /* ── Responsive ── */
287
391
  @media (max-width: 640px) {
288
392
  .header-inner { padding: 0 16px; }
393
+ .aria-chip { display: none; } /* header chip hidden on mobile to save space */
289
394
  .main { padding: 16px 16px 48px; }
290
395
  .card { padding: 18px; }
291
396
  .card-footer { flex-direction: column; align-items: stretch; gap: 12px; }
292
397
  .commit-meta { grid-template-columns: 1fr 1fr; }
398
+ /* Aria hero: stack vertically */
399
+ .aria-hero { flex-direction: column; align-items: center; padding: 16px; gap: 14px; }
400
+ .aria-robot { width: 70px; height: auto; }
401
+ .aria-bubble { max-width: 100%; }
402
+ .aria-bubble::before, .aria-bubble::after { display: none; }
403
+ .aria-bubble-wrap { align-items: center; width: 100%; }
293
404
  }