kimhyunwoo commited on
Commit
a7f8baa
ยท
verified ยท
1 Parent(s): 4daea0d

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +252 -383
index.html CHANGED
@@ -3,30 +3,29 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>AI ์ฝ”๋”ฉ ํŒŒํŠธ๋„ˆ</title>
7
- <!-- Monaco Editor ๋กœ๋” CSS -->
8
  <link rel="stylesheet" data-name="vs/editor/editor.main" href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/editor/editor.main.min.css">
9
- <!-- Google Fonts (macOS ๋А๋‚Œ ํฐํŠธ) -->
10
  <link rel="preconnect" href="https://fonts.googleapis.com">
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
13
  <style>
 
14
  :root {
15
- --bg-color: #f0f2f5; /* ๋ฐ์€ ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ */
16
- --app-bg-color: #ffffff; /* ์•ฑ ๋ฐฐ๊ฒฝ */
17
- --sidebar-bg-color: #e8e8e8; /* ์‚ฌ์ด๋“œ๋ฐ” ๋ฐฐ๊ฒฝ (๋” ๋ฐ๊ฒŒ) */
18
- --text-color: #1c1e21; /* ๊ธฐ๋ณธ ํ…์ŠคํŠธ */
19
- --text-secondary-color: #606770; /* ๋ณด์กฐ ํ…์ŠคํŠธ */
20
- --accent-color: #007aff; /* macOS ๋ธ”๋ฃจ */
21
  --border-color: #d1d1d7;
22
- --editor-bg: #282c34; /* ์–ด๋‘์šด ์—๋””ํ„ฐ ๋ฐฐ๊ฒฝ */
 
23
  --font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
24
  --border-radius: 12px;
25
  --box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
26
  --box-shadow-light: 0 1px 3px rgba(0,0,0,0.05);
27
  }
28
 
29
- /* ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ (์„ ํƒ ์‚ฌํ•ญ) */
30
  ::-webkit-scrollbar { width: 8px; height: 8px; }
31
  ::-webkit-scrollbar-track { background: transparent; }
32
  ::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; }
@@ -57,270 +56,131 @@
57
  overflow: hidden;
58
  }
59
 
60
- /* ์‚ฌ์ด๋“œ๋ฐ” (์„ ํƒ ์‚ฌํ•ญ - ์—ฌ๊ธฐ์„œ๋Š” ์ฑ„ํŒ…, ์—๋””ํ„ฐ, ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์ฃผ ์˜์—ญ์œผ๋กœ) */
61
- /* .sidebar { flex: 0 0 280px; background-color: var(--sidebar-bg-color); padding: 20px; border-right: 1px solid var(--border-color); display: flex; flex-direction: column; } */
 
 
62
 
