Spaces:
Running
Running
Update index.html
Browse files- 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 ์ฝ๋ฉ
|
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;
|
21 |
--border-color: #d1d1d7;
|
22 |
-
--editor-bg: #
|
|
|
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 |
-
|
|
|
|
|
62 |
|
63 |
-
.
|
64 |
-
|
65 |
-
|
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 |
-
.
|
129 |
-
|
130 |
-
|
131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
}
|
|
|
144 |
|
145 |
|
146 |
-
.chat-input-area {
|
147 |
-
|
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 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
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-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
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 |
-
|
253 |
-
|
254 |
-
|
255 |
-
font-size: 13px;
|
256 |
-
color: var(--text-secondary-color);
|
257 |
-
margin-left: 10px; /* ์
๋ ฅ์ฐฝ ์์ ํ์๋ ๋ */
|
258 |
}
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
<
|
332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
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์๊ฒ
|
360 |
language: languageSelect.value,
|
361 |
-
theme: 'vs-dark',
|
362 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
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.
|
413 |
-
insertButton.
|
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(
|
424 |
monaco.editor.setModelLanguage(editor.getModel(), lang);
|
425 |
-
|
426 |
-
|
427 |
-
}
|
428 |
}
|
429 |
};
|
430 |
-
|
|
|
|
|
|
|
431 |
|
432 |
-
|
433 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
448 |
-
|
449 |
-
|
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 |
-
|
460 |
-
chatInput.value = '';
|
461 |
-
|
462 |
-
sendButton.disabled = true;
|
463 |
-
showLoader(chatLoader);
|
464 |
|
465 |
-
|
466 |
-
let endpoint = '/api/generate-code'; // ๊ธฐ๋ณธ์ ์ฝ๋ ์์ฑ
|
467 |
let payload = { prompt: messageText, language: languageSelect.value };
|
468 |
-
let
|
469 |
-
let responseLang = languageSelect.value;
|
470 |
|
471 |
-
if (messageText.toLowerCase().includes('html
|
472 |
endpoint = '/api/generate-html';
|
473 |
payload = { prompt: messageText };
|
474 |
-
|
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
|
499 |
-
if (endpoint === '/api/generate-html')
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
} else if (endpoint === '/api/generate-
|
511 |
-
|
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 |
-
|
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 =
|
534 |
-
const
|
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 |
-
|
557 |
-
|
558 |
-
|
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>
|