ltcai 0.1.25 → 0.1.27

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/static/chat.html CHANGED
@@ -534,6 +534,44 @@
534
534
  }
535
535
  .mcp-install-btn:hover { background: rgba(34,211,160,0.15); }
536
536
  .mcp-install-btn.installed { border-color: rgba(255,255,255,0.1); background: rgba(255,255,255,0.04); color: var(--faint); }
537
+ /* MCP 소스 배지 */
538
+ .mcp-source-badge {
539
+ font-size: 9.5px; font-weight: 700; padding: 2px 6px; border-radius: 4px;
540
+ text-transform: uppercase; letter-spacing: .05em; flex-shrink: 0;
541
+ }
542
+ .mcp-source-badge.claude-code { background: rgba(100,180,255,0.12); color: #64b4ff; border: 1px solid rgba(100,180,255,0.2); }
543
+ .mcp-source-badge.custom { background: rgba(255,180,60,0.12); color: #ffb43c; border: 1px solid rgba(255,180,60,0.2); }
544
+ /* MCP 탭 */
545
+ .mcp-tabs { display: flex; gap: 4px; padding: 12px 20px 0; border-bottom: 1px solid rgba(255,255,255,0.07); }
546
+ .mcp-tab {
547
+ padding: 7px 14px; border-radius: 8px 8px 0 0; font-size: 12px; font-weight: 600;
548
+ color: var(--faint); cursor: pointer; border: none; background: none;
549
+ border-bottom: 2px solid transparent; transition: all .15s;
550
+ }
551
+ .mcp-tab.active { color: var(--accent); border-bottom-color: var(--accent); background: rgba(34,211,160,0.06); }
552
+ .mcp-tab:hover:not(.active) { color: var(--text); background: rgba(255,255,255,0.04); }
553
+ /* Custom MCP 추가 폼 */
554
+ .mcp-add-form { display: flex; flex-direction: column; gap: 10px; padding: 4px 0; }
555
+ .mcp-add-form label { font-size: 11px; font-weight: 600; color: var(--faint); text-transform: uppercase; letter-spacing: .06em; }
556
+ .mcp-add-form input, .mcp-add-form textarea, .mcp-add-form select {
557
+ width: 100%; padding: 8px 11px; border-radius: 8px; font-size: 13px;
558
+ background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
559
+ color: var(--text); outline: none; transition: border .15s; box-sizing: border-box;
560
+ }
561
+ .mcp-add-form input:focus, .mcp-add-form textarea:focus { border-color: rgba(34,211,160,0.4); }
562
+ .mcp-add-form .field-hint { font-size: 10.5px; color: var(--faint); margin-top: -6px; }
563
+ .mcp-submit-btn {
564
+ padding: 9px 18px; border-radius: 8px; font-size: 13px; font-weight: 700;
565
+ background: rgba(34,211,160,0.12); border: 1px solid rgba(34,211,160,0.3);
566
+ color: var(--accent); cursor: pointer; transition: all .15s; align-self: flex-end;
567
+ }
568
+ .mcp-submit-btn:hover { background: rgba(34,211,160,0.22); }
569
+ .mcp-delete-btn {
570
+ padding: 5px 10px; border-radius: 6px; font-size: 11px; font-weight: 600;
571
+ background: rgba(255,80,80,0.08); border: 1px solid rgba(255,80,80,0.2);
572
+ color: #ff6b6b; cursor: pointer; transition: all .15s; flex-shrink: 0;
573
+ }
574
+ .mcp-delete-btn:hover { background: rgba(255,80,80,0.18); }
537
575
  .acct-modal {
538
576
  background: var(--surface, #1e293b);
539
577
  border: 1px solid rgba(255,255,255,0.08);
@@ -3028,10 +3066,331 @@
3028
3066
  background: #ffffff;
3029
3067
  box-shadow: 0 10px 24px rgba(0,0,0,0.28);
3030
3068
  }
3069
+
3070
+ /* PPT reference skin: light workspace, indigo navigation, modal-first flow. */
3071
+ :root {
3072
+ --bg: #f8f6ff;
3073
+ --surface: #ffffff;
3074
+ --surface-2: #f3f0ff;
3075
+ --surface-3: #ece7ff;
3076
+ --accent: #6f4bf6;
3077
+ --accent-2: #ffb84d;
3078
+ --accent-3: #7868ff;
3079
+ --accent-soft: rgba(111,75,246,0.10);
3080
+ --danger: #ef4444;
3081
+ --text: #24223d;
3082
+ --muted: #625f7c;
3083
+ --faint: #9690b2;
3084
+ --border: rgba(111,75,246,0.13);
3085
+ --border-strong: rgba(111,75,246,0.24);
3086
+ --shadow: 0 24px 68px rgba(86,70,160,0.16);
3087
+ --shadow-sm: 0 10px 28px rgba(86,70,160,0.10);
3088
+ }
3089
+ body::before {
3090
+ background:
3091
+ radial-gradient(circle at 76% 14%, rgba(111,75,246,0.12), transparent 30%),
3092
+ radial-gradient(circle at 8% 80%, rgba(196,181,253,0.26), transparent 35%),
3093
+ linear-gradient(180deg, #fbfaff 0%, #f6f2ff 100%);
3094
+ }
3095
+ .bg-grid {
3096
+ opacity: 0.28;
3097
+ background-image:
3098
+ radial-gradient(circle, rgba(111,75,246,0.28) 1px, transparent 1.8px),
3099
+ linear-gradient(rgba(111,75,246,0.06) 1px, transparent 1px),
3100
+ linear-gradient(90deg, rgba(111,75,246,0.05) 1px, transparent 1px);
3101
+ background-size: 84px 84px, 48px 48px, 48px 48px;
3102
+ mask-image: linear-gradient(180deg, rgba(0,0,0,0.24), rgba(0,0,0,0.04));
3103
+ }
3104
+ .app-layout::after {
3105
+ background:
3106
+ linear-gradient(115deg, transparent 0 40%, rgba(111,75,246,0.08) 40.15%, transparent 40.4% 100%),
3107
+ radial-gradient(ellipse at 5% 88%, rgba(111,75,246,0.16), transparent 35%);
3108
+ }
3109
+ .app-layout .sidebar {
3110
+ background: rgba(255,255,255,0.88);
3111
+ border-right-color: rgba(111,75,246,0.12);
3112
+ box-shadow: 12px 0 46px rgba(86,70,160,0.08);
3113
+ }
3114
+ .app-layout .sidebar-header,
3115
+ .app-layout .user-strip,
3116
+ .app-layout .sidebar-footer,
3117
+ .app-layout .sidebar-primary-actions,
3118
+ .app-layout .chat-header {
3119
+ border-color: rgba(111,75,246,0.10);
3120
+ }
3121
+ .app-layout .logo-box,
3122
+ .app-layout .user-avatar {
3123
+ background: linear-gradient(135deg, #6f4bf6, #9b87ff);
3124
+ color: #fff;
3125
+ box-shadow: 0 14px 28px rgba(111,75,246,0.22);
3126
+ }
3127
+ .app-layout .brand-title,
3128
+ .app-layout .empty-state h1 {
3129
+ color: #24223d;
3130
+ }
3131
+ .app-layout .brand-subtitle,
3132
+ .app-layout .user-strip,
3133
+ .app-layout .history-item,
3134
+ .app-layout .logout-btn {
3135
+ color: var(--muted);
3136
+ }
3137
+ .app-layout .sidebar-search input,
3138
+ .app-layout .mcp-add-form input,
3139
+ .app-layout .mcp-add-form textarea,
3140
+ .app-layout .mcp-add-form select,
3141
+ .app-layout .pw-field input {
3142
+ background: #fbfaff;
3143
+ border-color: rgba(111,75,246,0.12);
3144
+ color: var(--text);
3145
+ }
3146
+ .app-layout .new-chat-btn,
3147
+ .app-layout .status-btn,
3148
+ .app-layout .setup-wizard-sidebar-btn,
3149
+ .app-layout .admin-btn {
3150
+ background: #fff;
3151
+ border-color: rgba(111,75,246,0.13);
3152
+ color: var(--text);
3153
+ box-shadow: 0 8px 22px rgba(86,70,160,0.06);
3154
+ }
3155
+ .app-layout .new-chat-btn:hover,
3156
+ .app-layout .status-btn:hover,
3157
+ .app-layout .setup-wizard-sidebar-btn:hover,
3158
+ .app-layout .admin-btn:hover,
3159
+ .app-layout .history-item:hover,
3160
+ .app-layout .history-item.active {
3161
+ background: #f1ecff;
3162
+ border-color: rgba(111,75,246,0.25);
3163
+ color: var(--accent);
3164
+ }
3165
+ .app-layout .chat-header {
3166
+ background: rgba(255,255,255,0.78);
3167
+ box-shadow: 0 8px 28px rgba(86,70,160,0.08);
3168
+ }
3169
+ .app-layout .model-badge,
3170
+ .app-layout .status-pill,
3171
+ .app-layout .logout-btn {
3172
+ background: rgba(255,255,255,0.78);
3173
+ border-color: rgba(111,75,246,0.14);
3174
+ color: var(--muted);
3175
+ }
3176
+ .app-layout .model-badge {
3177
+ color: var(--text);
3178
+ }
3179
+ .app-layout .status-dot {
3180
+ background: var(--accent);
3181
+ box-shadow: 0 0 0 4px rgba(111,75,246,0.10), 0 0 18px rgba(111,75,246,0.30);
3182
+ }
3183
+ .app-layout .ops-card,
3184
+ .app-layout .empty-chip,
3185
+ .app-layout .ai .bubble,
3186
+ .app-layout .acct-modal,
3187
+ .app-layout .mcp-modal {
3188
+ background: rgba(255,255,255,0.86);
3189
+ border-color: rgba(111,75,246,0.13);
3190
+ box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255,255,255,0.9);
3191
+ }
3192
+ .app-layout .ops-card.primary {
3193
+ background: linear-gradient(135deg, rgba(111,75,246,0.12), rgba(255,255,255,0.92) 62%);
3194
+ border-color: rgba(111,75,246,0.23);
3195
+ }
3196
+ .app-layout .ops-card.interactive:hover,
3197
+ .app-layout .empty-chip:hover {
3198
+ background: #f3efff;
3199
+ border-color: rgba(111,75,246,0.30);
3200
+ box-shadow: 0 18px 42px rgba(86,70,160,0.16);
3201
+ }
3202
+ .app-layout .ops-card.primary .ops-icon,
3203
+ .app-layout .ops-card:not(.primary) .ops-icon,
3204
+ .app-layout .empty-state > div[style] {
3205
+ background: linear-gradient(135deg, rgba(111,75,246,0.14), rgba(155,135,255,0.12)) !important;
3206
+ border-color: rgba(111,75,246,0.18) !important;
3207
+ color: var(--accent) !important;
3208
+ }
3209
+ .app-layout .messages-viewport {
3210
+ background: transparent;
3211
+ }
3212
+ .app-layout .user .bubble {
3213
+ background: linear-gradient(135deg, #6f4bf6, #7366ff);
3214
+ color: #fff;
3215
+ border-color: rgba(111,75,246,0.28);
3216
+ box-shadow: 0 14px 32px rgba(111,75,246,0.22);
3217
+ }
3218
+ .app-layout .input-area {
3219
+ background: linear-gradient(0deg, rgba(248,246,255,0.98) 0%, rgba(248,246,255,0.76) 64%, transparent 100%);
3220
+ }
3221
+ .app-layout .input-box {
3222
+ background: rgba(255,255,255,0.92);
3223
+ border-color: rgba(111,75,246,0.16);
3224
+ box-shadow: 0 18px 54px rgba(86,70,160,0.15);
3225
+ }
3226
+ .app-layout textarea {
3227
+ color: var(--text);
3228
+ }
3229
+ .app-layout .send-btn {
3230
+ background: linear-gradient(135deg, #6f4bf6, #796cff);
3231
+ color: #fff;
3232
+ box-shadow: 0 14px 30px rgba(111,75,246,0.24);
3233
+ }
3234
+ .workspace-modal-overlay,
3235
+ .mode-modal-overlay {
3236
+ display: none;
3237
+ position: fixed;
3238
+ inset: 0;
3239
+ z-index: 1400;
3240
+ align-items: center;
3241
+ justify-content: center;
3242
+ padding: 24px;
3243
+ background: rgba(25, 21, 47, 0.24);
3244
+ backdrop-filter: blur(14px);
3245
+ }
3246
+ .workspace-modal-overlay.open,
3247
+ .mode-modal-overlay.open {
3248
+ display: flex;
3249
+ }
3250
+ .workspace-modal,
3251
+ .mode-modal {
3252
+ width: min(620px, 100%);
3253
+ border-radius: 20px;
3254
+ background:
3255
+ linear-gradient(180deg, rgba(255,255,255,0.98), rgba(250,248,255,0.94));
3256
+ border: 1px solid rgba(111,75,246,0.16);
3257
+ box-shadow: 0 30px 90px rgba(60,45,130,0.24);
3258
+ padding: 30px;
3259
+ color: var(--text);
3260
+ position: relative;
3261
+ }
3262
+ .mode-modal {
3263
+ width: min(520px, 100%);
3264
+ background:
3265
+ radial-gradient(circle at 18% 8%, rgba(111,75,246,0.28), transparent 32%),
3266
+ linear-gradient(180deg, #211b3b, #151329);
3267
+ color: #fff;
3268
+ border-color: rgba(255,255,255,0.12);
3269
+ }
3270
+ .modal-kicker {
3271
+ color: var(--accent);
3272
+ font-size: 11px;
3273
+ font-weight: 800;
3274
+ letter-spacing: .08em;
3275
+ text-transform: uppercase;
3276
+ text-align: center;
3277
+ margin-bottom: 8px;
3278
+ }
3279
+ .mode-modal .modal-kicker { color: #b9aaff; }
3280
+ .workspace-modal h2,
3281
+ .mode-modal h2 {
3282
+ text-align: center;
3283
+ font-size: 24px;
3284
+ margin-bottom: 7px;
3285
+ }
3286
+ .workspace-modal p,
3287
+ .mode-modal p {
3288
+ text-align: center;
3289
+ color: var(--muted);
3290
+ font-size: 13px;
3291
+ line-height: 1.6;
3292
+ margin-bottom: 22px;
3293
+ }
3294
+ .mode-modal p { color: #b8b2d4; }
3295
+ .workspace-options,
3296
+ .mode-options {
3297
+ display: grid;
3298
+ grid-template-columns: repeat(2, minmax(0, 1fr));
3299
+ gap: 14px;
3300
+ }
3301
+ .workspace-card,
3302
+ .mode-card {
3303
+ border-radius: 16px;
3304
+ border: 1px solid rgba(111,75,246,0.14);
3305
+ background: #fff;
3306
+ padding: 22px 18px;
3307
+ cursor: pointer;
3308
+ text-align: center;
3309
+ transition: transform .18s, box-shadow .18s, border-color .18s;
3310
+ }
3311
+ .workspace-card:hover,
3312
+ .workspace-card.selected,
3313
+ .mode-card:hover,
3314
+ .mode-card.selected {
3315
+ transform: translateY(-3px);
3316
+ border-color: rgba(111,75,246,0.42);
3317
+ box-shadow: 0 18px 44px rgba(86,70,160,0.18);
3318
+ }
3319
+ .mode-card {
3320
+ background: rgba(255,255,255,0.08);
3321
+ border-color: rgba(255,255,255,0.12);
3322
+ color: #fff;
3323
+ position: relative;
3324
+ }
3325
+ .mode-card.selected::after {
3326
+ content: '✓';
3327
+ position: absolute;
3328
+ top: 12px;
3329
+ right: 12px;
3330
+ width: 20px;
3331
+ height: 20px;
3332
+ border-radius: 50%;
3333
+ background: #7b6dff;
3334
+ display: grid;
3335
+ place-items: center;
3336
+ font-size: 12px;
3337
+ }
3338
+ .workspace-icon,
3339
+ .mode-icon {
3340
+ width: 58px;
3341
+ height: 58px;
3342
+ margin: 0 auto 14px;
3343
+ border-radius: 20px;
3344
+ display: grid;
3345
+ place-items: center;
3346
+ color: #fff;
3347
+ font-size: 26px;
3348
+ background: linear-gradient(135deg, #6f4bf6, #9b87ff);
3349
+ box-shadow: 0 14px 30px rgba(111,75,246,0.24);
3350
+ }
3351
+ .workspace-card h3,
3352
+ .mode-card h3 {
3353
+ font-size: 15px;
3354
+ margin-bottom: 7px;
3355
+ }
3356
+ .workspace-card span,
3357
+ .mode-card span {
3358
+ display: block;
3359
+ color: var(--muted);
3360
+ font-size: 12px;
3361
+ line-height: 1.45;
3362
+ }
3363
+ .mode-card span { color: #c9c3e2; }
3364
+ .modal-footnote {
3365
+ color: var(--faint);
3366
+ font-size: 12px;
3367
+ text-align: center;
3368
+ margin-top: 18px;
3369
+ }
3370
+ .mode-close {
3371
+ position: absolute;
3372
+ top: 14px;
3373
+ right: 14px;
3374
+ border: 0;
3375
+ background: rgba(255,255,255,0.08);
3376
+ color: #c9c3e2;
3377
+ border-radius: 10px;
3378
+ width: 32px;
3379
+ height: 32px;
3380
+ cursor: pointer;
3381
+ }
3382
+ @media (max-width: 768px) {
3383
+ .app-layout .sidebar { background: #fff; }
3384
+ .workspace-options,
3385
+ .mode-options { grid-template-columns: 1fr; }
3386
+ .workspace-modal,
3387
+ .mode-modal { padding: 24px 18px; }
3388
+ }
3031
3389
  </style>
3390
+ <link rel="stylesheet" href="/static/lattice-reference.css">
3032
3391
  </head>
3033
3392
 
3034
- <body>
3393
+ <body class="lattice-ref-chat">
3035
3394
  <!-- 배경 장식 -->
3036
3395
  <div class="bg-shapes">
3037
3396
  <div class="bg-orb bg-orb-1"></div>
@@ -3060,6 +3419,13 @@
3060
3419
  <div style="font-size:10.5px;color:var(--faint)">Local Workspace</div>
3061
3420
  </div>
3062
3421
  </div>
3422
+ <nav class="reference-side-nav" aria-label="Lattice AI navigation">
3423
+ <button class="reference-nav-item active"><i class="ti ti-home"></i><span>홈</span></button>
3424
+ <button class="reference-nav-item" onclick="startNewChat()"><i class="ti ti-message-circle"></i><span>채팅</span></button>
3425
+ <button class="reference-nav-item" onclick="openDataGraph()"><i class="ti ti-chart-dots-3"></i><span>지식 그래프</span></button>
3426
+ <button class="reference-nav-item" onclick="openLocalBrowser()"><i class="ti ti-file"></i><span>파일</span></button>
3427
+ <button class="reference-nav-item" onclick="openSetupWizard()"><i class="ti ti-settings"></i><span>설정</span></button>
3428
+ </nav>
3063
3429
  <div class="sidebar-search">
3064
3430
  <div class="sidebar-search-wrap">
3065
3431
  <i class="ti ti-search"></i>
@@ -3095,6 +3461,7 @@
3095
3461
  <div id="vpc-header-pill" class="status-pill hide-mobile"><i class="ti ti-network"></i> VPC 대기</div>
3096
3462
  </div>
3097
3463
  <div class="header-pills">
3464
+ <button class="status-pill" onclick="openModeSelector()" id="mode-pill"><i class="ti ti-layout-grid"></i> 기본 모드</button>
3098
3465
  <div class="status-pill"><i class="ti ti-device-desktop"></i> Local</div>
3099
3466
  <div class="lang-picker" id="header-lang-picker">
3100
3467
  <button class="logout-btn" id="lang-btn" onclick="toggleLangMenu('header-lang-picker')" title="Language">🌐</button>
@@ -3160,6 +3527,11 @@
3160
3527
  <h3><i class="ti ti-plug-connected"></i> MCP 서버 관리</h3>
3161
3528
  <button class="mcp-modal-close" onclick="closeMcpModal()"><i class="ti ti-x"></i></button>
3162
3529
  </div>
3530
+ <div class="mcp-tabs" id="mcp-tabs">
3531
+ <button class="mcp-tab active" onclick="switchMcpTab('registry',this)">🗂 레지스트리</button>
3532
+ <button class="mcp-tab" onclick="switchMcpTab('claude-code',this)">🤖 Claude Code</button>
3533
+ <button class="mcp-tab" onclick="switchMcpTab('custom',this)">➕ 직접 추가</button>
3534
+ </div>
3163
3535
  <div class="mcp-modal-body" id="mcp-modal-body">
3164
3536
  <div style="color:var(--faint);font-size:13px;text-align:center;padding:24px">로딩 중...</div>
3165
3537
  </div>
@@ -3203,14 +3575,56 @@
3203
3575
 
3204
3576
  <div class="messages-viewport" id="chat-viewport">
3205
3577
  <div class="empty-state" id="empty-state">
3206
- <div style="width:64px;height:64px;background:linear-gradient(135deg,rgba(34,211,160,0.18),rgba(129,140,248,0.12));border:1px solid rgba(34,211,160,0.18);border-radius:18px;display:flex;align-items:center;justify-content:center;font-size:28px;color:var(--accent);margin:0 auto 18px;box-shadow:0 0 32px rgba(34,211,160,0.14)"><i class="ti ti-sparkles"></i></div>
3207
- <h1 data-i18n="empty_title">무엇을 만들까요?</h1>
3208
- <p data-i18n="empty_sub">로컬 모델, 이미지 분석, 코드 생성, 프라이빗 VPC — 모든 걸 한 화면에서 이어가세요.</p>
3209
- <div class="empty-grid">
3210
- <div class="empty-chip" id="chip-file" onclick="document.getElementById('user-input').value=t('chip_file_prompt');document.getElementById('user-input').focus()"><span class="empty-chip-icon"><i class="ti ti-file-text"></i></span><span data-i18n="chip_file">파일 생성 · 코드 초안</span></div>
3211
- <div class="empty-chip" id="chip-vpc" onclick="document.getElementById('user-input').value=t('chip_vpc_prompt');document.getElementById('user-input').focus()"><span class="empty-chip-icon"><i class="ti ti-shield-check"></i></span><span data-i18n="chip_vpc">VPC 보안 구성 점검</span></div>
3212
- <div class="empty-chip" id="chip-kb" onclick="document.getElementById('user-input').value=t('chip_kb_prompt');document.getElementById('user-input').focus()"><span class="empty-chip-icon"><i class="ti ti-brain"></i></span><span data-i18n="chip_kb">로컬 지식 정리</span></div>
3213
- </div>
3578
+ <section class="reference-home">
3579
+ <div class="reference-home-head">
3580
+ <h1>안녕하세요, 사용자님 👋</h1>
3581
+ <p>Basic Mode에서 로컬 AI, 지식 그래프, 파일, 자동 설정을 한 화면에서 관리하세요.</p>
3582
+ </div>
3583
+ <div class="reference-card-grid">
3584
+ <article class="reference-dash-card">
3585
+ <div class="reference-card-icon"><i class="ti ti-activity"></i></div>
3586
+ <h3>AI 상태</h3>
3587
+ <p><span class="green-dot"></span> 실행 중</p>
3588
+ <div class="reference-stat-row"><span>모델</span><strong>Gemma 4 26B</strong></div>
3589
+ <div class="reference-progress"><span style="width:45%"></span></div>
3590
+ <button onclick="openModelPanel()"><i class="ti ti-cube"></i> 모델 변경</button>
3591
+ </article>
3592
+ <article class="reference-dash-card">
3593
+ <div class="reference-card-icon"><i class="ti ti-chart-dots-3"></i></div>
3594
+ <h3>지식 그래프</h3>
3595
+ <div class="reference-stat-row"><span>대화</span><strong>12개</strong></div>
3596
+ <div class="reference-stat-row"><span>파일</span><strong>5개</strong></div>
3597
+ <div class="reference-stat-row"><span>연결</span><strong>48개</strong></div>
3598
+ <button onclick="openDataGraph()"><i class="ti ti-chart-dots"></i> 그래프 보기</button>
3599
+ </article>
3600
+ <article class="reference-dash-card">
3601
+ <div class="reference-card-icon"><i class="ti ti-tool"></i></div>
3602
+ <h3>자동 설정</h3>
3603
+ <p>설치 가능한 도구</p>
3604
+ <div class="reference-big-number">3개</div>
3605
+ <button onclick="openSetupWizard()"><i class="ti ti-wand"></i> 설정 시작</button>
3606
+ </article>
3607
+ </div>
3608
+ <div class="reference-lists">
3609
+ <article>
3610
+ <header><i class="ti ti-message-circle"></i> 최근 채팅 <button onclick="startNewChat()">전체 보기</button></header>
3611
+ <p>Lattice AI의 기능과 활용 방법에 대해 알려줘 <span>오전 10:30</span></p>
3612
+ <p>RAG와 벡터 검색의 차이점은 무엇인가요? <span>어제</span></p>
3613
+ <p>파이썬으로 웹 크롤링하는 예제 코드 작성해줘 <span>어제</span></p>
3614
+ </article>
3615
+ <article>
3616
+ <header><i class="ti ti-file-text"></i> 최근 파일 <button onclick="openLocalBrowser()">전체 보기</button></header>
3617
+ <p>Lattice AI 사용자 가이드.pdf <span>2.4 MB</span></p>
3618
+ <p>AI 모델 성능 비교.xlsx <span>1.1 MB</span></p>
3619
+ <p>프로젝트 계획서.docx <span>856 KB</span></p>
3620
+ </article>
3621
+ </div>
3622
+ <div class="empty-grid">
3623
+ <div class="empty-chip" id="chip-file" onclick="document.getElementById('user-input').value=t('chip_file_prompt');document.getElementById('user-input').focus()"><span class="empty-chip-icon"><i class="ti ti-file-text"></i></span><span data-i18n="chip_file">파일 생성 · 코드 초안</span></div>
3624
+ <div class="empty-chip" id="chip-vpc" onclick="document.getElementById('user-input').value=t('chip_vpc_prompt');document.getElementById('user-input').focus()"><span class="empty-chip-icon"><i class="ti ti-shield-check"></i></span><span data-i18n="chip_vpc">VPC 보안 구성 점검</span></div>
3625
+ <div class="empty-chip" id="chip-kb" onclick="document.getElementById('user-input').value=t('chip_kb_prompt');document.getElementById('user-input').focus()"><span class="empty-chip-icon"><i class="ti ti-brain"></i></span><span data-i18n="chip_kb">로컬 지식 정리</span></div>
3626
+ </div>
3627
+ </section>
3214
3628
  </div>
3215
3629
  </div>
3216
3630
 
@@ -3248,6 +3662,48 @@
3248
3662
  </main>
3249
3663
  </div>
3250
3664
 
3665
+ <div class="workspace-modal-overlay" id="workspace-modal-overlay">
3666
+ <section class="workspace-modal" role="dialog" aria-modal="true" aria-labelledby="workspace-title">
3667
+ <div class="modal-kicker">Lattice AI</div>
3668
+ <h2 id="workspace-title" data-i18n="workspace_title">워크스페이스 선택</h2>
3669
+ <p data-i18n="workspace_sub">사용 목적에 맞는 시작 공간을 고르면 홈 화면과 기본 도구가 그 흐름에 맞춰 정리됩니다.</p>
3670
+ <div class="workspace-options">
3671
+ <button class="workspace-card" onclick="selectWorkspace('personal')">
3672
+ <div class="workspace-icon"><i class="ti ti-user"></i></div>
3673
+ <h3 data-i18n="workspace_personal">개인 워크스페이스</h3>
3674
+ <span data-i18n="workspace_personal_sub">개인 프로젝트, 로컬 파일, 지식베이스 중심</span>
3675
+ </button>
3676
+ <button class="workspace-card" onclick="selectWorkspace('company')">
3677
+ <div class="workspace-icon"><i class="ti ti-building-skyscraper"></i></div>
3678
+ <h3 data-i18n="workspace_company">회사 워크스페이스</h3>
3679
+ <span data-i18n="workspace_company_sub">SSO, 보안 정책, 팀 운영 대시보드 중심</span>
3680
+ </button>
3681
+ </div>
3682
+ <div class="modal-footnote" data-i18n="workspace_note">나중에 상단 모드 버튼에서 다시 바꿀 수 있습니다.</div>
3683
+ </section>
3684
+ </div>
3685
+
3686
+ <div class="mode-modal-overlay" id="mode-modal-overlay" onclick="if(event.target===this)closeModeSelector()">
3687
+ <section class="mode-modal" role="dialog" aria-modal="true" aria-labelledby="mode-title">
3688
+ <button class="mode-close" onclick="closeModeSelector()" title="닫기"><i class="ti ti-x"></i></button>
3689
+ <div class="modal-kicker">Mode Select</div>
3690
+ <h2 id="mode-title" data-i18n="mode_title">모드 선택</h2>
3691
+ <p data-i18n="mode_sub">작업 성격에 맞춰 Lattice AI의 화면 밀도와 기본 프롬프트를 전환합니다.</p>
3692
+ <div class="mode-options">
3693
+ <button class="mode-card selected" id="mode-card-default" onclick="selectMode('default')">
3694
+ <div class="mode-icon"><i class="ti ti-layout-dashboard"></i></div>
3695
+ <h3 data-i18n="mode_default">기본 모드</h3>
3696
+ <span data-i18n="mode_default_sub">대화, 파일 생성, 지식 정리를 한 화면에서</span>
3697
+ </button>
3698
+ <button class="mode-card" id="mode-card-code" onclick="selectMode('code')">
3699
+ <div class="mode-icon"><i class="ti ti-terminal-2"></i></div>
3700
+ <h3 data-i18n="mode_code">코딩 모드</h3>
3701
+ <span data-i18n="mode_code_sub">계획, 실행, 리뷰 파이프라인 중심</span>
3702
+ </button>
3703
+ </div>
3704
+ </section>
3705
+ </div>
3706
+
3251
3707
  <div id="model-overlay" class="model-overlay">
3252
3708
  <section class="model-panel">
3253
3709
  <div class="model-panel-header">
@@ -3883,6 +4339,20 @@
3883
4339
  ph_input: 'Lattice AI에게 작업을 지시하세요...',
3884
4340
  // 파일 툴바
3885
4341
  create_file: '파일 만들기', local_files: '로컬 파일',
4342
+ // 워크스페이스 / 모드
4343
+ workspace_title: '워크스페이스 선택',
4344
+ workspace_sub: '사용 목적에 맞는 시작 공간을 고르면 홈 화면과 기본 도구가 그 흐름에 맞춰 정리됩니다.',
4345
+ workspace_personal: '개인 워크스페이스',
4346
+ workspace_personal_sub: '개인 프로젝트, 로컬 파일, 지식베이스 중심',
4347
+ workspace_company: '회사 워크스페이스',
4348
+ workspace_company_sub: 'SSO, 보안 정책, 팀 운영 대시보드 중심',
4349
+ workspace_note: '나중에 상단 모드 버튼에서 다시 바꿀 수 있습니다.',
4350
+ mode_title: '모드 선택',
4351
+ mode_sub: '작업 성격에 맞춰 Lattice AI의 화면 밀도와 기본 프롬프트를 전환합니다.',
4352
+ mode_default: '기본 모드',
4353
+ mode_default_sub: '대화, 파일 생성, 지식 정리를 한 화면에서',
4354
+ mode_code: '코딩 모드',
4355
+ mode_code_sub: '계획, 실행, 리뷰 파이프라인 중심',
3886
4356
  // 패널 제목
3887
4357
  model_switcher: '모델 스위처',
3888
4358
  model_switcher_sub: '실행 엔진을 설치하고, 엔진에 맞는 local/cloud LLM을 선택합니다.',
@@ -3922,6 +4392,20 @@
3922
4392
  ph_input: 'Ask Lattice AI anything...',
3923
4393
  // File toolbar
3924
4394
  create_file: 'Create file', local_files: 'Local files',
4395
+ // Workspace / mode
4396
+ workspace_title: 'Select Workspace',
4397
+ workspace_sub: 'Choose a starting space so the home view and default tools match how you work.',
4398
+ workspace_personal: 'Personal Workspace',
4399
+ workspace_personal_sub: 'Personal projects, local files, and knowledge base',
4400
+ workspace_company: 'Company Workspace',
4401
+ workspace_company_sub: 'SSO, security policy, and team operations',
4402
+ workspace_note: 'You can change this later from the mode button.',
4403
+ mode_title: 'Mode Select',
4404
+ mode_sub: 'Switch Lattice AI density and defaults based on the job.',
4405
+ mode_default: 'Default Mode',
4406
+ mode_default_sub: 'Chat, file creation, and knowledge in one view',
4407
+ mode_code: 'Code Mode',
4408
+ mode_code_sub: 'Plan, execute, and review pipeline first',
3925
4409
  // Panel titles
3926
4410
  model_switcher: 'Model Switcher',
3927
4411
  model_switcher_sub: 'Install a runtime engine and select a local/cloud LLM.',
@@ -3964,6 +4448,7 @@
3964
4448
  localStorage.setItem('ltcai_lang', lang);
3965
4449
  document.querySelectorAll('.lang-picker-menu').forEach(m => m.classList.remove('open'));
3966
4450
  applyI18n();
4451
+ updateWorkspaceModeUi();
3967
4452
  }
3968
4453
 
3969
4454
  document.addEventListener('click', (e) => {
@@ -3972,6 +4457,66 @@
3972
4457
  }
3973
4458
  });
3974
4459
 
4460
+ function workspaceLabel(kind) {
4461
+ if (kind === 'company') return currentLang === 'ko' ? '회사' : 'Company';
4462
+ return currentLang === 'ko' ? '개인' : 'Personal';
4463
+ }
4464
+
4465
+ function modeLabel(mode) {
4466
+ if (mode === 'code') return currentLang === 'ko' ? '코딩 모드' : 'Code Mode';
4467
+ return currentLang === 'ko' ? '기본 모드' : 'Default Mode';
4468
+ }
4469
+
4470
+ function updateWorkspaceModeUi() {
4471
+ const workspace = localStorage.getItem('ltcai_workspace') || 'personal';
4472
+ const mode = localStorage.getItem('ltcai_mode') || 'default';
4473
+ const pill = document.getElementById('mode-pill');
4474
+ if (pill) pill.innerHTML = `<i class="ti ti-layout-grid"></i> ${workspaceLabel(workspace)} · ${modeLabel(mode)}`;
4475
+ document.querySelectorAll('.workspace-card').forEach((card, index) => {
4476
+ const kind = index === 1 ? 'company' : 'personal';
4477
+ card.classList.toggle('selected', kind === workspace);
4478
+ });
4479
+ ['default', 'code'].forEach(item => {
4480
+ const el = document.getElementById(`mode-card-${item}`);
4481
+ if (el) el.classList.toggle('selected', item === mode);
4482
+ });
4483
+ }
4484
+
4485
+ function maybeShowWorkspaceModal() {
4486
+ updateWorkspaceModeUi();
4487
+ if (!localStorage.getItem('ltcai_workspace')) {
4488
+ document.getElementById('workspace-modal-overlay')?.classList.add('open');
4489
+ }
4490
+ }
4491
+
4492
+ function selectWorkspace(kind) {
4493
+ localStorage.setItem('ltcai_workspace', kind === 'company' ? 'company' : 'personal');
4494
+ document.getElementById('workspace-modal-overlay')?.classList.remove('open');
4495
+ updateWorkspaceModeUi();
4496
+ openModeSelector();
4497
+ }
4498
+
4499
+ function openModeSelector() {
4500
+ updateWorkspaceModeUi();
4501
+ document.getElementById('mode-modal-overlay')?.classList.add('open');
4502
+ }
4503
+
4504
+ function closeModeSelector() {
4505
+ document.getElementById('mode-modal-overlay')?.classList.remove('open');
4506
+ }
4507
+
4508
+ function selectMode(mode) {
4509
+ localStorage.setItem('ltcai_mode', mode === 'code' ? 'code' : 'default');
4510
+ updateWorkspaceModeUi();
4511
+ closeModeSelector();
4512
+ const input = document.getElementById('user-input');
4513
+ if (input && !input.value.trim()) {
4514
+ input.placeholder = mode === 'code'
4515
+ ? (currentLang === 'ko' ? '무엇을 구현하거나 고칠까요?' : 'What should we build or fix?')
4516
+ : t('ph_input');
4517
+ }
4518
+ }
4519
+
3975
4520
  function switchAcctTab(tab) {
3976
4521
  ['profile', 'password'].forEach(t => {
3977
4522
  document.getElementById(`tab-${t}`).classList.toggle('active', t === tab);
@@ -6382,6 +6927,7 @@
6382
6927
 
6383
6928
  document.getElementById('new-chat-btn').onclick = startNewChat;
6384
6929
  applyI18n();
6930
+ maybeShowWorkspaceModal();
6385
6931
 
6386
6932
  // Session check — redirect to /account if not logged in
6387
6933
  (async function restoreSession() {
@@ -6765,8 +7311,28 @@
6765
7311
  setTimeout(() => el.classList.add('visible'), 80 + i * 90);
6766
7312
  });
6767
7313
 
7314
+ const zero = env.zero_config || {};
7315
+ const zeroRec = zero.recommend || {};
7316
+ const zeroPlan = zero.plan || {};
7317
+ if (zeroRec.model_id || zeroRec.runtime || zeroRec.backend) {
7318
+ const planCount = (zeroPlan.steps || []).length;
7319
+ const el = document.createElement('div');
7320
+ el.className = 'scan-row zero-config-row';
7321
+ el.innerHTML = `
7322
+ <span class="scan-icon">⚙️</span>
7323
+ <div>
7324
+ <div class="scan-label">Zero-Config 추천</div>
7325
+ <div class="scan-value">${escapeHtml([zeroRec.runtime, zeroRec.backend, zeroRec.model_id].filter(Boolean).join(' · '))}</div>
7326
+ <div class="scan-value" style="margin-top:4px;color:var(--faint)">${planCount ? escapeHtml(`${planCount}개 설치/검증 단계 준비됨`) : '추가 설치 단계 없음'}</div>
7327
+ </div>`;
7328
+ grid.appendChild(el);
7329
+ setTimeout(() => el.classList.add('visible'), 80 + rows.length * 90);
7330
+ }
7331
+
6768
7332
  const sum = _wizRecs?.summary || {};
6769
- _subtitle(`${escapeHtml(chip.name || 'Unknown')} · RAM ${env.ram_gb}GB · 여유 ${env.disk_free_gb}GB`);
7333
+ const zeroSummary = sum.zero_config || {};
7334
+ const recSuffix = zeroSummary.model_id ? ` · ${zeroSummary.model_id}` : '';
7335
+ _subtitle(`${escapeHtml(chip.name || 'Unknown')} · RAM ${env.ram_gb}GB · 여유 ${env.disk_free_gb}GB${escapeHtml(recSuffix)}`);
6770
7336
  _footInfo(`추천 최대 모델 크기: ${sum.max_model_gb || '?'}GB`);
6771
7337
  setTimeout(() => {
6772
7338
  _footBtns(`
@@ -6952,18 +7518,39 @@
6952
7518
 
6953
7519
  <script>
6954
7520
  // ── MCP 관리 모달 ────────────────────────────────────────────────────────
7521
+ let _mcpCurrentTab = 'registry';
7522
+
6955
7523
  async function openMcpModal() {
6956
7524
  document.getElementById('mcp-modal-overlay').classList.add('open');
6957
- await renderMcpModal();
7525
+ await renderMcpModal(_mcpCurrentTab);
6958
7526
  }
6959
7527
 
6960
7528
  function closeMcpModal() {
6961
7529
  document.getElementById('mcp-modal-overlay').classList.remove('open');
6962
7530
  }
6963
7531
 
6964
- async function renderMcpModal() {
7532
+ function switchMcpTab(tab, btn) {
7533
+ _mcpCurrentTab = tab;
7534
+ document.querySelectorAll('.mcp-tab').forEach(b => b.classList.remove('active'));
7535
+ btn.classList.add('active');
7536
+ renderMcpModal(tab);
7537
+ }
7538
+
7539
+ async function renderMcpModal(tab) {
7540
+ tab = tab || _mcpCurrentTab || 'registry';
6965
7541
  const body = document.getElementById('mcp-modal-body');
6966
7542
  body.innerHTML = '<div style="color:var(--faint);font-size:13px;text-align:center;padding:24px">로딩 중...</div>';
7543
+
7544
+ if (tab === 'claude-code') {
7545
+ await renderMcpClaudeCode(body);
7546
+ } else if (tab === 'custom') {
7547
+ await renderMcpCustom(body);
7548
+ } else {
7549
+ await renderMcpRegistry(body);
7550
+ }
7551
+ }
7552
+
7553
+ async function renderMcpRegistry(body) {
6967
7554
  try {
6968
7555
  const [installedRes, toolsRes] = await Promise.all([
6969
7556
  apiFetch('/mcp/installed'),
@@ -7015,6 +7602,148 @@
7015
7602
  }
7016
7603
  }
7017
7604
 
7605
+ async function renderMcpClaudeCode(body) {
7606
+ try {
7607
+ const res = await apiFetch('/mcp/claude-code-servers');
7608
+ const data = res.ok ? await res.json() : { servers: [] };
7609
+ const servers = data.servers || [];
7610
+
7611
+ if (!servers.length) {
7612
+ body.innerHTML = `
7613
+ <div style="color:var(--faint);font-size:13px;text-align:center;padding:32px 24px;">
7614
+ <div style="font-size:28px;margin-bottom:10px">🤖</div>
7615
+ <div><strong>Claude Code MCP 없음</strong></div>
7616
+ <div style="margin-top:6px;font-size:11px">~/.claude/settings.json에 mcpServers 항목이 없습니다.</div>
7617
+ <div style="margin-top:4px;font-size:11px">Claude Code에서 MCP를 설치하면 여기에 자동으로 표시됩니다.</div>
7618
+ </div>`;
7619
+ return;
7620
+ }
7621
+
7622
+ let html = '<div class="mcp-section-label">Claude Code에서 설치된 MCP</div>';
7623
+ html += servers.map(srv => `
7624
+ <div class="mcp-item">
7625
+ <div class="mcp-item-icon">${srv.icon || '🤖'}</div>
7626
+ <div class="mcp-item-info">
7627
+ <div class="mcp-item-name" style="display:flex;align-items:center;gap:6px;">
7628
+ ${escapeHtml(srv.name)}
7629
+ <span class="mcp-source-badge claude-code">Claude Code</span>
7630
+ </div>
7631
+ <div class="mcp-item-desc" title="${escapeHtml(srv.package || '')}">${escapeHtml(srv.package || '')}</div>
7632
+ ${srv.env_vars && srv.env_vars.length ? `<div class="mcp-item-desc" style="margin-top:2px">ENV: ${escapeHtml(srv.env_vars.map(e=>e.name).join(', '))}</div>` : ''}
7633
+ </div>
7634
+ <span class="mcp-item-status">활성</span>
7635
+ </div>
7636
+ `).join('');
7637
+ body.innerHTML = html;
7638
+ } catch (e) {
7639
+ body.innerHTML = `<div style="color:#ff6b6b;font-size:13px;text-align:center;padding:24px">로드 실패: ${escapeHtml(e.message)}</div>`;
7640
+ }
7641
+ }
7642
+
7643
+ async function renderMcpCustom(body) {
7644
+ // Load existing custom MCPs
7645
+ let existingHtml = '';
7646
+ try {
7647
+ const res = await apiFetch('/mcp/custom');
7648
+ const data = res.ok ? await res.json() : { custom: [] };
7649
+ const customs = data.custom || [];
7650
+ if (customs.length) {
7651
+ existingHtml = '<div class="mcp-section-label">직접 추가한 MCP</div>';
7652
+ existingHtml += customs.map(c => `
7653
+ <div class="mcp-item" id="mcp-custom-item-${escapeHtml(c.id)}">
7654
+ <div class="mcp-item-icon">${c.icon || '🔌'}</div>
7655
+ <div class="mcp-item-info">
7656
+ <div class="mcp-item-name" style="display:flex;align-items:center;gap:6px;">
7657
+ ${escapeHtml(c.name)}
7658
+ <span class="mcp-source-badge custom">Custom</span>
7659
+ </div>
7660
+ <div class="mcp-item-desc" title="${escapeHtml(c.package||'')}">${escapeHtml(c.package||'')}</div>
7661
+ ${c.description ? `<div class="mcp-item-desc">${escapeHtml(c.description)}</div>` : ''}
7662
+ </div>
7663
+ <button class="mcp-delete-btn" onclick="deleteCustomMcp('${escapeHtml(c.id)}')">삭제</button>
7664
+ </div>
7665
+ `).join('');
7666
+ }
7667
+ } catch {}
7668
+
7669
+ body.innerHTML = existingHtml + `
7670
+ <div class="mcp-section-label" style="margin-top:${existingHtml?'16px':'4px'}">새 MCP 추가</div>
7671
+ <div class="mcp-add-form">
7672
+ <div>
7673
+ <label>이름 *</label>
7674
+ <input id="mcp-add-name" type="text" placeholder="예: my-database" />
7675
+ </div>
7676
+ <div>
7677
+ <label>패키지 / 명령어 *</label>
7678
+ <input id="mcp-add-package" type="text" placeholder="예: @company/mcp-server 또는 npx mcp-server" />
7679
+ <div class="field-hint">npm 패키지명 또는 실행 명령어</div>
7680
+ </div>
7681
+ <div>
7682
+ <label>설명</label>
7683
+ <input id="mcp-add-desc" type="text" placeholder="이 MCP가 하는 일 (선택)" />
7684
+ </div>
7685
+ <div>
7686
+ <label>필요한 환경변수 (쉼표 구분)</label>
7687
+ <input id="mcp-add-envs" type="text" placeholder="예: API_KEY, BASE_URL" />
7688
+ <div class="field-hint">서버 실행 시 필요한 env var 이름들</div>
7689
+ </div>
7690
+ <div style="display:flex;align-items:center;gap:8px;">
7691
+ <input id="mcp-add-icon" type="text" placeholder="🔌" style="width:56px;text-align:center;" maxlength="4" />
7692
+ <label style="margin:0">아이콘 (이모지)</label>
7693
+ </div>
7694
+ <div id="mcp-add-error" style="color:#ff6b6b;font-size:12px;display:none;"></div>
7695
+ <button class="mcp-submit-btn" onclick="submitCustomMcp()">➕ MCP 추가</button>
7696
+ </div>
7697
+ `;
7698
+ }
7699
+
7700
+ async function submitCustomMcp() {
7701
+ const name = document.getElementById('mcp-add-name').value.trim();
7702
+ const pkg = document.getElementById('mcp-add-package').value.trim();
7703
+ const desc = document.getElementById('mcp-add-desc').value.trim();
7704
+ const envsRaw = document.getElementById('mcp-add-envs').value.trim();
7705
+ const icon = document.getElementById('mcp-add-icon').value.trim() || '🔌';
7706
+ const errEl = document.getElementById('mcp-add-error');
7707
+ errEl.style.display = 'none';
7708
+
7709
+ if (!name) { errEl.textContent = '이름을 입력해주세요.'; errEl.style.display='block'; return; }
7710
+ if (!pkg) { errEl.textContent = '패키지를 입력해주세요.'; errEl.style.display='block'; return; }
7711
+
7712
+ const env_vars = envsRaw ? envsRaw.split(',').map(s=>({name:s.trim(),description:''})).filter(e=>e.name) : [];
7713
+ try {
7714
+ const res = await apiFetch('/mcp/custom', {
7715
+ method: 'POST',
7716
+ headers: { 'Content-Type': 'application/json' },
7717
+ body: JSON.stringify({ name, package: pkg, description: desc, icon, env_vars }),
7718
+ });
7719
+ if (res.ok) {
7720
+ await renderMcpModal('custom');
7721
+ } else {
7722
+ const d = await res.json().catch(()=>({}));
7723
+ errEl.textContent = d.detail || '추가 실패';
7724
+ errEl.style.display = 'block';
7725
+ }
7726
+ } catch (e) {
7727
+ errEl.textContent = e.message || '네트워크 오류';
7728
+ errEl.style.display = 'block';
7729
+ }
7730
+ }
7731
+
7732
+ async function deleteCustomMcp(id) {
7733
+ if (!confirm('이 MCP를 삭제하시겠습니까?')) return;
7734
+ try {
7735
+ const res = await apiFetch('/mcp/custom/' + encodeURIComponent(id), { method: 'DELETE' });
7736
+ if (res.ok) {
7737
+ await renderMcpModal('custom');
7738
+ } else {
7739
+ const d = await res.json().catch(()=>({}));
7740
+ alert(d.detail || '삭제 실패');
7741
+ }
7742
+ } catch (e) {
7743
+ alert(e.message || '네트워크 오류');
7744
+ }
7745
+ }
7746
+
7018
7747
  async function installMcp(id) {
7019
7748
  const btn = document.querySelector(`#mcp-item-${CSS.escape(id)} .mcp-install-btn`);
7020
7749
  if (btn) { btn.disabled = true; btn.textContent = '설치 중...'; }
@@ -7025,7 +7754,7 @@
7025
7754
  body: JSON.stringify({ mcp_id: id }),
7026
7755
  });
7027
7756
  if (res.ok) {
7028
- await renderMcpModal();
7757
+ await renderMcpModal('registry');
7029
7758
  } else {
7030
7759
  const d = await res.json().catch(() => ({}));
7031
7760
  if (btn) { btn.disabled = false; btn.textContent = '설치'; }