63
- .main-content {
64
- flex-grow: 1;
65
- display: flex;
66
- flex-direction: column;
67
- overflow: hidden;
68
- }
69
-
70
- .chat-editor-area {
71
- display: flex;
72
- flex-grow: 1;
73
- overflow: hidden; /* ๋‚ด๋ถ€ ์Šคํฌ๋กค ๊ด€๋ฆฌ */
74
- }
75
-
76
- .chat-panel {
77
- flex: 1;
78
- display: flex;
79
- flex-direction: column;
80
- padding: 20px;
81
- border-right: 1px solid var(--border-color);
82
- overflow-y: auto; /* ์ฑ„ํŒ… ์Šคํฌ๋กค */
83
- }
84
-
85
- .chat-messages {
86
- flex-grow: 1;
87
- margin-bottom: 15px;
88
- overflow-y: auto;
89
- padding-right: 10px; /* ์Šคํฌ๋กค๋ฐ” ๊ณต๊ฐ„ */
90
- }
91
-
92
- .message {
93
- display: flex;
94
- margin-bottom: 15px;
95
- max-width: 90%;
96
- }
97
-
98
- .message.user {
99
- margin-left: auto; /* ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€๋Š” ์˜ค๋ฅธ์ชฝ ์ •๋ ฌ */
100
- flex-direction: row-reverse;
101
- }
102
-
103
- .avatar {
104
- width: 36px;
105
- height: 36px;
106
- border-radius: 50%;
107
- background-color: var(--accent-color);
108
- color: white;
109
- display: flex;
110
- align-items: center;
111
- justify-content: center;
112
- font-weight: 500;
113
- font-size: 16px;
114
- margin-right: 10px;
115
- flex-shrink: 0;
116
- }
117
  .message.user .avatar { margin-left: 10px; margin-right: 0; background-color: #6c757d; }
118
-
119
- .message-content {
120
- padding: 10px 15px;
121
- border-radius: 18px;
122
- background-color: #e4e6eb; /* AI ๋ฉ”์‹œ์ง€ ๋ฐฐ๊ฒฝ */
123
- color: var(--text-color);
124
- word-wrap: break-word; /* ๊ธด ๋‹จ์–ด ์ค„๋ฐ”๊ฟˆ */
125
- }
126
  .message.user .message-content { background-color: var(--accent-color); color: white; }
127
-
128
- .message-content pre { /* ์ฝ”๋“œ ๋ธ”๋ก ์Šคํƒ€์ผ */
129
- background-color: var(--editor-bg) !important; /* Monaco ํ…Œ๋งˆ์™€ ์œ ์‚ฌํ•˜๊ฒŒ */
130
- color: #abb2bf !important; /* Monaco ๊ธฐ๋ณธ ํ…์ŠคํŠธ ์ƒ‰์ƒ */
131
- padding: 10px;
 
 
 
 
 
 
 
132
  border-radius: 8px;
133
  overflow-x: auto;
134
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
135
  font-size: 0.9em;
136
- white-space: pre-wrap; /* ์ž๋™ ์ค„๋ฐ”๊ฟˆ */
137
- word-break: break-all; /* ๊ธด ์ฝ”๋“œ ์ค„๋ฐ”๊ฟˆ */
 
138
  }
139
- /* Monaco ์—๋””ํ„ฐ๊ฐ€ ๋ Œ๋”๋งํ•œ ์ฝ”๋“œ ๋ธ”๋ก์— ๋Œ€ํ•œ ์Šคํƒ€์ผ (ํ•„์š” ์‹œ) */
140
- .monaco-editor-code-block {
141
- border-radius: 8px !important;
142
- box-shadow: var(--box-shadow-light);
 
 
 
 
 
 
 
143
  }
 
144
 
145
 
146
- .chat-input-area {
147
- display: flex;
148
- align-items: center;
149
- padding-top: 15px;
150
- border-top: 1px solid var(--border-color);
151
- }
152
-
153
- .chat-input-area textarea {
154
- flex-grow: 1;
155
- padding: 10px 15px;
156
- border: 1px solid var(--border-color);
157
- border-radius: 18px;
158
- resize: none;
159
- font-family: var(--font-family);
160
- font-size: 15px;
161
- min-height: 22px; /* 1์ค„ ๋†’์ด */
162
- max-height: 100px; /* ์ตœ๋Œ€ ๋†’์ด */
163
- line-height: 1.4;
164
- margin-right: 10px;
165
- box-sizing: border-box;
166
- overflow-y: auto; /* ๋‚ด์šฉ ๋งŽ์•„์ง€๋ฉด ์Šคํฌ๋กค */
167
- }
168
  .chat-input-area textarea:focus { border-color: var(--accent-color); outline: none; box-shadow: 0 0 0 2px rgba(0,122,255,0.2); }
169
-
170
- .chat-input-area button {
171
- background-color: var(--accent-color);
172
- color: white;
173
- border: none;
174
- border-radius: 50%; /* ์›ํ˜• ๋ฒ„ํŠผ */
175
- width: 40px;
176
- height: 40px;
177
- font-size: 20px;
178
- cursor: pointer;
179
- display: flex;
180
- align-items: center;
181
- justify-content: center;
182
- transition: background-color 0.2s;
183
- }
184
  .chat-input-area button:hover { background-color: #0056b3; }
185
  .chat-input-area button:disabled { background-color: #b0c4de; cursor: not-allowed; }
186
 
187
- .editor-preview-area {
188
- flex: 2; /* ์—๋””ํ„ฐ์™€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์˜์—ญ์„ ๋” ๋„“๊ฒŒ */
189
- display: flex;
190
- flex-direction: column;
191
- overflow: hidden;
192
- }
193
-
194
- .editor-wrapper {
195
- flex-basis: 60%; /* ์—๋””ํ„ฐ๊ฐ€ ์ฐจ์ง€ํ•  ๊ธฐ๋ณธ ๋†’์ด ๋น„์œจ */
196
- min-height: 200px; /* ์ตœ์†Œ ๋†’์ด */
197
- padding: 10px; /* ์—๋””ํ„ฐ ์ฃผ๋ณ€ ์—ฌ๋ฐฑ */
198
- background-color: #f9f9f9; /* ์—๋””ํ„ฐ ์˜์—ญ ๋ฐฐ๊ฒฝ */
199
- display: flex;
200
- flex-direction: column;
201
- }
202
- #editor-container {
203
- width: 100% !important;
204
- height: 100% !important; /* ๋ถ€๋ชจ ๋†’์ด์— ๋งž์ถค */
205
- border: 1px solid var(--border-color);
206
- border-radius: var(--border-radius);
207
- overflow: hidden; /* Monaco ์—๋””ํ„ฐ์˜ ์ž์ฒด ์Šคํฌ๋กค ์‚ฌ์šฉ */
208
- }
209
- .editor-controls {
210
- display: flex;
211
- justify-content: space-between;
212
- align-items: center;
213
- padding: 8px 0;
214
- }
215
- .editor-controls select, .editor-controls button {
216
- padding: 6px 12px;
217
- border-radius: 6px;
218
- border: 1px solid var(--border-color);
219
- background-color: white;
220
- font-family: var(--font-family);
221
- font-size: 13px;
222
- cursor: pointer;
223
- }
224
  .editor-controls button { background-color: #e9ecef; color: var(--text-color); }
225
  .editor-controls button:hover { background-color: #d1d5db; }
226
 
227
-
228
- .preview-wrapper {
229
- flex-basis: 40%; /* ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ์ฐจ์ง€ํ•  ๊ธฐ๋ณธ ๋†’์ด ๋น„์œจ */
230
- min-height: 150px;
231
- border-top: 1px solid var(--border-color);
232
- padding: 10px;
233
- background-color: #f9f9f9; /* ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์˜์—ญ ๋ฐฐ๊ฒฝ */
234
- display: flex; /* ๋‚ด๋ถ€ iframe ํฌ๊ธฐ ์กฐ์ ˆ ์œ„ํ•ด */
235
- flex-direction: column;
236
- }
237
- .preview-wrapper h3 {
238
- margin: 0 0 8px 0;
239
- font-size: 14px;
240
- font-weight: 500;
241
- color: var(--text-secondary-color);
242
- }
243
- #html-preview {
244
- flex-grow: 1; /* ๋‚จ์€ ๊ณต๊ฐ„ ๋ชจ๋‘ ์ฐจ์ง€ */
245
  width: 100%;
246
  border: 1px solid var(--border-color);
247
  border-radius: var(--border-radius);
248
  background-color: white;
 
 
249
  }
250
-
251
- /* ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ */
252
- .loader {
253
- display: flex;
254
- align-items: center;
255
- font-size: 13px;
256
- color: var(--text-secondary-color);
257
- margin-left: 10px; /* ์ž…๋ ฅ์ฐฝ ์˜†์— ํ‘œ์‹œ๋  ๋•Œ */
258
  }
259
- .loader.inline-editor { margin-top: 5px; justify-content: center; }
260
-
261
- .spinner {
262
- width: 16px;
263
- height: 16px;
264
- border: 2px solid var(--accent-color);
265
- border-top-color: transparent;
266
- border-radius: 50%;
267
- animation: spin 0.8s linear infinite;
268
- margin-right: 8px;
 
 
 
 
269
  }
270
- @keyframes spin { to { transform: rotate(360deg); } }
271
 
272
- /* ๋ฐ˜์‘ํ˜• ์กฐ์ • */
273
- @media (max-width: 1024px) {
274
- .app-container { flex-direction: column; height: auto; max-height: none; }
275
- .chat-editor-area { flex-direction: column; }
276
- .chat-panel { border-right: none; border-bottom: 1px solid var(--border-color); max-height: 50vh; }
277
- .editor-preview-area { flex: 1; } /* ๋‚จ์€ ๊ณต๊ฐ„ ๋ชจ๋‘ ์ฐจ์ง€ */
278
- }
279
- @media (max-width: 768px) {
280
- body { padding: 0; }
281
- .app-container { width: 100%; height: 100vh; border-radius: 0; }
282
- .chat-panel { padding: 15px; }
283
- .editor-wrapper, .preview-wrapper { padding: 8px; }
284
- .chat-input-area textarea { font-size: 14px; }
285
- .chat-input-area button { width: 36px; height: 36px; font-size: 18px; }
286
- }
287
 
 
 
 
 
 
 
 
288
  </style>
289
  </head>
290
  <body>
291
  <div class="app-container">
292
- <!-- <div class="sidebar">Sidebar Content (Optional)</div> -->
293
  <div class="main-content">
294
  <div class="chat-editor-area">
295
  <div class="chat-panel">
296
  <div class="chat-messages" id="chatMessages">
297
- <!-- AI ์‹œ์ž‘ ๋ฉ”์‹œ์ง€ -->
298
- <div class="message ai">
299
- <div class="avatar">AI</div>
300
- <div class="message-content">
301
- ์•ˆ๋…•ํ•˜์„ธ์š”! ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”? HTML ์ƒ์„ฑ, ์ฝ”๋“œ ์ž‘์„ฑ, ๋˜๋Š” ๊ธฐ์กด ์ฝ”๋“œ ์ž๋™ ์™„์„ฑ์„ ์š”์ฒญํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
302
- </div>
303
- </div>
304
  </div>
305
  <div class="chat-input-area">
306
  <textarea id="chatInput" placeholder="์—ฌ๊ธฐ์— ๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”..." rows="1"></textarea>
307
- <button id="sendButton" title="์ „์†ก">
308
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
309
- </button>
310
  <div class="loader" id="chatLoader" style="display: none;"><div class="spinner"></div>์ฒ˜๋ฆฌ ์ค‘...</div>
311
  </div>
312
  </div>
313
-
314
  <div class="editor-preview-area">
315
  <div class="editor-wrapper">
316
  <div class="editor-controls">
317
  <select id="languageSelect">
318
- <option value="html">HTML</option>
319
- <option value="css">CSS</option>
320
- <option value="javascript" selected>JavaScript</option>
321
- <option value="python">Python</option>
322
- <option value="java">Java</option>
323
- <option value="csharp">C#</option>
324
  </select>
325
  <button id="getCompletionBtn">AI ์ฝ”๋“œ ์ฑ„์šฐ๊ธฐ (FIM)</button>
326
  </div>
@@ -328,8 +188,14 @@
328
  <div class="loader inline-editor" id="completionLoader" style="display: none;"><div class="spinner"></div>AI๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ฑ„์šฐ๋Š” ์ค‘...</div>
329
  </div>
330
  <div class="preview-wrapper">
331
- <h3>HTML ๋ฏธ๋ฆฌ๋ณด๊ธฐ</h3>
332
- <iframe id="html-preview" sandbox="allow-scripts allow-same-origin"></iframe>
 
 
 
 
 
 
333
  </div>
334
  </div>
335
  </div>
@@ -344,236 +210,239 @@
344
  const sendButton = document.getElementById('sendButton');
345
  const languageSelect = document.getElementById('languageSelect');
346
  const getCompletionBtn = document.getElementById('getCompletionBtn');
347
- const htmlPreviewEl = document.getElementById('html-preview');
 
 
 
 
348
  const chatLoader = document.getElementById('chatLoader');
349
  const completionLoader = document.getElementById('completionLoader');
350
 
351
- // --- ๋กœ๋” ํ‘œ์‹œ/์ˆจ๊น€ ํ•จ์ˆ˜ ---
352
  function showLoader(loaderElement) { if (loaderElement) loaderElement.style.display = 'flex'; }
353
  function hideLoader(loaderElement) { if (loaderElement) loaderElement.style.display = 'none'; }
354
 
355
- // --- Monaco Editor ์ดˆ๊ธฐํ™” ---
356
  require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs' }});
357
  require(['vs/editor/editor.main'], function() {
358
  editor = monaco.editor.create(document.getElementById('editor-container'), {
359
- value: '// ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ AI์—๊ฒŒ ์š”์ฒญํ•˜์„ธ์š”.\n// ์ฑ„ํŒ…์ฐฝ์— "์‚ฌ๊ฐํ˜• ๋ฒ„ํŠผ ๋งŒ๋“ค์–ด์ค˜" ๊ฐ™์ด ์ž…๋ ฅํ•ด๋ณด์„ธ์š”.',
360
  language: languageSelect.value,
361
- theme: 'vs-dark', // ์–ด๋‘์šด ํ…Œ๋งˆ
362
- automaticLayout: true,
363
- wordWrap: 'on',
364
- minimap: { enabled: true, scale: 1 },
365
- fontSize: 14,
366
- lineNumbers: 'on',
367
- roundedSelection: false,
368
- scrollBeyondLastLine: false,
369
- readOnly: false, // ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ํŽธ์ง‘ ๊ฐ€๋Šฅํ•˜๋„๋ก
370
  padding: { top: 10, bottom: 10 }
371
  });
372
-
373
- // ์–ธ์–ด ์„ ํƒ ์‹œ ์—๋””ํ„ฐ ์–ธ์–ด ๋ณ€๊ฒฝ
374
- languageSelect.addEventListener('change', () => {
375
- monaco.editor.setModelLanguage(editor.getModel(), languageSelect.value);
376
- });
377
-
378
- // FIM ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ
379
  getCompletionBtn.addEventListener('click', handleFIMRequest);
380
  });
381
 
382
- // --- ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ---
383
- function addMessage(text, sender, isCode = false, lang = 'plaintext') {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  const messageDiv = document.createElement('div');
385
  messageDiv.classList.add('message', sender);
386
-
387
  const avatarDiv = document.createElement('div');
388
  avatarDiv.classList.add('avatar');
389
  avatarDiv.textContent = sender === 'user' ? '๋‚˜' : 'AI';
390
  messageDiv.appendChild(avatarDiv);
391
-
392
  const contentDiv = document.createElement('div');
393
  contentDiv.classList.add('message-content');
 
394
 
395
- if (isCode) {
396
- // ์ฝ”๋“œ๋ฅผ Monaco Editor๋ฅผ ์‚ฌ์šฉํ•ด ๋ Œ๋”๋ง (์ฝ๊ธฐ ์ „์šฉ)
397
- const pre = document.createElement('pre');
398
- // ๊ฐ„๋‹จํžˆ ํ…์ŠคํŠธ๋กœ ๋„ฃ๊ฑฐ๋‚˜, ์ƒˆ๋กœ์šด Monaco ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ.
399
- // ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํžˆ pre ํƒœ๊ทธ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ , CSS๋กœ ์Šคํƒ€์ผ๋ง.
400
- // ์‹ค์ œ๋กœ๋Š” AI ์‘๋‹ต์—์„œ ```html ... ``` ๊ฐ™์€ ๋งˆํฌ๋‹ค์šด ์ฝ”๋“œ๋ธ”๋ก ํŒŒ์‹ฑ ํ•„์š”
401
- let codeContent = text;
402
- const codeBlockMatch = text.match(/```(\w*)\n([\s\S]*?)```/);
403
- if (codeBlockMatch) {
404
- lang = codeBlockMatch[1] || lang;
405
- codeContent = codeBlockMatch[2];
406
  }
407
- pre.textContent = codeContent; // ์‹ค์ œ๋กœ๋Š” highlight.js ๋“ฑ์œผ๋กœ ํ•˜์ด๋ผ์ดํŒ… ์ ์šฉ
408
- contentDiv.appendChild(pre);
409
 
410
- // ์ฝ”๋“œ ์—๋””ํ„ฐ์— ์ฝ”๋“œ ์‚ฝ์ž… ๋ฒ„ํŠผ ์ถ”๊ฐ€ (์„ ํƒ ์‚ฌํ•ญ)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  const insertButton = document.createElement('button');
412
- insertButton.textContent = '์—๋””ํ„ฐ์— ์‚ฝ์ž…';
413
- insertButton.style.marginTop = '8px';
414
- insertButton.style.padding = '5px 10px';
415
- insertButton.style.fontSize = '12px';
416
- insertButton.style.backgroundColor = '#f0f0f0';
417
- insertButton.style.color = '#333';
418
- insertButton.style.border = '1px solid #ccc';
419
- insertButton.style.borderRadius = '4px';
420
- insertButton.style.cursor = 'pointer';
421
  insertButton.onclick = () => {
422
  if (editor) {
423
- editor.setValue(codeContent);
424
  monaco.editor.setModelLanguage(editor.getModel(), lang);
425
- if (lang.toLowerCase() === 'html') {
426
- htmlPreviewEl.srcdoc = codeContent;
427
- }
428
  }
429
  };
430
- contentDiv.appendChild(insertButton);
 
 
 
431
 
432
- } else {
433
- contentDiv.textContent = text;
 
 
 
 
434
  }
435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  messageDiv.appendChild(contentDiv);
437
  chatMessages.appendChild(messageDiv);
438
- chatMessages.scrollTop = chatMessages.scrollHeight; // ํ•ญ์ƒ ์ตœ์‹  ๋ฉ”์‹œ์ง€ ๋ณด์ด๋„๋ก ์Šคํฌ๋กค
439
  }
440
-
441
- // --- ์ฑ„ํŒ… ์ž…๋ ฅ ์ฒ˜๋ฆฌ ---
442
- chatInput.addEventListener('input', () => { // textarea ๋†’์ด ์ž๋™ ์กฐ์ ˆ
443
- chatInput.style.height = 'auto';
444
- chatInput.style.height = (chatInput.scrollHeight) + 'px';
445
- });
446
 
447
- chatInput.addEventListener('keypress', (e) => {
448
- if (e.key === 'Enter' && !e.shiftKey) { // Shift+Enter๋Š” ์ค„๋ฐ”๊ฟˆ
449
- e.preventDefault();
450
- handleSendMessage();
451
- }
452
- });
453
  sendButton.addEventListener('click', handleSendMessage);
454
 
455
  async function handleSendMessage() {
456
  const messageText = chatInput.value.trim();
457
  if (!messageText) return;
458
 
459
- addMessage(messageText, 'user');
460
- chatInput.value = '';
461
- chatInput.style.height = 'auto'; // ๋†’์ด ์ดˆ๊ธฐํ™”
462
- sendButton.disabled = true;
463
- showLoader(chatLoader);
464
 
465
- // AI ์‘๋‹ต ์ฒ˜๋ฆฌ (๊ฐ„๋‹จํ•œ ๊ทœ์น™ ๊ธฐ๋ฐ˜์œผ๋กœ ์–ด๋–ค API ํ˜ธ์ถœํ• ์ง€ ๊ฒฐ์ •)
466
- let endpoint = '/api/generate-code'; // ๊ธฐ๋ณธ์€ ์ฝ”๋“œ ์ƒ์„ฑ
467
  let payload = { prompt: messageText, language: languageSelect.value };
468
- let responseIsCode = true;
469
- let responseLang = languageSelect.value;
470
 
471
- if (messageText.toLowerCase().includes('html ๋งŒ๋“ค์–ด์ค˜') || messageText.toLowerCase().includes('์›นํŽ˜์ด์ง€ ๋งŒ๋“ค์–ด์ค˜') || messageText.toLowerCase().includes('html๋กœ')) {
472
  endpoint = '/api/generate-html';
473
  payload = { prompt: messageText };
474
- responseLang = 'html';
475
- } else if (messageText.toLowerCase().includes('๋‹ค์Œ ์ฝ”๋“œ ์™„์„ฑํ•ด์ค˜') || messageText.toLowerCase().includes('์ฝ”๋“œ ์ฑ„์›Œ์ค˜')) {
476
- // ์ด ๊ฒฝ์šฐ๋Š” FIM ๋ฒ„ํŠผ์„ ์‚ฌ์šฉํ•˜๋„๋ก ์œ ๋„ํ•˜๊ฑฐ๋‚˜, ์—๋””ํ„ฐ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์™€์•ผ ํ•จ.
477
- // ์—ฌ๊ธฐ์„œ๋Š” ์ผ๋ฐ˜ ์ฝ”๋“œ ์ƒ์„ฑ์œผ๋กœ ์ฒ˜๋ฆฌ.
478
- addMessage("AI: ์ฝ”๋“œ ์ž๋™ ์™„์„ฑ(FIM)์€ ์—๋””ํ„ฐ ์œ„์˜ 'AI ์ฝ”๏ฟฝ๏ฟฝ ์ฑ„์šฐ๊ธฐ' ๋ฒ„ํŠผ์„ ์‚ฌ์šฉํ•ด์ฃผ์„ธ์š”.", 'ai');
479
- hideLoader(chatLoader);
480
- sendButton.disabled = false;
481
- return;
482
  }
483
-
484
-
485
  try {
486
  const response = await fetch(endpoint, {
487
  method: 'POST',
488
  headers: { 'Content-Type': 'application/json' },
489
  body: JSON.stringify(payload)
490
  });
491
-
492
- if (!response.ok) {
493
- const errorData = await response.json();
494
- throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
495
- }
496
  const data = await response.json();
497
 
498
- let aiResponseText = '';
499
- if (endpoint === '/api/generate-html') {
500
- aiResponseText = data.html;
501
- addMessage("AI: ์š”์ฒญํ•˜์‹  HTML ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.", 'ai');
502
- addMessage(aiResponseText, 'ai', true, 'html');
503
- if (editor && confirm("์ƒ์„ฑ๋œ HTML์„ ์—๋””ํ„ฐ์— ํ‘œ์‹œํ•˜๊ณ  ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์—…๋ฐ์ดํŠธํ• ๊นŒ์š”?")) {
504
- editor.setValue(aiResponseText);
505
- monaco.editor.setModelLanguage(editor.getModel(), 'html');
506
- htmlPreviewEl.srcdoc = aiResponseText;
507
- } else {
508
- htmlPreviewEl.srcdoc = aiResponseText; // ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋Š” ํ•ญ์ƒ ์—…๋ฐ์ดํŠธ
509
- }
510
- } else if (endpoint === '/api/generate-code') {
511
- aiResponseText = data.code;
512
- addMessage(`AI: ์š”์ฒญํ•˜์‹  ${responseLang} ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.`, 'ai');
513
- addMessage(aiResponseText, 'ai', true, responseLang);
514
- if (editor && confirm(`์ƒ์„ฑ๋œ ${responseLang} ์ฝ”๋“œ๋ฅผ ์—๋””ํ„ฐ์— ํ‘œ์‹œํ• ๊นŒ์š”?`)) {
515
- editor.setValue(aiResponseText);
516
- monaco.editor.setModelLanguage(editor.getModel(), responseLang);
517
- }
518
  }
519
 
 
520
  } catch (error) {
521
  console.error('API Error:', error);
522
- addMessage(`AI: ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์š”์ฒญ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: ${error.message}`, 'ai');
523
  } finally {
524
- hideLoader(chatLoader);
525
- sendButton.disabled = false;
526
  }
527
  }
528
 
529
- // --- FIM ์š”์ฒญ ์ฒ˜๋ฆฌ ---
530
  async function handleFIMRequest() {
531
  if (!editor) return;
532
-
533
- const model = editor.getModel();
534
- const position = editor.getPosition();
535
- const fullCode = model.getValue();
536
- const offset = model.getOffsetAt(position);
537
- const prefix = fullCode.substring(0, offset);
538
- const suffix = fullCode.substring(offset);
539
  const language = languageSelect.value;
540
 
541
- showLoader(completionLoader);
542
- getCompletionBtn.disabled = true;
543
-
544
  try {
545
  const response = await fetch('/api/complete-code', {
546
- method: 'POST',
547
- headers: { 'Content-Type': 'application/json' },
548
  body: JSON.stringify({ prefix, suffix, language })
549
  });
550
- if (!response.ok) {
551
- const errorData = await response.json();
552
- throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
553
- }
554
  const data = await response.json();
555
-
556
- editor.executeEdits("api-fim-completion", [{
557
- range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
558
- text: data.completion
559
- }]);
560
- addMessage(`AI: ์ฝ”๋“œ๋ฅผ ์ฑ„์›Œ ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค. (์—๋””ํ„ฐ ํ™•์ธ)`, 'ai');
561
-
562
-
563
- } catch (error)
564
- {
565
- console.error('FIM Error:', error);
566
- addMessage(`AI: ์ฝ”๋“œ ์ž๋™ ์™„์„ฑ ์ค‘ ์˜ค๋ฅ˜: ${error.message}`, 'ai');
567
- alert(`์ฝ”๋“œ ์ž๋™ ์™„์„ฑ ์ค‘ ์˜ค๋ฅ˜: ${error.message}`);
568
  } finally {
569
- hideLoader(completionLoader);
570
- getCompletionBtn.disabled = false;
571
  }
572
  }
573
-
574
- // ์ดˆ๊ธฐ ๋ฉ”์‹œ์ง€ ์ดํ›„ ์ฒซ ์ž…๋ ฅ ์‹œ ์•ˆ๋‚ด (์„ ํƒ)
575
- // chatInput.addEventListener('focus', () => { ... }, { once: true });
576
-
577
  </script>
578
  </body>
579
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI ์ฝ”๋”ฉ ํŒŒํŠธ๋„ˆ v2</title>
 
7
  <link rel="stylesheet" data-name="vs/editor/editor.main" href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/editor/editor.main.min.css">
 
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
11
  <style>
12
+ /* ์ด์ „ CSS์™€ ๊ฑฐ์˜ ๋™์ผ, ์ผ๋ถ€ ๋ฏธ์„ธ ์กฐ์ • ๋ฐ ์ถ”๊ฐ€ */
13
  :root {
14
+ --bg-color: #f0f2f5;
15
+ --app-bg-color: #ffffff;
16
+ --sidebar-bg-color: #e8e8e8;
17
+ --text-color: #1c1e21;
18
+ --text-secondary-color: #606770;
19
+ --accent-color: #007aff;
20
  --border-color: #d1d1d7;
21
+ --editor-bg: #1e1e1e; /* Monaco 'vs-dark' ๋ฐฐ๊ฒฝ๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ */
22
+ --code-text-color: #d4d4d4; /* Monaco 'vs-dark' ํ…์ŠคํŠธ์™€ ์œ ์‚ฌํ•˜๊ฒŒ */
23
  --font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
24
  --border-radius: 12px;
25
  --box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
26
  --box-shadow-light: 0 1px 3px rgba(0,0,0,0.05);
27
  }
28
 
 
29
  ::-webkit-scrollbar { width: 8px; height: 8px; }
30
  ::-webkit-scrollbar-track { background: transparent; }
31
  ::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; }
 
56
  overflow: hidden;
57
  }
58
 
59
+ .main-content { flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; }
60
+ .chat-editor-area { display: flex; flex-grow: 1; overflow: hidden; }
61
+ .chat-panel { flex: 1; display: flex; flex-direction: column; padding: 20px; border-right: 1px solid var(--border-color); overflow-y: auto; }
62
+ .chat-messages { flex-grow: 1; margin-bottom: 15px; overflow-y: auto; padding-right: 10px; }
63
 
64
+ .message { display: flex; margin-bottom: 15px; max-width: 90%; }
65
+ .message.user { margin-left: auto; flex-direction: row-reverse; }
66
+ .avatar { width: 36px; height: 36px; border-radius: 50%; background-color: var(--accent-color); color: white; display: flex; align-items: center; justify-content: center; font-weight: 500; font-size: 16px; margin-right: 10px; flex-shrink: 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  .message.user .avatar { margin-left: 10px; margin-right: 0; background-color: #6c757d; }
68
+ .message-content { padding: 10px 15px; border-radius: 18px; background-color: #e4e6eb; color: var(--text-color); word-wrap: break-word; }
 
 
 
 
 
 
 
69
  .message.user .message-content { background-color: var(--accent-color); color: white; }
70
+
71
+ .code-block-wrapper { margin-top: 8px; }
72
+ .code-block-header {
73
+ font-size: 0.8em;
74
+ color: var(--text-secondary-color);
75
+ margin-bottom: 4px;
76
+ text-transform: uppercase;
77
+ }
78
+ .message-content pre {
79
+ background-color: var(--editor-bg) !important;
80
+ color: var(--code-text-color) !important;
81
+ padding: 12px 15px;
82
  border-radius: 8px;
83
  overflow-x: auto;
84
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
85
  font-size: 0.9em;
86
+ white-space: pre-wrap;
87
+ word-break: break-all;
88
+ border: 1px solid #3a3a3a; /* ์–ด๋‘์šด ํ…Œ๋งˆ์— ๋งž๋Š” ๋ณด๋” */
89
  }
90
+ .insert-code-button {
91
+ display: block; /* ๋ฒ„ํŠผ์„ ์ฝ”๋“œ ๋ธ”๋ก ์•„๋ž˜์— ์˜ค๋„๋ก */
92
+ margin-top: 8px;
93
+ padding: 6px 12px;
94
+ font-size: 12px;
95
+ background-color: #555; /* ์–ด๋‘์šด ๋ฐฐ๊ฒฝ์— ์–ด์šธ๋ฆฌ๊ฒŒ */
96
+ color: #eee;
97
+ border: 1px solid #666;
98
+ border-radius: 4px;
99
+ cursor: pointer;
100
+ transition: background-color 0.2s;
101
  }
102
+ .insert-code-button:hover { background-color: #666; }
103
 
104
 
105
+ .chat-input-area { display: flex; align-items: center; padding-top: 15px; border-top: 1px solid var(--border-color); }
106
+ .chat-input-area textarea { flex-grow: 1; padding: 10px 15px; border: 1px solid var(--border-color); border-radius: 18px; resize: none; font-family: var(--font-family); font-size: 15px; min-height: 22px; max-height: 100px; line-height: 1.4; margin-right: 10px; box-sizing: border-box; overflow-y: auto; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  .chat-input-area textarea:focus { border-color: var(--accent-color); outline: none; box-shadow: 0 0 0 2px rgba(0,122,255,0.2); }
108
+ .chat-input-area button { background-color: var(--accent-color); color: white; border: none; border-radius: 50%; width: 40px; height: 40px; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  .chat-input-area button:hover { background-color: #0056b3; }
110
  .chat-input-area button:disabled { background-color: #b0c4de; cursor: not-allowed; }
111
 
112
+ .editor-preview-area { flex: 2; display: flex; flex-direction: column; overflow: hidden; }
113
+ .editor-wrapper { flex-basis: 60%; min-height: 200px; padding: 10px; background-color: #f9f9f9; display: flex; flex-direction: column; }
114
+ #editor-container { width: 100% !important; height: 100% !important; border: 1px solid var(--border-color); border-radius: var(--border-radius); overflow: hidden; }
115
+ .editor-controls { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; }
116
+ .editor-controls select, .editor-controls button { padding: 6px 12px; border-radius: 6px; border: 1px solid var(--border-color); background-color: white; font-family: var(--font-family); font-size: 13px; cursor: pointer; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  .editor-controls button { background-color: #e9ecef; color: var(--text-color); }
118
  .editor-controls button:hover { background-color: #d1d5db; }
119
 
120
+ .preview-wrapper { flex-basis: 40%; min-height: 150px; border-top: 1px solid var(--border-color); padding: 10px; background-color: #f9f9f9; display: flex; flex-direction: column; }
121
+ .preview-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
122
+ .preview-header h3 { margin: 0; font-size: 14px; font-weight: 500; color: var(--text-secondary-color); }
123
+ .preview-header .preview-type { font-size: 0.8em; padding: 3px 6px; background-color: #e0e0e0; border-radius: 4px; }
124
+ #code-preview-container { /* HTML iframe๊ณผ ์ฝ”๋“œ ํ‘œ์‹œ <pre>๋ฅผ ๋‹ด์„ ์ปจํ…Œ์ด๋„ˆ */
125
+ flex-grow: 1;
 
 
 
 
 
 
 
 
 
 
 
 
126
  width: 100%;
127
  border: 1px solid var(--border-color);
128
  border-radius: var(--border-radius);
129
  background-color: white;
130
+ overflow: auto; /* ๋‚ด์šฉ์ด ๊ธธ๋ฉด ์Šคํฌ๋กค */
131
+ position: relative; /* iframe ์šฉ */
132
  }
133
+ #html-preview-iframe { /* ๊ธฐ์กด iframe์˜ ID ๋ณ€๊ฒฝ */
134
+ width: 100%;
135
+ height: 100%;
136
+ border: none; /* ์ปจํ…Œ์ด๋„ˆ์— ๋ณด๋”๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ iframe ์ž์ฒด ๋ณด๋”๋Š” ์ œ๊ฑฐ */
137
+ display: none; /* ๊ธฐ๋ณธ ์ˆจ๊น€, HTML์ผ ๋•Œ๋งŒ ํ‘œ์‹œ */
 
 
 
138
  }
139
+ #code-display-pre { /* HTML ์•„๋‹Œ ์ฝ”๋“œ ํ‘œ์‹œ์šฉ pre */
140
+ width: 100%;
141
+ height: 100%;
142
+ margin: 0;
143
+ padding: 15px;
144
+ box-sizing: border-box;
145
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
146
+ font-size: 0.9em;
147
+ white-space: pre-wrap;
148
+ word-break: break-all;
149
+ background-color: var(--editor-bg); /* ์—๋””ํ„ฐ์™€ ์œ ์‚ฌํ•œ ๋ฐฐ๊ฒฝ */
150
+ color: var(--code-text-color); /* ์—๋””ํ„ฐ์™€ ์œ ์‚ฌํ•œ ํ…์ŠคํŠธ ์ƒ‰์ƒ */
151
+ border-radius: var(--border-radius); /* ์ปจํ…Œ์ด๋„ˆ์™€ ๋งž์ถค */
152
+ display: none; /* ๊ธฐ๋ณธ ์ˆจ๊น€, HTML ์•„๋‹ ๋•Œ ํ‘œ์‹œ */
153
  }
 
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
+ .loader { display: flex; align-items: center; font-size: 13px; color: var(--text-secondary-color); margin-left: 10px; }
157
+ .loader.inline-editor { margin-top: 5px; justify-content: center; }
158
+ .spinner { width: 16px; height: 16px; border: 2px solid var(--accent-color); border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; margin-right: 8px; }
159
+ @keyframes spin { to { transform: rotate(360deg); } }
160
+
161
+ @media (max-width: 1024px) { .app-container { flex-direction: column; height: auto; max-height: none; } .chat-editor-area { flex-direction: column; } .chat-panel { border-right: none; border-bottom: 1px solid var(--border-color); max-height: 50vh; } .editor-preview-area { flex: 1; } }
162
+ @media (max-width: 768px) { body { padding: 0; } .app-container { width: 100%; height: 100vh; border-radius: 0; } .chat-panel { padding: 15px; } .editor-wrapper, .preview-wrapper { padding: 8px; } .chat-input-area textarea { font-size: 14px; } .chat-input-area button { width: 36px; height: 36px; font-size: 18px; } }
163
  </style>
164
  </head>
165
  <body>
166
  <div class="app-container">
 
167
  <div class="main-content">
168
  <div class="chat-editor-area">
169
  <div class="chat-panel">
170
  <div class="chat-messages" id="chatMessages">
171
+ <div class="message ai"><div class="avatar">AI</div><div class="message-content">์•ˆ๋…•ํ•˜์„ธ์š”! ์ฝ”๋”ฉ ํŒŒํŠธ๋„ˆ์ž…๋‹ˆ๋‹ค. ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?</div></div>
 
 
 
 
 
 
172
  </div>
173
  <div class="chat-input-area">
174
  <textarea id="chatInput" placeholder="์—ฌ๊ธฐ์— ๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”..." rows="1"></textarea>
175
+ <button id="sendButton" title="์ „์†ก"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg></button>
 
 
176
  <div class="loader" id="chatLoader" style="display: none;"><div class="spinner"></div>์ฒ˜๋ฆฌ ์ค‘...</div>
177
  </div>
178
  </div>
 
179
  <div class="editor-preview-area">
180
  <div class="editor-wrapper">
181
  <div class="editor-controls">
182
  <select id="languageSelect">
183
+ <option value="html">HTML</option><option value="css">CSS</option><option value="javascript" selected>JavaScript</option><option value="python">Python</option><option value="java">Java</option><option value="csharp">C#</option>
 
 
 
 
 
184
  </select>
185
  <button id="getCompletionBtn">AI ์ฝ”๋“œ ์ฑ„์šฐ๊ธฐ (FIM)</button>
186
  </div>
 
188
  <div class="loader inline-editor" id="completionLoader" style="display: none;"><div class="spinner"></div>AI๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ฑ„์šฐ๋Š” ์ค‘...</div>
189
  </div>
190
  <div class="preview-wrapper">
191
+ <div class="preview-header">
192
+ <h3>๋ฏธ๋ฆฌ๋ณด๊ธฐ / ์ฝ”๋“œ ํ‘œ์‹œ</h3>
193
+ <span id="previewTypeBadge" class="preview-type">N/A</span>
194
+ </div>
195
+ <div id="code-preview-container">
196
+ <iframe id="html-preview-iframe" sandbox="allow-scripts allow-same-origin"></iframe>
197
+ <pre id="code-display-pre"></pre>
198
+ </div>
199
  </div>
200
  </div>
201
  </div>
 
210
  const sendButton = document.getElementById('sendButton');
211
  const languageSelect = document.getElementById('languageSelect');
212
  const getCompletionBtn = document.getElementById('getCompletionBtn');
213
+
214
+ const htmlPreviewIframe = document.getElementById('html-preview-iframe');
215
+ const codeDisplayPre = document.getElementById('code-display-pre');
216
+ const previewTypeBadge = document.getElementById('previewTypeBadge');
217
+
218
  const chatLoader = document.getElementById('chatLoader');
219
  const completionLoader = document.getElementById('completionLoader');
220
 
 
221
  function showLoader(loaderElement) { if (loaderElement) loaderElement.style.display = 'flex'; }
222
  function hideLoader(loaderElement) { if (loaderElement) loaderElement.style.display = 'none'; }
223
 
 
224
  require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs' }});
225
  require(['vs/editor/editor.main'], function() {
226
  editor = monaco.editor.create(document.getElementById('editor-container'), {
227
+ value: '// ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ AI์—๊ฒŒ ์š”์ฒญํ•˜์„ธ์š”.',
228
  language: languageSelect.value,
229
+ theme: 'vs-dark', automaticLayout: true, wordWrap: 'on', minimap: { enabled: true }, fontSize: 14,
230
+ lineNumbers: 'on', roundedSelection: false, scrollBeyondLastLine: false, readOnly: false,
 
 
 
 
 
 
 
231
  padding: { top: 10, bottom: 10 }
232
  });
233
+ languageSelect.addEventListener('change', () => monaco.editor.setModelLanguage(editor.getModel(), languageSelect.value));
 
 
 
 
 
 
234
  getCompletionBtn.addEventListener('click', handleFIMRequest);
235
  });
236
 
237
+ function displayInPreview(code, lang) {
238
+ htmlPreviewIframe.style.display = 'none';
239
+ codeDisplayPre.style.display = 'none';
240
+ previewTypeBadge.textContent = lang.toUpperCase();
241
+
242
+ if (lang.toLowerCase() === 'html') {
243
+ htmlPreviewIframe.srcdoc = code;
244
+ htmlPreviewIframe.style.display = 'block';
245
+ } else if (lang.toLowerCase() === 'css') {
246
+ // CSS๋Š” iframe ๋‚ด๋ถ€์˜ ๊ธฐ๋ณธ HTML์— ์ ์šฉํ•˜๊ฑฐ๋‚˜, ์ฝ”๋“œ ์ž์ฒด๋ฅผ ํ‘œ์‹œ
247
+ // ์—ฌ๊ธฐ์„œ๋Š” ์ฝ”๋“œ ์ž์ฒด๋ฅผ ํ‘œ์‹œํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋ฉด ์—๋””ํ„ฐ์— ๋„ฃ์–ด ํ…Œ์ŠคํŠธํ•˜๋„๋ก ์œ ๋„
248
+ codeDisplayPre.textContent = code;
249
+ codeDisplayPre.style.display = 'block';
250
+ // ๋งŒ์•ฝ iframe์˜ HTML์— ์ ์šฉํ•˜๋ ค๋ฉด:
251
+ // const doc = htmlPreviewIframe.contentWindow.document;
252
+ // let styleElement = doc.getElementById('ai-generated-style');
253
+ // if (!styleElement) {
254
+ // styleElement = doc.createElement('style');
255
+ // styleElement.id = 'ai-generated-style';
256
+ // doc.head.appendChild(styleElement);
257
+ // }
258
+ // styleElement.textContent = code;
259
+ // htmlPreviewIframe.style.display = 'block'; // iframe์„ ๋ณด์—ฌ์ค˜์•ผ ํ•จ
260
+ } else if (lang.toLowerCase() === 'javascript') {
261
+ // JS๋Š” ์‹คํ–‰ ์œ„ํ—˜ ๋•Œ๋ฌธ์— ์ฝ”๋“œ ์ž์ฒด๋งŒ ํ‘œ์‹œ
262
+ codeDisplayPre.textContent = code;
263
+ codeDisplayPre.style.display = 'block';
264
+ } else { // ๊ธฐํƒ€ ์–ธ์–ด
265
+ codeDisplayPre.textContent = code;
266
+ codeDisplayPre.style.display = 'block';
267
+ }
268
+ }
269
+
270
+ function parseAndAddCodeBlocks(rawText, sender) {
271
+ const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
272
+ let lastIndex = 0;
273
+ let match;
274
+ let hasCodeBlocks = false;
275
+
276
  const messageDiv = document.createElement('div');
277
  messageDiv.classList.add('message', sender);
 
278
  const avatarDiv = document.createElement('div');
279
  avatarDiv.classList.add('avatar');
280
  avatarDiv.textContent = sender === 'user' ? '๋‚˜' : 'AI';
281
  messageDiv.appendChild(avatarDiv);
 
282
  const contentDiv = document.createElement('div');
283
  contentDiv.classList.add('message-content');
284
+ messageDiv.appendChild(contentDiv);
285
 
286
+ while ((match = codeBlockRegex.exec(rawText)) !== null) {
287
+ hasCodeBlocks = true;
288
+ // ์ฝ”๋“œ ๋ธ”๋ก ์ด์ „์˜ ํ…์ŠคํŠธ ์ถ”๊ฐ€ (์ผ๋ฐ˜ ๋ฉ”์‹œ์ง€)
289
+ if (match.index > lastIndex) {
290
+ const textNode = document.createTextNode(rawText.substring(lastIndex, match.index).trim());
291
+ if (textNode.textContent) { // ๋นˆ ํ…์ŠคํŠธ ๋…ธ๋“œ ์ถ”๊ฐ€ ๋ฐฉ์ง€
292
+ const p = document.createElement('p');
293
+ p.appendChild(textNode);
294
+ contentDiv.appendChild(p);
295
+ }
 
296
  }
 
 
297
 
298
+ const lang = match[1] || 'plaintext';
299
+ const code = match[2].trim();
300
+
301
+ const codeBlockWrapper = document.createElement('div');
302
+ codeBlockWrapper.classList.add('code-block-wrapper');
303
+
304
+ const langHeader = document.createElement('div');
305
+ langHeader.classList.add('code-block-header');
306
+ langHeader.textContent = lang;
307
+ codeBlockWrapper.appendChild(langHeader);
308
+
309
+ const pre = document.createElement('pre');
310
+ pre.textContent = code;
311
+ codeBlockWrapper.appendChild(pre);
312
+
313
  const insertButton = document.createElement('button');
314
+ insertButton.classList.add('insert-code-button');
315
+ insertButton.textContent = `${lang.toUpperCase()} ์—๋””ํ„ฐ์— ์‚ฝ์ž…`;
 
 
 
 
 
 
 
316
  insertButton.onclick = () => {
317
  if (editor) {
318
+ editor.setValue(code);
319
  monaco.editor.setModelLanguage(editor.getModel(), lang);
320
+ languageSelect.value = lang.toLowerCase(); // ๋“œ๋กญ๋‹ค์šด๋„ ๋™๊ธฐํ™”
321
+ displayInPreview(code, lang); // ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋„ ์—…๋ฐ์ดํŠธ
 
322
  }
323
  };
324
+ codeBlockWrapper.appendChild(insertButton);
325
+ contentDiv.appendChild(codeBlockWrapper);
326
+ lastIndex = codeBlockRegex.lastIndex;
327
+ }
328
 
329
+ // ๋งˆ์ง€๋ง‰ ์ฝ”๋“œ ๋ธ”๋ก ์ดํ›„์˜ ํ…์ŠคํŠธ ์ถ”๊ฐ€
330
+ if (lastIndex < rawText.length && rawText.substring(lastIndex).trim()) {
331
+ const textNode = document.createTextNode(rawText.substring(lastIndex).trim());
332
+ const p = document.createElement('p');
333
+ p.appendChild(textNode);
334
+ contentDiv.appendChild(p);
335
  }
336
 
337
+ // ์ฝ”๋“œ ๋ธ”๋ก์ด ํ•˜๋‚˜๋„ ์—†์—ˆ๊ณ , ์ „์ฒด๊ฐ€ ์ผ๋ฐ˜ ํ…์ŠคํŠธ์ธ ๊ฒฝ์šฐ
338
+ if (!hasCodeBlocks && rawText.trim()) {
339
+ contentDiv.textContent = rawText;
340
+ }
341
+
342
+ // ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ์ด ์žˆ์„ ๋•Œ๋งŒ ์ฑ„ํŒ…์ฐฝ์— ์ถ”๊ฐ€
343
+ if (contentDiv.hasChildNodes() || contentDiv.textContent.trim()) {
344
+ chatMessages.appendChild(messageDiv);
345
+ chatMessages.scrollTop = chatMessages.scrollHeight;
346
+ }
347
+ }
348
+
349
+
350
+ function addTextMessage(text, sender) { // ์ผ๋ฐ˜ ํ…์ŠคํŠธ ๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€์šฉ (์ฝ”๋“œ ๋ธ”๋ก ํŒŒ์‹ฑ ์•ˆํ•จ)
351
+ const messageDiv = document.createElement('div');
352
+ messageDiv.classList.add('message', sender);
353
+ const avatarDiv = document.createElement('div');
354
+ avatarDiv.classList.add('avatar');
355
+ avatarDiv.textContent = sender === 'user' ? '๋‚˜' : 'AI';
356
+ messageDiv.appendChild(avatarDiv);
357
+ const contentDiv = document.createElement('div');
358
+ contentDiv.classList.add('message-content');
359
+ contentDiv.textContent = text;
360
  messageDiv.appendChild(contentDiv);
361
  chatMessages.appendChild(messageDiv);
362
+ chatMessages.scrollTop = chatMessages.scrollHeight;
363
  }
 
 
 
 
 
 
364
 
365
+
366
+ chatInput.addEventListener('input', () => { chatInput.style.height = 'auto'; chatInput.style.height = (chatInput.scrollHeight) + 'px'; });
367
+ chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } });
 
 
 
368
  sendButton.addEventListener('click', handleSendMessage);
369
 
370
  async function handleSendMessage() {
371
  const messageText = chatInput.value.trim();
372
  if (!messageText) return;
373
 
374
+ addTextMessage(messageText, 'user'); // ์‚ฌ์šฉ์ž์˜ ๋ฉ”์‹œ์ง€๋Š” ์ผ๋ฐ˜ ํ…์ŠคํŠธ๋กœ ๋ฐ”๋กœ ์ถ”๊ฐ€
375
+ chatInput.value = ''; chatInput.style.height = 'auto';
376
+ sendButton.disabled = true; showLoader(chatLoader);
 
 
377
 
378
+ let endpoint = '/api/generate-code';
 
379
  let payload = { prompt: messageText, language: languageSelect.value };
380
+ let defaultResponseLang = languageSelect.value;
 
381
 
382
+ if (messageText.toLowerCase().includes('html') || messageText.toLowerCase().includes('์›นํŽ˜์ด์ง€')) {
383
  endpoint = '/api/generate-html';
384
  payload = { prompt: messageText };
385
+ defaultResponseLang = 'html';
 
 
 
 
 
 
 
386
  }
387
+
 
388
  try {
389
  const response = await fetch(endpoint, {
390
  method: 'POST',
391
  headers: { 'Content-Type': 'application/json' },
392
  body: JSON.stringify(payload)
393
  });
394
+ if (!response.ok) { const errData = await response.json(); throw new Error(errData.error || `HTTP ${response.status}`); }
 
 
 
 
395
  const data = await response.json();
396
 
397
+ let aiRawResponseText = '';
398
+ if (endpoint === '/api/generate-html') aiRawResponseText = data.html || "<!-- HTML ์ƒ์„ฑ ์‹คํŒจ -->";
399
+ else if (endpoint === '/api/generate-code') aiRawResponseText = data.code || `// ์ฝ”๋“œ ์ƒ์„ฑ ์‹คํŒจ (${defaultResponseLang})`;
400
+
401
+ parseAndAddCodeBlocks(aiRawResponseText, 'ai'); // AI ์‘๋‹ต์€ ์ฝ”๋“œ ๋ธ”๋ก ํŒŒ์‹ฑ
402
+
403
+ // ์ฒซ ๋ฒˆ์งธ ์ฝ”๋“œ ๋ธ”๋ก์„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์— ํ‘œ์‹œ (๋งŒ์•ฝ ์žˆ๋‹ค๋ฉด)
404
+ const firstCodeBlockMatch = aiRawResponseText.match(/```(\w*)\n([\s\S]*?)```/);
405
+ if (firstCodeBlockMatch) {
406
+ const lang = firstCodeBlockMatch[1] || defaultResponseLang;
407
+ const code = firstCodeBlockMatch[2].trim();
408
+ displayInPreview(code, lang);
409
+ } else if (endpoint === '/api/generate-html' && aiRawResponseText.startsWith("<!DOCTYPE html")) { // ์ˆœ์ˆ˜ HTML ์‘๋‹ต
410
+ displayInPreview(aiRawResponseText, 'html');
 
 
 
 
 
 
411
  }
412
 
413
+
414
  } catch (error) {
415
  console.error('API Error:', error);
416
+ addTextMessage(`AI: ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`, 'ai');
417
  } finally {
418
+ hideLoader(chatLoader); sendButton.disabled = false;
 
419
  }
420
  }
421
 
 
422
  async function handleFIMRequest() {
423
  if (!editor) return;
424
+ const model = editor.getModel(); const position = editor.getPosition();
425
+ const fullCode = model.getValue(); const offset = model.getOffsetAt(position);
426
+ const prefix = fullCode.substring(0, offset); const suffix = fullCode.substring(offset);
 
 
 
 
427
  const language = languageSelect.value;
428
 
429
+ showLoader(completionLoader); getCompletionBtn.disabled = true;
 
 
430
  try {
431
  const response = await fetch('/api/complete-code', {
432
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
 
433
  body: JSON.stringify({ prefix, suffix, language })
434
  });
435
+ if (!response.ok) { const errData = await response.json(); throw new Error(errData.error || `HTTP ${response.status}`);}
 
 
 
436
  const data = await response.json();
437
+ editor.executeEdits("api-fim", [{ range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), text: data.completion }]);
438
+ addTextMessage(`AI: ์ฝ”๋“œ๋ฅผ ์ฑ„์› ์Šต๋‹ˆ๋‹ค (์—๋””ํ„ฐ ํ™•์ธ).`, 'ai');
439
+ } catch (error) {
440
+ console.error('FIM Error:', error); addTextMessage(`AI: FIM ์˜ค๋ฅ˜: ${error.message}`, 'ai');
 
 
 
 
 
 
 
 
 
441
  } finally {
442
+ hideLoader(completionLoader); getCompletionBtn.disabled = false;
 
443
  }
444
  }
445
+ displayInPreview("<!-- ์—ฌ๊ธฐ์— ์ฝ”๋“œ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค -->", "N/A"); // ์ดˆ๊ธฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ƒํƒœ
 
 
 
446
  </script>
447
  </body>
448
  </html>