@n2world/orchestrator 1.1.0
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/dist/agent-os-rd.d.ts +100 -0
- package/dist/agent-os-rd.js +258 -0
- package/dist/audit-store.d.ts +14 -0
- package/dist/audit-store.js +107 -0
- package/dist/beta-runner.d.ts +95 -0
- package/dist/beta-runner.js +251 -0
- package/dist/beta.d.ts +102 -0
- package/dist/beta.js +180 -0
- package/dist/browser-agent.d.ts +90 -0
- package/dist/browser-agent.js +223 -0
- package/dist/channel-gateway.d.ts +74 -0
- package/dist/channel-gateway.js +270 -0
- package/dist/channels.d.ts +120 -0
- package/dist/channels.js +432 -0
- package/dist/chat-store.d.ts +29 -0
- package/dist/chat-store.js +120 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +607 -0
- package/dist/command-screen.d.ts +12 -0
- package/dist/command-screen.js +44 -0
- package/dist/commit-gate.d.ts +98 -0
- package/dist/commit-gate.js +258 -0
- package/dist/companion-api.d.ts +37 -0
- package/dist/companion-api.js +101 -0
- package/dist/conversation-graph.d.ts +39 -0
- package/dist/conversation-graph.js +92 -0
- package/dist/cost-estimator.d.ts +27 -0
- package/dist/cost-estimator.js +42 -0
- package/dist/cron-runner.d.ts +31 -0
- package/dist/cron-runner.js +46 -0
- package/dist/dashboard/chat.html +326 -0
- package/dist/dashboard/dental.html +58 -0
- package/dist/dashboard/freebie.png +0 -0
- package/dist/dashboard/icon-192.png +0 -0
- package/dist/dashboard/index.html +892 -0
- package/dist/dashboard/manifest.json +15 -0
- package/dist/dashboard/service-worker.js +28 -0
- package/dist/dashboard-server.d.ts +37 -0
- package/dist/dashboard-server.js +457 -0
- package/dist/dental-intake-service.d.ts +37 -0
- package/dist/dental-intake-service.js +61 -0
- package/dist/dental-metrics.d.ts +25 -0
- package/dist/dental-metrics.js +37 -0
- package/dist/docking.d.ts +36 -0
- package/dist/docking.js +73 -0
- package/dist/finance-mcts-candidate.d.ts +37 -0
- package/dist/finance-mcts-candidate.js +106 -0
- package/dist/finance-regulation-kr.d.ts +33 -0
- package/dist/finance-regulation-kr.js +104 -0
- package/dist/finance-workflow.d.ts +135 -0
- package/dist/finance-workflow.js +242 -0
- package/dist/gateway.d.ts +18 -0
- package/dist/gateway.js +123 -0
- package/dist/governance.d.ts +39 -0
- package/dist/governance.js +48 -0
- package/dist/governed-executor.d.ts +31 -0
- package/dist/governed-executor.js +63 -0
- package/dist/governed-llm.d.ts +41 -0
- package/dist/governed-llm.js +83 -0
- package/dist/gpu-bridge.d.ts +16 -0
- package/dist/gpu-bridge.js +53 -0
- package/dist/health.d.ts +47 -0
- package/dist/health.js +66 -0
- package/dist/identity-link.d.ts +32 -0
- package/dist/identity-link.js +98 -0
- package/dist/index.d.ts +184 -0
- package/dist/index.js +417 -0
- package/dist/integrations/emr-adapter.d.ts +41 -0
- package/dist/integrations/emr-adapter.js +63 -0
- package/dist/kakao-oauth.d.ts +16 -0
- package/dist/kakao-oauth.js +87 -0
- package/dist/knowledge-graph.d.ts +53 -0
- package/dist/knowledge-graph.js +156 -0
- package/dist/llm.d.ts +65 -0
- package/dist/llm.js +357 -0
- package/dist/mcp-client-guard.d.ts +32 -0
- package/dist/mcp-client-guard.js +179 -0
- package/dist/mcp-macaroon.d.ts +75 -0
- package/dist/mcp-macaroon.js +161 -0
- package/dist/mcts-kernel-bridge.d.ts +36 -0
- package/dist/mcts-kernel-bridge.js +99 -0
- package/dist/mcts-prior.d.ts +79 -0
- package/dist/mcts-prior.js +170 -0
- package/dist/model-router.d.ts +51 -0
- package/dist/model-router.js +75 -0
- package/dist/multi-axis-lift.d.ts +43 -0
- package/dist/multi-axis-lift.js +141 -0
- package/dist/net-guard.d.ts +39 -0
- package/dist/net-guard.js +141 -0
- package/dist/onboarding.d.ts +38 -0
- package/dist/onboarding.js +94 -0
- package/dist/oracle-anchored-search.d.ts +25 -0
- package/dist/oracle-anchored-search.js +50 -0
- package/dist/oracle.d.ts +22 -0
- package/dist/oracle.js +116 -0
- package/dist/p6-governance.d.ts +150 -0
- package/dist/p6-governance.js +252 -0
- package/dist/pairing.d.ts +22 -0
- package/dist/pairing.js +81 -0
- package/dist/personalization.d.ts +35 -0
- package/dist/personalization.js +73 -0
- package/dist/pglite-hnsw-bridge.d.ts +118 -0
- package/dist/pglite-hnsw-bridge.js +311 -0
- package/dist/pglite-store.d.ts +59 -0
- package/dist/pglite-store.js +180 -0
- package/dist/playbook.d.ts +79 -0
- package/dist/playbook.js +83 -0
- package/dist/playbooks/dental-intake.d.ts +20 -0
- package/dist/playbooks/dental-intake.js +112 -0
- package/dist/predictive-agent.d.ts +157 -0
- package/dist/predictive-agent.js +535 -0
- package/dist/prompt-optimizer.d.ts +18 -0
- package/dist/prompt-optimizer.js +104 -0
- package/dist/rate-limiter.d.ts +25 -0
- package/dist/rate-limiter.js +75 -0
- package/dist/safety-anneal.d.ts +83 -0
- package/dist/safety-anneal.js +153 -0
- package/dist/sandbox-controller.d.ts +12 -0
- package/dist/sandbox-controller.js +95 -0
- package/dist/satisfaction-metrics.d.ts +26 -0
- package/dist/satisfaction-metrics.js +61 -0
- package/dist/sensor-bridge.d.ts +53 -0
- package/dist/sensor-bridge.js +133 -0
- package/dist/session-repair.d.ts +27 -0
- package/dist/session-repair.js +66 -0
- package/dist/slack-finance-intake.d.ts +42 -0
- package/dist/slack-finance-intake.js +122 -0
- package/dist/symbolic-dynamics.d.ts +113 -0
- package/dist/symbolic-dynamics.js +420 -0
- package/dist/telemetry.d.ts +19 -0
- package/dist/telemetry.js +68 -0
- package/dist/text-embedding.d.ts +6 -0
- package/dist/text-embedding.js +42 -0
- package/dist/tier-classifier.d.ts +20 -0
- package/dist/tier-classifier.js +58 -0
- package/dist/tier-guard.d.ts +36 -0
- package/dist/tier-guard.js +56 -0
- package/dist/tui.d.ts +9 -0
- package/dist/tui.js +214 -0
- package/dist/update-security.d.ts +31 -0
- package/dist/update-security.js +112 -0
- package/dist/v-calibration.d.ts +16 -0
- package/dist/v-calibration.js +42 -0
- package/dist/value-calibration.d.ts +41 -0
- package/dist/value-calibration.js +133 -0
- package/dist/value-head.d.ts +20 -0
- package/dist/value-head.js +91 -0
- package/dist/wal-buffer.d.ts +23 -0
- package/dist/wal-buffer.js +144 -0
- package/dist/wiki-synthesizer.d.ts +80 -0
- package/dist/wiki-synthesizer.js +0 -0
- package/dist/worker-agent.d.ts +10 -0
- package/dist/worker-agent.js +19 -0
- package/package.json +65 -0
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ko">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="theme-color" content="#42a5f5">
|
|
7
|
+
<title>n2world Agent Dashboard</title>
|
|
8
|
+
<link rel="manifest" href="/manifest.json">
|
|
9
|
+
<!-- 탈-Google: Google Fonts CDN(fonts.googleapis.com/gstatic.com) 제거.
|
|
10
|
+
오프라인 PWA·프라이버시 보장을 위해 외부 폰트 요청을 하지 않는다.
|
|
11
|
+
브랜드 폰트(Outfit/Inter)를 쓰려면 .woff2 를 src/dashboard/fonts/ 에 넣고
|
|
12
|
+
아래 @font-face 주석을 활성화하라(셀프 호스팅). 미설치 시 시스템 폰트로 폴백. -->
|
|
13
|
+
<!--
|
|
14
|
+
<style>
|
|
15
|
+
@font-face { font-family:'Outfit'; src:url('/fonts/Outfit.woff2') format('woff2'); font-display:swap; }
|
|
16
|
+
@font-face { font-family:'Inter'; src:url('/fonts/Inter.woff2') format('woff2'); font-display:swap; }
|
|
17
|
+
</style>
|
|
18
|
+
-->
|
|
19
|
+
|
|
20
|
+
<style>
|
|
21
|
+
:root {
|
|
22
|
+
--bg-color: #f4f7fa;
|
|
23
|
+
--card-bg: rgba(255, 255, 255, 0.85);
|
|
24
|
+
--text-main: #2b3a4a;
|
|
25
|
+
--text-muted: #6b7c90;
|
|
26
|
+
--pastel-blue: #e3f2fd;
|
|
27
|
+
--pastel-orange: #fff3e0;
|
|
28
|
+
--pastel-green: #e8f5e9;
|
|
29
|
+
--accent-blue: #42a5f5;
|
|
30
|
+
--accent-orange: #ffb74d;
|
|
31
|
+
--accent-green: #66bb6a;
|
|
32
|
+
--shadow: 0 12px 40px rgba(163, 177, 198, 0.25);
|
|
33
|
+
/* 셀프 호스팅 폰트가 있으면 자동 사용, 없으면 시스템 폰트로 우아하게 폴백 */
|
|
34
|
+
--font-outfit: 'Outfit', 'Pretendard', system-ui, -apple-system, 'Segoe UI', Roboto, 'Malgun Gothic', sans-serif;
|
|
35
|
+
--font-inter: 'Inter', 'Pretendard', system-ui, -apple-system, 'Segoe UI', Roboto, 'Malgun Gothic', sans-serif;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
* {
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
body {
|
|
45
|
+
background-color: var(--bg-color);
|
|
46
|
+
font-family: var(--font-inter);
|
|
47
|
+
color: var(--text-main);
|
|
48
|
+
display: flex;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
align-items: center;
|
|
51
|
+
min-height: 100vh;
|
|
52
|
+
overflow-x: hidden;
|
|
53
|
+
padding: 20px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Mobile Screen Viewport Emulator */
|
|
57
|
+
.mobile-frame {
|
|
58
|
+
width: 100%;
|
|
59
|
+
max-width: 410px;
|
|
60
|
+
height: 820px;
|
|
61
|
+
background: var(--card-bg);
|
|
62
|
+
border-radius: 40px;
|
|
63
|
+
box-shadow: var(--shadow);
|
|
64
|
+
border: 8px solid #ffffff;
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
position: relative;
|
|
69
|
+
backdrop-filter: blur(20px);
|
|
70
|
+
-webkit-backdrop-filter: blur(20px);
|
|
71
|
+
transition: transform 0.3s ease;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.mobile-frame:hover {
|
|
75
|
+
transform: translateY(-5px);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Header Section */
|
|
79
|
+
.header {
|
|
80
|
+
padding: 25px 25px 15px 25px;
|
|
81
|
+
display: flex;
|
|
82
|
+
justify-content: space-between;
|
|
83
|
+
align-items: center;
|
|
84
|
+
border-bottom: 1px solid rgba(227, 242, 253, 0.6);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.brand {
|
|
88
|
+
font-family: var(--font-outfit);
|
|
89
|
+
font-size: 24px;
|
|
90
|
+
font-weight: 800;
|
|
91
|
+
color: var(--accent-blue);
|
|
92
|
+
letter-spacing: -0.5px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.status-badge {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 6px;
|
|
99
|
+
background: var(--pastel-green);
|
|
100
|
+
padding: 6px 14px;
|
|
101
|
+
border-radius: 20px;
|
|
102
|
+
font-size: 13px;
|
|
103
|
+
font-weight: 600;
|
|
104
|
+
color: var(--accent-green);
|
|
105
|
+
font-family: var(--font-outfit);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.status-dot {
|
|
109
|
+
width: 8px;
|
|
110
|
+
height: 8px;
|
|
111
|
+
background: var(--accent-green);
|
|
112
|
+
border-radius: 50%;
|
|
113
|
+
animation: pulse 1.5s infinite;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Avatar Mascot Section */
|
|
117
|
+
.mascot-container {
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
align-items: center;
|
|
121
|
+
padding: 25px 0;
|
|
122
|
+
background: linear-gradient(180deg, rgba(227, 242, 253, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.avatar-wrapper {
|
|
126
|
+
width: 110px;
|
|
127
|
+
height: 110px;
|
|
128
|
+
background: #ffffff;
|
|
129
|
+
border-radius: 50%;
|
|
130
|
+
box-shadow: 0 8px 24px rgba(66, 165, 245, 0.15);
|
|
131
|
+
display: flex;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
align-items: center;
|
|
134
|
+
position: relative;
|
|
135
|
+
border: 4px solid var(--pastel-blue);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.avatar-wrapper.idle img {
|
|
139
|
+
width: 90px;
|
|
140
|
+
height: 90px;
|
|
141
|
+
border-radius: 50%;
|
|
142
|
+
object-fit: cover;
|
|
143
|
+
animation: float 3s ease-in-out infinite;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.avatar-wrapper.thinking img {
|
|
147
|
+
width: 90px;
|
|
148
|
+
height: 90px;
|
|
149
|
+
border-radius: 50%;
|
|
150
|
+
object-fit: cover;
|
|
151
|
+
animation: shake 0.5s ease-in-out infinite alternate;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@keyframes shake {
|
|
155
|
+
0% { transform: translate(1px, 1px) rotate(0deg); }
|
|
156
|
+
10% { transform: translate(-1px, -2px) rotate(-1deg); }
|
|
157
|
+
20% { transform: translate(-3px, 0px) rotate(1deg); }
|
|
158
|
+
30% { transform: translate(0px, 2px) rotate(0deg); }
|
|
159
|
+
40% { transform: translate(1px, -1px) rotate(1deg); }
|
|
160
|
+
50% { transform: translate(-1px, 2px) rotate(-1deg); }
|
|
161
|
+
60% { transform: translate(-3px, 1px) rotate(0deg); }
|
|
162
|
+
75% { transform: translate(2px, 1px) rotate(-2deg); }
|
|
163
|
+
90% { transform: translate(-1px, -1px) rotate(0deg); }
|
|
164
|
+
100% { transform: translate(1px, 2px) rotate(0deg); }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.mic-btn {
|
|
168
|
+
width: 48px;
|
|
169
|
+
height: 48px;
|
|
170
|
+
background: var(--pastel-blue);
|
|
171
|
+
border: none;
|
|
172
|
+
border-radius: 50%;
|
|
173
|
+
cursor: pointer;
|
|
174
|
+
display: flex;
|
|
175
|
+
justify-content: center;
|
|
176
|
+
align-items: center;
|
|
177
|
+
transition: all 0.2s ease;
|
|
178
|
+
box-shadow: 0 4px 12px rgba(66, 165, 245, 0.1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.mic-btn.recording {
|
|
182
|
+
background: #ffcdd2;
|
|
183
|
+
animation: mic-pulse 1s infinite alternate;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.mic-btn.recording svg {
|
|
187
|
+
fill: #d32f2f;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.mic-btn svg {
|
|
191
|
+
width: 20px;
|
|
192
|
+
height: 20px;
|
|
193
|
+
fill: var(--accent-blue);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@keyframes mic-pulse {
|
|
197
|
+
from { transform: scale(1); }
|
|
198
|
+
to { transform: scale(1.1); }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.mascot-name {
|
|
202
|
+
font-family: var(--font-outfit);
|
|
203
|
+
font-size: 20px;
|
|
204
|
+
font-weight: 700;
|
|
205
|
+
margin-top: 12px;
|
|
206
|
+
color: var(--text-main);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.mascot-sub {
|
|
210
|
+
font-size: 13px;
|
|
211
|
+
color: var(--text-muted);
|
|
212
|
+
margin-top: 3px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Progress Rings / Stats */
|
|
216
|
+
.stats-row {
|
|
217
|
+
display: flex;
|
|
218
|
+
justify-content: space-around;
|
|
219
|
+
padding: 0 20px 20px 20px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.stat-card {
|
|
223
|
+
background: #ffffff;
|
|
224
|
+
border-radius: 20px;
|
|
225
|
+
padding: 15px;
|
|
226
|
+
width: 105px;
|
|
227
|
+
box-shadow: 0 4px 15px rgba(163, 177, 198, 0.1);
|
|
228
|
+
display: flex;
|
|
229
|
+
flex-direction: column;
|
|
230
|
+
align-items: center;
|
|
231
|
+
position: relative;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.stat-svg {
|
|
235
|
+
width: 60px;
|
|
236
|
+
height: 60px;
|
|
237
|
+
transform: rotate(-90deg);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.stat-svg circle {
|
|
241
|
+
fill: none;
|
|
242
|
+
stroke-width: 6;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.stat-svg .bg-circle {
|
|
246
|
+
stroke: var(--bg-color);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.stat-svg .val-circle {
|
|
250
|
+
stroke-linecap: round;
|
|
251
|
+
transition: stroke-dashoffset 1s ease-out;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.stat-label {
|
|
255
|
+
font-size: 11px;
|
|
256
|
+
font-weight: 600;
|
|
257
|
+
color: var(--text-muted);
|
|
258
|
+
margin-top: 10px;
|
|
259
|
+
text-align: center;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.stat-number {
|
|
263
|
+
position: absolute;
|
|
264
|
+
top: 32px;
|
|
265
|
+
font-family: var(--font-outfit);
|
|
266
|
+
font-size: 13px;
|
|
267
|
+
font-weight: 700;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Conversations / Thought Logs */
|
|
271
|
+
.thought-console {
|
|
272
|
+
flex: 1;
|
|
273
|
+
padding: 0 25px;
|
|
274
|
+
overflow-y: auto;
|
|
275
|
+
display: flex;
|
|
276
|
+
flex-direction: column;
|
|
277
|
+
gap: 12px;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Thought bubbles */
|
|
281
|
+
.thought-bubble {
|
|
282
|
+
max-width: 85%;
|
|
283
|
+
padding: 14px 18px;
|
|
284
|
+
border-radius: 20px;
|
|
285
|
+
font-size: 14px;
|
|
286
|
+
line-height: 1.45;
|
|
287
|
+
animation: slideIn 0.3s ease;
|
|
288
|
+
box-shadow: 0 4px 12px rgba(163, 177, 198, 0.08);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.bubble-agent {
|
|
292
|
+
background: var(--pastel-blue);
|
|
293
|
+
color: var(--text-main);
|
|
294
|
+
align-self: flex-start;
|
|
295
|
+
border-bottom-left-radius: 4px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.bubble-user {
|
|
299
|
+
background: var(--pastel-orange);
|
|
300
|
+
color: var(--text-main);
|
|
301
|
+
align-self: flex-end;
|
|
302
|
+
border-bottom-right-radius: 4px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* Control Footer */
|
|
306
|
+
.control-footer {
|
|
307
|
+
padding: 20px 25px 25px 25px;
|
|
308
|
+
background: #ffffff;
|
|
309
|
+
border-top: 1px solid rgba(227, 242, 253, 0.6);
|
|
310
|
+
display: flex;
|
|
311
|
+
gap: 12px;
|
|
312
|
+
align-items: center;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.input-box {
|
|
316
|
+
flex: 1;
|
|
317
|
+
height: 48px;
|
|
318
|
+
background: var(--bg-color);
|
|
319
|
+
border: 1.5px solid transparent;
|
|
320
|
+
border-radius: 24px;
|
|
321
|
+
padding: 0 20px;
|
|
322
|
+
font-size: 14px;
|
|
323
|
+
font-family: var(--font-inter);
|
|
324
|
+
outline: none;
|
|
325
|
+
transition: all 0.2s ease;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.input-box:focus {
|
|
329
|
+
border-color: var(--accent-blue);
|
|
330
|
+
background: #ffffff;
|
|
331
|
+
box-shadow: 0 0 10px rgba(66, 165, 245, 0.1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.action-btn {
|
|
335
|
+
width: 48px;
|
|
336
|
+
height: 48px;
|
|
337
|
+
background: var(--accent-blue);
|
|
338
|
+
border: none;
|
|
339
|
+
border-radius: 50%;
|
|
340
|
+
cursor: pointer;
|
|
341
|
+
display: flex;
|
|
342
|
+
justify-content: center;
|
|
343
|
+
align-items: center;
|
|
344
|
+
transition: transform 0.2s ease, background-color 0.2s ease;
|
|
345
|
+
box-shadow: 0 4px 12px rgba(66, 165, 245, 0.3);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.action-btn:hover {
|
|
349
|
+
transform: scale(1.05);
|
|
350
|
+
background-color: #1e88e5;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.action-btn svg {
|
|
354
|
+
width: 20px;
|
|
355
|
+
height: 20px;
|
|
356
|
+
fill: #ffffff;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* Safe Approval Popup Dialog */
|
|
360
|
+
.popup-overlay {
|
|
361
|
+
position: absolute;
|
|
362
|
+
top: 0;
|
|
363
|
+
left: 0;
|
|
364
|
+
width: 100%;
|
|
365
|
+
height: 100%;
|
|
366
|
+
background: rgba(43, 58, 74, 0.4);
|
|
367
|
+
backdrop-filter: blur(4px);
|
|
368
|
+
display: none;
|
|
369
|
+
justify-content: center;
|
|
370
|
+
align-items: center;
|
|
371
|
+
z-index: 100;
|
|
372
|
+
animation: fadeIn 0.2s ease;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.popup-card {
|
|
376
|
+
width: 85%;
|
|
377
|
+
background: #ffffff;
|
|
378
|
+
border-radius: 28px;
|
|
379
|
+
padding: 25px;
|
|
380
|
+
box-shadow: 0 20px 50px rgba(43, 58, 74, 0.15);
|
|
381
|
+
text-align: center;
|
|
382
|
+
animation: scaleUp 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.popup-title {
|
|
386
|
+
font-family: var(--font-outfit);
|
|
387
|
+
font-size: 18px;
|
|
388
|
+
font-weight: 700;
|
|
389
|
+
color: var(--text-main);
|
|
390
|
+
margin-bottom: 10px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.popup-desc {
|
|
394
|
+
font-size: 13px;
|
|
395
|
+
color: var(--text-muted);
|
|
396
|
+
line-height: 1.5;
|
|
397
|
+
margin-bottom: 20px;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.popup-btn-row {
|
|
401
|
+
display: flex;
|
|
402
|
+
gap: 12px;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.popup-btn {
|
|
406
|
+
flex: 1;
|
|
407
|
+
height: 44px;
|
|
408
|
+
border: none;
|
|
409
|
+
border-radius: 22px;
|
|
410
|
+
font-size: 14px;
|
|
411
|
+
font-weight: 600;
|
|
412
|
+
cursor: pointer;
|
|
413
|
+
font-family: var(--font-inter);
|
|
414
|
+
transition: all 0.2s ease;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.btn-cancel {
|
|
418
|
+
background: var(--bg-color);
|
|
419
|
+
color: var(--text-muted);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.btn-cancel:hover {
|
|
423
|
+
background: #e2e8f0;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.btn-approve {
|
|
427
|
+
background: var(--accent-blue);
|
|
428
|
+
color: #ffffff;
|
|
429
|
+
box-shadow: 0 4px 10px rgba(66, 165, 245, 0.3);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.btn-approve:hover {
|
|
433
|
+
background-color: #1e88e5;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/* Animations */
|
|
437
|
+
@keyframes pulse {
|
|
438
|
+
0% { transform: scale(1); opacity: 1; }
|
|
439
|
+
50% { transform: scale(1.25); opacity: 0.6; }
|
|
440
|
+
100% { transform: scale(1); opacity: 1; }
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
@keyframes float {
|
|
444
|
+
0% { transform: translateY(0px); }
|
|
445
|
+
50% { transform: translateY(-8px); }
|
|
446
|
+
100% { transform: translateY(0px); }
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
@keyframes slideIn {
|
|
450
|
+
from { transform: translateY(15px); opacity: 0; }
|
|
451
|
+
to { transform: translateY(0); opacity: 1; }
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
@keyframes fadeIn {
|
|
455
|
+
from { opacity: 0; }
|
|
456
|
+
to { opacity: 1; }
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
@keyframes scaleUp {
|
|
460
|
+
from { transform: scale(0.9); opacity: 0; }
|
|
461
|
+
to { transform: scale(1); opacity: 1; }
|
|
462
|
+
}
|
|
463
|
+
</style>
|
|
464
|
+
</head>
|
|
465
|
+
<body>
|
|
466
|
+
|
|
467
|
+
<!-- Mobile Screen Emulator -->
|
|
468
|
+
<div class="mobile-frame">
|
|
469
|
+
|
|
470
|
+
<!-- Top Header -->
|
|
471
|
+
<div class="header">
|
|
472
|
+
<div class="brand">n2world</div>
|
|
473
|
+
<div class="status-badge">
|
|
474
|
+
<div class="status-dot"></div>
|
|
475
|
+
<span id="status-text">대기 중</span>
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
|
|
479
|
+
<!-- Avatar Robot Mascot -->
|
|
480
|
+
<div class="mascot-container">
|
|
481
|
+
<div class="avatar-wrapper idle" id="avatar-container">
|
|
482
|
+
<!-- Smiling Robot Image -->
|
|
483
|
+
<img src="freebie.png" alt="Mascot Character">
|
|
484
|
+
</div>
|
|
485
|
+
<div class="mascot-name">에이전트 에단 (Ethan)</div>
|
|
486
|
+
<div class="mascot-sub" id="mascot-sub-text">안녕하세요! 무엇을 도와드릴까요?</div>
|
|
487
|
+
</div>
|
|
488
|
+
|
|
489
|
+
<!-- Circular Stats Rings -->
|
|
490
|
+
<div class="stats-row">
|
|
491
|
+
<!-- Energy Saved Ring -->
|
|
492
|
+
<div class="stat-card">
|
|
493
|
+
<svg class="stat-svg">
|
|
494
|
+
<circle class="bg-circle" cx="30" cy="30" r="25" />
|
|
495
|
+
<circle class="val-circle" cx="30" cy="30" r="25" stroke="#66bb6a" stroke-dasharray="157" stroke-dashoffset="157" id="ring-energy" />
|
|
496
|
+
</svg>
|
|
497
|
+
<span class="stat-number" id="val-energy">0%</span>
|
|
498
|
+
<span class="stat-label">에너지 절약</span>
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
<!-- Tasks Done Ring -->
|
|
502
|
+
<div class="stat-card">
|
|
503
|
+
<svg class="stat-svg">
|
|
504
|
+
<circle class="bg-circle" cx="30" cy="30" r="25" />
|
|
505
|
+
<circle class="val-circle" cx="30" cy="30" r="25" stroke="#ffb74d" stroke-dasharray="157" stroke-dashoffset="157" id="ring-tasks" />
|
|
506
|
+
</svg>
|
|
507
|
+
<span class="stat-number" id="val-tasks">0</span>
|
|
508
|
+
<span class="stat-label">수행 미션</span>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
<!-- Memory Free Ring -->
|
|
512
|
+
<div class="stat-card">
|
|
513
|
+
<svg class="stat-svg">
|
|
514
|
+
<circle class="bg-circle" cx="30" cy="30" r="25" />
|
|
515
|
+
<circle class="val-circle" cx="30" cy="30" r="25" stroke="#42a5f5" stroke-dasharray="157" stroke-dashoffset="157" id="ring-memory" />
|
|
516
|
+
</svg>
|
|
517
|
+
<span class="stat-number" id="val-memory">0.1M</span>
|
|
518
|
+
<span class="stat-label">대기 메모리</span>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
<!-- Thought Conversation Logs -->
|
|
523
|
+
<div class="thought-console" id="console">
|
|
524
|
+
<div class="thought-bubble bubble-agent">
|
|
525
|
+
반가워요! 저는 n2world 에이전트 에단이에요. 🤖 모노레포 빌드, 자원 조율, 또는 샌드박스 컴파일 등 원하는 명령을 내려주시면 즉시 탐색할게요!
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
|
|
529
|
+
<!-- Footer Input Bar -->
|
|
530
|
+
<div class="control-footer">
|
|
531
|
+
<button class="mic-btn" id="btn-mic" title="음성 인식">
|
|
532
|
+
<svg viewBox="0 0 24 24">
|
|
533
|
+
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" />
|
|
534
|
+
</svg>
|
|
535
|
+
</button>
|
|
536
|
+
<input type="text" class="input-box" placeholder="에이전트에게 명령하기..." id="input-task">
|
|
537
|
+
<button class="action-btn" id="btn-submit">
|
|
538
|
+
<svg viewBox="0 0 24 24">
|
|
539
|
+
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
|
|
540
|
+
</svg>
|
|
541
|
+
</button>
|
|
542
|
+
</div>
|
|
543
|
+
|
|
544
|
+
<!-- Safe Approval Modal overlay -->
|
|
545
|
+
<div class="popup-overlay" id="approval-popup">
|
|
546
|
+
<div class="popup-card">
|
|
547
|
+
<div class="popup-title">잠깐! 승인이 필요해요 🖐️</div>
|
|
548
|
+
<div class="popup-desc" id="popup-desc-text">에이전트 에단이 "/workspace/build.ts" 파일을 수정하려고 승인을 요청했습니다. 안전합니까?</div>
|
|
549
|
+
<div class="popup-btn-row">
|
|
550
|
+
<button class="popup-btn btn-cancel" id="btn-popup-cancel">거절</button>
|
|
551
|
+
<button class="popup-btn btn-approve" id="btn-popup-approve">허가</button>
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
|
|
556
|
+
</div>
|
|
557
|
+
|
|
558
|
+
<script>
|
|
559
|
+
// Register Service Worker for PWA
|
|
560
|
+
if ('serviceWorker' in navigator) {
|
|
561
|
+
window.addEventListener('load', () => {
|
|
562
|
+
navigator.serviceWorker.register('/service-worker.js').catch(() => {});
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const ringEnergy = document.getElementById('ring-energy');
|
|
567
|
+
const ringTasks = document.getElementById('ring-tasks');
|
|
568
|
+
const ringMemory = document.getElementById('ring-memory');
|
|
569
|
+
|
|
570
|
+
const valEnergy = document.getElementById('val-energy');
|
|
571
|
+
const valTasks = document.getElementById('val-tasks');
|
|
572
|
+
const valMemory = document.getElementById('val-memory');
|
|
573
|
+
|
|
574
|
+
const inputTask = document.getElementById('input-task');
|
|
575
|
+
const btnSubmit = document.getElementById('btn-submit');
|
|
576
|
+
const btnMic = document.getElementById('btn-mic');
|
|
577
|
+
const consoleDiv = document.getElementById('console');
|
|
578
|
+
const statusText = document.getElementById('status-text');
|
|
579
|
+
const mascotSubText = document.getElementById('mascot-sub-text');
|
|
580
|
+
const avatarContainer = document.getElementById('avatar-container');
|
|
581
|
+
|
|
582
|
+
const approvalPopup = document.getElementById('approval-popup');
|
|
583
|
+
const popupDescText = document.getElementById('popup-desc-text');
|
|
584
|
+
const btnPopupCancel = document.getElementById('btn-popup-cancel');
|
|
585
|
+
const btnPopupApprove = document.getElementById('btn-popup-approve');
|
|
586
|
+
|
|
587
|
+
let missionCount = 0;
|
|
588
|
+
let currentTask = '';
|
|
589
|
+
|
|
590
|
+
// Set SVG progress rings offset
|
|
591
|
+
function setProgress(circle, percent) {
|
|
592
|
+
const radius = circle.r.baseVal.value;
|
|
593
|
+
const circumference = 2 * Math.PI * radius;
|
|
594
|
+
const offset = circumference - (percent / 100) * circumference;
|
|
595
|
+
circle.style.strokeDashoffset = offset;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
let isLocalDemoMode = !navigator.onLine;
|
|
599
|
+
|
|
600
|
+
function updateStatusToOfflineDemo() {
|
|
601
|
+
statusText.textContent = "오프라인 데모";
|
|
602
|
+
const badge = document.querySelector('.status-badge');
|
|
603
|
+
const dot = document.querySelector('.status-dot');
|
|
604
|
+
badge.style.background = '#eceff1';
|
|
605
|
+
badge.style.color = '#546e7a';
|
|
606
|
+
dot.style.background = '#546e7a';
|
|
607
|
+
mascotSubText.textContent = "오프라인 데모 모드로 작동 중입니다. 🤖";
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function updateStatusToOnline() {
|
|
611
|
+
statusText.textContent = "대기 중";
|
|
612
|
+
const badge = document.querySelector('.status-badge');
|
|
613
|
+
const dot = document.querySelector('.status-dot');
|
|
614
|
+
badge.style.background = 'var(--pastel-green)';
|
|
615
|
+
badge.style.color = 'var(--accent-green)';
|
|
616
|
+
dot.style.background = 'var(--accent-green)';
|
|
617
|
+
mascotSubText.textContent = "안녕하세요! 무엇을 도와드릴까요?";
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
let fallbackInterval = null;
|
|
621
|
+
function startLocalTelemetrySimulation() {
|
|
622
|
+
if (fallbackInterval) return;
|
|
623
|
+
fallbackInterval = setInterval(() => {
|
|
624
|
+
const mockEnergy = (99.8 + (Math.random() * 0.1 - 0.05)).toFixed(1);
|
|
625
|
+
const mockMemory = (0.1 + (Math.random() * 0.05)).toFixed(3) + 'MB';
|
|
626
|
+
|
|
627
|
+
setProgress(ringEnergy, parseFloat(mockEnergy));
|
|
628
|
+
valEnergy.textContent = mockEnergy + '%';
|
|
629
|
+
|
|
630
|
+
const memVal = parseFloat(mockMemory);
|
|
631
|
+
const memPercent = Math.min(100, (memVal / 0.15) * 100);
|
|
632
|
+
setProgress(ringMemory, memPercent);
|
|
633
|
+
valMemory.textContent = mockMemory;
|
|
634
|
+
}, 1000);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function stopLocalTelemetrySimulation() {
|
|
638
|
+
if (fallbackInterval) {
|
|
639
|
+
clearInterval(fallbackInterval);
|
|
640
|
+
fallbackInterval = null;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Dynamic Telemetry SSE Binding with fallback
|
|
645
|
+
let eventSource = null;
|
|
646
|
+
function connectTelemetry() {
|
|
647
|
+
if (eventSource) {
|
|
648
|
+
eventSource.close();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (!navigator.onLine) {
|
|
652
|
+
isLocalDemoMode = true;
|
|
653
|
+
updateStatusToOfflineDemo();
|
|
654
|
+
startLocalTelemetrySimulation();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
eventSource = new EventSource('/api/telemetry');
|
|
659
|
+
|
|
660
|
+
eventSource.onmessage = (e) => {
|
|
661
|
+
try {
|
|
662
|
+
const data = JSON.parse(e.data);
|
|
663
|
+
setProgress(ringEnergy, parseFloat(data.energy));
|
|
664
|
+
valEnergy.textContent = data.energy + '%';
|
|
665
|
+
const memVal = parseFloat(data.memory);
|
|
666
|
+
const memPercent = Math.min(100, (memVal / 0.15) * 100);
|
|
667
|
+
setProgress(ringMemory, memPercent);
|
|
668
|
+
valMemory.textContent = data.memory;
|
|
669
|
+
|
|
670
|
+
if (statusText.textContent === "오프라인 데모") {
|
|
671
|
+
isLocalDemoMode = false;
|
|
672
|
+
updateStatusToOnline();
|
|
673
|
+
stopLocalTelemetrySimulation();
|
|
674
|
+
}
|
|
675
|
+
} catch (err) {}
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
eventSource.onerror = () => {
|
|
679
|
+
isLocalDemoMode = true;
|
|
680
|
+
updateStatusToOfflineDemo();
|
|
681
|
+
startLocalTelemetrySimulation();
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Connect initially
|
|
686
|
+
connectTelemetry();
|
|
687
|
+
|
|
688
|
+
window.addEventListener('online', () => {
|
|
689
|
+
isLocalDemoMode = false;
|
|
690
|
+
updateStatusToOnline();
|
|
691
|
+
stopLocalTelemetrySimulation();
|
|
692
|
+
connectTelemetry();
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
window.addEventListener('offline', () => {
|
|
696
|
+
isLocalDemoMode = true;
|
|
697
|
+
updateStatusToOfflineDemo();
|
|
698
|
+
startLocalTelemetrySimulation();
|
|
699
|
+
if (eventSource) {
|
|
700
|
+
eventSource.close();
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// Native Web Speech Recognition (STT)
|
|
705
|
+
// ⚠️ 제공자 종속 고지: 일부 브라우저(Chrome의 webkitSpeechRecognition)는
|
|
706
|
+
// 음성 오디오를 Google 음성 서버로 전송해 인식한다. 제공자 중립/오프라인이
|
|
707
|
+
// 필요하면 온디바이스 STT 또는 자체 STT 엔드포인트로 교체할 것.
|
|
708
|
+
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
709
|
+
if (SpeechRecognition) {
|
|
710
|
+
const recognition = new SpeechRecognition();
|
|
711
|
+
recognition.lang = 'ko-KR';
|
|
712
|
+
recognition.interimResults = false;
|
|
713
|
+
|
|
714
|
+
recognition.onstart = () => {
|
|
715
|
+
btnMic.classList.add('recording');
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
recognition.onend = () => {
|
|
719
|
+
btnMic.classList.remove('recording');
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
recognition.onresult = (event) => {
|
|
723
|
+
const transcript = event.results[0][0].transcript;
|
|
724
|
+
inputTask.value = transcript;
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// F2: 명시적 동의 게이팅 — Web Speech(webkit)는 음성을 Google 서버로 전송할 수 있다.
|
|
728
|
+
// 첫 사용 시 사용자에게 고지·동의를 받고, 동의를 1회 저장한다(거부 시 마이크 미작동).
|
|
729
|
+
btnMic.addEventListener('click', () => {
|
|
730
|
+
let consent = false;
|
|
731
|
+
try { consent = localStorage.getItem('n2w_stt_consent') === 'yes'; } catch (e) {}
|
|
732
|
+
if (!consent) {
|
|
733
|
+
const ok = window.confirm(
|
|
734
|
+
'음성 인식 안내: 이 브라우저의 음성 인식(Web Speech)은 마이크 오디오를 ' +
|
|
735
|
+
'인식을 위해 외부 서버(예: Google)로 전송할 수 있습니다.\n\n동의하고 계속할까요?\n' +
|
|
736
|
+
'(거부 시 텍스트 입력을 사용하세요.)'
|
|
737
|
+
);
|
|
738
|
+
if (!ok) return;
|
|
739
|
+
try { localStorage.setItem('n2w_stt_consent', 'yes'); } catch (e) {}
|
|
740
|
+
}
|
|
741
|
+
recognition.start();
|
|
742
|
+
});
|
|
743
|
+
} else {
|
|
744
|
+
btnMic.style.display = 'none';
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Click submit
|
|
748
|
+
btnSubmit.addEventListener('click', handleTaskSubmit);
|
|
749
|
+
inputTask.addEventListener('keypress', (e) => {
|
|
750
|
+
if (e.key === 'Enter') handleTaskSubmit();
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
function handleTaskSubmit() {
|
|
754
|
+
const task = inputTask.value.trim();
|
|
755
|
+
if (!task) return;
|
|
756
|
+
|
|
757
|
+
// Append user bubble
|
|
758
|
+
appendBubble(task, 'user');
|
|
759
|
+
inputTask.value = '';
|
|
760
|
+
|
|
761
|
+
// Transition mascot status & animation to running
|
|
762
|
+
avatarContainer.className = 'avatar-wrapper thinking';
|
|
763
|
+
statusText.textContent = isLocalDemoMode ? "데모 실행 중" : "실행 중";
|
|
764
|
+
document.querySelector('.status-badge').style.background = 'var(--pastel-orange)';
|
|
765
|
+
document.querySelector('.status-badge').style.color = 'var(--accent-orange)';
|
|
766
|
+
document.querySelector('.status-dot').style.background = 'var(--accent-orange)';
|
|
767
|
+
mascotSubText.textContent = isLocalDemoMode ? "오프라인 시뮬레이션 명령을 처리하는 중... ⚙️" : "명령을 수행하는 중입니다... ⚙️";
|
|
768
|
+
|
|
769
|
+
if (isLocalDemoMode) {
|
|
770
|
+
setTimeout(() => {
|
|
771
|
+
let output = '';
|
|
772
|
+
const normalizedCmd = task.trim();
|
|
773
|
+
|
|
774
|
+
if (normalizedCmd.includes('파일') || normalizedCmd.includes('목록') || normalizedCmd.includes('dir') || normalizedCmd.includes('ls')) {
|
|
775
|
+
output = `[오프라인 데모 실행 결과]\nVolume in drive C has no label.\nDirectory of C:\\Users\\suhop\\ebook\\N2World\\packages\\orchestrator\\sandbox\n\n2026-06-14 11:00 <DIR> .\n2026-06-14 11:00 <DIR> ..\n2026-06-14 11:00 444 package.json\n2026-06-14 11:00 40 .env\n2026-06-14 11:00 1,024 EthanMascot.png\n\n* 실제 서버 연결이 감지되지 않아 오프라인 시뮬레이션 목록이 출력되었습니다.`;
|
|
776
|
+
} else {
|
|
777
|
+
output = `[오프라인 데모]\n입력하신 명령어 "${task}"를 시뮬레이션 샌드박스 내부에서 안전하게 모의 실행 완료했습니다.\n\n* 팁: 실제 노트북/서버의 와이파이 네트워크가 연결되면 노트북의 격리 터미널을 통해 실시간 자연어 명령어 처리가 활성화됩니다.`;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
missionCount++;
|
|
781
|
+
setProgress(ringTasks, Math.min(100, missionCount * 20));
|
|
782
|
+
valTasks.textContent = missionCount.toString();
|
|
783
|
+
appendOutputBubble(`명령 실행 완료! 🎉\n\n${output}`, 'success');
|
|
784
|
+
|
|
785
|
+
if (navigator.vibrate) {
|
|
786
|
+
navigator.vibrate(50);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
avatarContainer.className = 'avatar-wrapper idle';
|
|
790
|
+
updateStatusToOfflineDemo();
|
|
791
|
+
}, 1200);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
fetch('/api/run', {
|
|
796
|
+
method: 'POST',
|
|
797
|
+
headers: {
|
|
798
|
+
'Content-Type': 'application/json'
|
|
799
|
+
},
|
|
800
|
+
body: JSON.stringify({ command: task })
|
|
801
|
+
})
|
|
802
|
+
.then(res => res.json())
|
|
803
|
+
.then(data => {
|
|
804
|
+
if (data.error) {
|
|
805
|
+
appendBubble(`명령 실행 실패: ${data.error} 🚫`, 'agent');
|
|
806
|
+
} else {
|
|
807
|
+
let output = '';
|
|
808
|
+
if (data.stdout && data.stdout.trim()) {
|
|
809
|
+
output += `[출력]\n${data.stdout.trim()}`;
|
|
810
|
+
}
|
|
811
|
+
if (data.stderr && data.stderr.trim()) {
|
|
812
|
+
if (output) output += '\n';
|
|
813
|
+
output += `[에러]\n${data.stderr.trim()}`;
|
|
814
|
+
}
|
|
815
|
+
if (!output) {
|
|
816
|
+
output = '명령이 출력 없이 완료되었습니다.';
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Consolidated Single Reply Bubble
|
|
820
|
+
if (data.success) {
|
|
821
|
+
missionCount++;
|
|
822
|
+
setProgress(ringTasks, Math.min(100, missionCount * 20));
|
|
823
|
+
valTasks.textContent = missionCount.toString();
|
|
824
|
+
appendOutputBubble(`명령 실행 완료! 🎉\n\n${output}`, 'success');
|
|
825
|
+
if (navigator.vibrate) {
|
|
826
|
+
navigator.vibrate(50); // subtle success haptic
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
appendOutputBubble(`명령 실행 실패! ⚠️\n\n${output}`, 'error');
|
|
830
|
+
if (navigator.vibrate) {
|
|
831
|
+
navigator.vibrate([100, 50, 100]); // warning haptic
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Restore status & animation
|
|
837
|
+
avatarContainer.className = 'avatar-wrapper idle';
|
|
838
|
+
updateStatusToOnline();
|
|
839
|
+
})
|
|
840
|
+
.catch(err => {
|
|
841
|
+
isLocalDemoMode = true;
|
|
842
|
+
updateStatusToOfflineDemo();
|
|
843
|
+
startLocalTelemetrySimulation();
|
|
844
|
+
|
|
845
|
+
appendBubble(`네트워크 오류로 인해 오프라인 데모 모드로 전환되었습니다. 🚫`, 'agent');
|
|
846
|
+
|
|
847
|
+
setTimeout(() => {
|
|
848
|
+
let output = `[오프라인 데모]\n입력하신 명령어 "${task}"를 모의 실행 완료했습니다.\n\n* 실제 서버 연결 실패: ${err.message}`;
|
|
849
|
+
missionCount++;
|
|
850
|
+
setProgress(ringTasks, Math.min(100, missionCount * 20));
|
|
851
|
+
valTasks.textContent = missionCount.toString();
|
|
852
|
+
appendOutputBubble(`명령 실행 완료! 🎉\n\n${output}`, 'success');
|
|
853
|
+
|
|
854
|
+
if (navigator.vibrate) {
|
|
855
|
+
navigator.vibrate(50);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
avatarContainer.className = 'avatar-wrapper idle';
|
|
859
|
+
}, 1200);
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function appendBubble(text, sender) {
|
|
864
|
+
const bubble = document.createElement('div');
|
|
865
|
+
bubble.className = `thought-bubble bubble-${sender}`;
|
|
866
|
+
bubble.textContent = text;
|
|
867
|
+
consoleDiv.appendChild(bubble);
|
|
868
|
+
|
|
869
|
+
// Scroll to bottom
|
|
870
|
+
consoleDiv.scrollTop = consoleDiv.scrollHeight;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function appendOutputBubble(text, type) {
|
|
874
|
+
const bubble = document.createElement('div');
|
|
875
|
+
bubble.className = `thought-bubble bubble-agent output-bubble ${type}`;
|
|
876
|
+
bubble.style.whiteSpace = 'pre-wrap';
|
|
877
|
+
bubble.style.fontFamily = 'monospace';
|
|
878
|
+
bubble.style.fontSize = '0.85rem';
|
|
879
|
+
bubble.style.backgroundColor = type === 'success' ? '#f1f8e9' : '#ffebee';
|
|
880
|
+
bubble.style.borderLeft = type === 'success' ? '4px solid #8bc34a' : '4px solid #e57373';
|
|
881
|
+
bubble.style.color = '#333';
|
|
882
|
+
bubble.style.padding = '10px';
|
|
883
|
+
bubble.style.borderRadius = '8px';
|
|
884
|
+
bubble.style.marginTop = '8px';
|
|
885
|
+
bubble.style.marginBottom = '8px';
|
|
886
|
+
bubble.textContent = text;
|
|
887
|
+
consoleDiv.appendChild(bubble);
|
|
888
|
+
consoleDiv.scrollTop = consoleDiv.scrollHeight;
|
|
889
|
+
}
|
|
890
|
+
</script>
|
|
891
|
+
</body>
|
|
892
|
+
</html>
|