code2 / index.html
kimhyunwoo's picture
Update index.html
a7f8baa verified
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI ์ฝ”๋”ฉ ํŒŒํŠธ๋„ˆ v2</title>
<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">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
/* ์ด์ „ CSS์™€ ๊ฑฐ์˜ ๋™์ผ, ์ผ๋ถ€ ๋ฏธ์„ธ ์กฐ์ • ๋ฐ ์ถ”๊ฐ€ */
:root {
--bg-color: #f0f2f5;
--app-bg-color: #ffffff;
--sidebar-bg-color: #e8e8e8;
--text-color: #1c1e21;
--text-secondary-color: #606770;
--accent-color: #007aff;
--border-color: #d1d1d7;
--editor-bg: #1e1e1e; /* Monaco 'vs-dark' ๋ฐฐ๊ฒฝ๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ */
--code-text-color: #d4d4d4; /* Monaco 'vs-dark' ํ…์ŠคํŠธ์™€ ์œ ์‚ฌํ•˜๊ฒŒ */
--font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--border-radius: 12px;
--box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
--box-shadow-light: 0 1px 3px rgba(0,0,0,0.05);
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #a1a1a1; }
body {
font-family: var(--font-family);
margin: 0;
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
box-sizing: border-box;
}
.app-container {
display: flex;
width: 95%;
max-width: 1400px;
height: 90vh;
max-height: 800px;
background-color: var(--app-bg-color);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
overflow: hidden;
}
.main-content { flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; }
.chat-editor-area { display: flex; flex-grow: 1; overflow: hidden; }
.chat-panel { flex: 1; display: flex; flex-direction: column; padding: 20px; border-right: 1px solid var(--border-color); overflow-y: auto; }
.chat-messages { flex-grow: 1; margin-bottom: 15px; overflow-y: auto; padding-right: 10px; }
.message { display: flex; margin-bottom: 15px; max-width: 90%; }
.message.user { margin-left: auto; flex-direction: row-reverse; }
.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; }
.message.user .avatar { margin-left: 10px; margin-right: 0; background-color: #6c757d; }
.message-content { padding: 10px 15px; border-radius: 18px; background-color: #e4e6eb; color: var(--text-color); word-wrap: break-word; }
.message.user .message-content { background-color: var(--accent-color); color: white; }
.code-block-wrapper { margin-top: 8px; }
.code-block-header {
font-size: 0.8em;
color: var(--text-secondary-color);
margin-bottom: 4px;
text-transform: uppercase;
}
.message-content pre {
background-color: var(--editor-bg) !important;
color: var(--code-text-color) !important;
padding: 12px 15px;
border-radius: 8px;
overflow-x: auto;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 0.9em;
white-space: pre-wrap;
word-break: break-all;
border: 1px solid #3a3a3a; /* ์–ด๋‘์šด ํ…Œ๋งˆ์— ๋งž๋Š” ๋ณด๋” */
}
.insert-code-button {
display: block; /* ๋ฒ„ํŠผ์„ ์ฝ”๋“œ ๋ธ”๋ก ์•„๋ž˜์— ์˜ค๋„๋ก */
margin-top: 8px;
padding: 6px 12px;
font-size: 12px;
background-color: #555; /* ์–ด๋‘์šด ๋ฐฐ๊ฒฝ์— ์–ด์šธ๋ฆฌ๊ฒŒ */
color: #eee;
border: 1px solid #666;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.insert-code-button:hover { background-color: #666; }
.chat-input-area { display: flex; align-items: center; padding-top: 15px; border-top: 1px solid var(--border-color); }
.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; }
.chat-input-area textarea:focus { border-color: var(--accent-color); outline: none; box-shadow: 0 0 0 2px rgba(0,122,255,0.2); }
.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; }
.chat-input-area button:hover { background-color: #0056b3; }
.chat-input-area button:disabled { background-color: #b0c4de; cursor: not-allowed; }
.editor-preview-area { flex: 2; display: flex; flex-direction: column; overflow: hidden; }
.editor-wrapper { flex-basis: 60%; min-height: 200px; padding: 10px; background-color: #f9f9f9; display: flex; flex-direction: column; }
#editor-container { width: 100% !important; height: 100% !important; border: 1px solid var(--border-color); border-radius: var(--border-radius); overflow: hidden; }
.editor-controls { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; }
.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; }
.editor-controls button { background-color: #e9ecef; color: var(--text-color); }
.editor-controls button:hover { background-color: #d1d5db; }
.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; }
.preview-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.preview-header h3 { margin: 0; font-size: 14px; font-weight: 500; color: var(--text-secondary-color); }
.preview-header .preview-type { font-size: 0.8em; padding: 3px 6px; background-color: #e0e0e0; border-radius: 4px; }
#code-preview-container { /* HTML iframe๊ณผ ์ฝ”๋“œ ํ‘œ์‹œ <pre>๋ฅผ ๋‹ด์„ ์ปจํ…Œ์ด๋„ˆ */
flex-grow: 1;
width: 100%;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
background-color: white;
overflow: auto; /* ๋‚ด์šฉ์ด ๊ธธ๋ฉด ์Šคํฌ๋กค */
position: relative; /* iframe ์šฉ */
}
#html-preview-iframe { /* ๊ธฐ์กด iframe์˜ ID ๋ณ€๊ฒฝ */
width: 100%;
height: 100%;
border: none; /* ์ปจํ…Œ์ด๋„ˆ์— ๋ณด๋”๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ iframe ์ž์ฒด ๋ณด๋”๋Š” ์ œ๊ฑฐ */
display: none; /* ๊ธฐ๋ณธ ์ˆจ๊น€, HTML์ผ ๋•Œ๋งŒ ํ‘œ์‹œ */
}
#code-display-pre { /* HTML ์•„๋‹Œ ์ฝ”๋“œ ํ‘œ์‹œ์šฉ pre */
width: 100%;
height: 100%;
margin: 0;
padding: 15px;
box-sizing: border-box;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 0.9em;
white-space: pre-wrap;
word-break: break-all;
background-color: var(--editor-bg); /* ์—๋””ํ„ฐ์™€ ์œ ์‚ฌํ•œ ๋ฐฐ๊ฒฝ */
color: var(--code-text-color); /* ์—๋””ํ„ฐ์™€ ์œ ์‚ฌํ•œ ํ…์ŠคํŠธ ์ƒ‰์ƒ */
border-radius: var(--border-radius); /* ์ปจํ…Œ์ด๋„ˆ์™€ ๋งž์ถค */
display: none; /* ๊ธฐ๋ณธ ์ˆจ๊น€, HTML ์•„๋‹ ๋•Œ ํ‘œ์‹œ */
}
.loader { display: flex; align-items: center; font-size: 13px; color: var(--text-secondary-color); margin-left: 10px; }
.loader.inline-editor { margin-top: 5px; justify-content: center; }
.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; }
@keyframes spin { to { transform: rotate(360deg); } }
@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; } }
@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; } }
</style>
</head>
<body>
<div class="app-container">
<div class="main-content">
<div class="chat-editor-area">
<div class="chat-panel">
<div class="chat-messages" id="chatMessages">
<div class="message ai"><div class="avatar">AI</div><div class="message-content">์•ˆ๋…•ํ•˜์„ธ์š”! ์ฝ”๋”ฉ ํŒŒํŠธ๋„ˆ์ž…๋‹ˆ๋‹ค. ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?</div></div>
</div>
<div class="chat-input-area">
<textarea id="chatInput" placeholder="์—ฌ๊ธฐ์— ๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”..." rows="1"></textarea>
<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>
<div class="loader" id="chatLoader" style="display: none;"><div class="spinner"></div>์ฒ˜๋ฆฌ ์ค‘...</div>
</div>
</div>
<div class="editor-preview-area">
<div class="editor-wrapper">
<div class="editor-controls">
<select id="languageSelect">
<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>
</select>
<button id="getCompletionBtn">AI ์ฝ”๋“œ ์ฑ„์šฐ๊ธฐ (FIM)</button>
</div>
<div id="editor-container"></div>
<div class="loader inline-editor" id="completionLoader" style="display: none;"><div class="spinner"></div>AI๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ฑ„์šฐ๋Š” ์ค‘...</div>
</div>
<div class="preview-wrapper">
<div class="preview-header">
<h3>๋ฏธ๋ฆฌ๋ณด๊ธฐ / ์ฝ”๋“œ ํ‘œ์‹œ</h3>
<span id="previewTypeBadge" class="preview-type">N/A</span>
</div>
<div id="code-preview-container">
<iframe id="html-preview-iframe" sandbox="allow-scripts allow-same-origin"></iframe>
<pre id="code-display-pre"></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/loader.min.js"></script>
<script>
let editor;
const chatMessages = document.getElementById('chatMessages');
const chatInput = document.getElementById('chatInput');
const sendButton = document.getElementById('sendButton');
const languageSelect = document.getElementById('languageSelect');
const getCompletionBtn = document.getElementById('getCompletionBtn');
const htmlPreviewIframe = document.getElementById('html-preview-iframe');
const codeDisplayPre = document.getElementById('code-display-pre');
const previewTypeBadge = document.getElementById('previewTypeBadge');
const chatLoader = document.getElementById('chatLoader');
const completionLoader = document.getElementById('completionLoader');
function showLoader(loaderElement) { if (loaderElement) loaderElement.style.display = 'flex'; }
function hideLoader(loaderElement) { if (loaderElement) loaderElement.style.display = 'none'; }
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs' }});
require(['vs/editor/editor.main'], function() {
editor = monaco.editor.create(document.getElementById('editor-container'), {
value: '// ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ AI์—๊ฒŒ ์š”์ฒญํ•˜์„ธ์š”.',
language: languageSelect.value,
theme: 'vs-dark', automaticLayout: true, wordWrap: 'on', minimap: { enabled: true }, fontSize: 14,
lineNumbers: 'on', roundedSelection: false, scrollBeyondLastLine: false, readOnly: false,
padding: { top: 10, bottom: 10 }
});
languageSelect.addEventListener('change', () => monaco.editor.setModelLanguage(editor.getModel(), languageSelect.value));
getCompletionBtn.addEventListener('click', handleFIMRequest);
});
function displayInPreview(code, lang) {
htmlPreviewIframe.style.display = 'none';
codeDisplayPre.style.display = 'none';
previewTypeBadge.textContent = lang.toUpperCase();
if (lang.toLowerCase() === 'html') {
htmlPreviewIframe.srcdoc = code;
htmlPreviewIframe.style.display = 'block';
} else if (lang.toLowerCase() === 'css') {
// CSS๋Š” iframe ๋‚ด๋ถ€์˜ ๊ธฐ๋ณธ HTML์— ์ ์šฉํ•˜๊ฑฐ๋‚˜, ์ฝ”๋“œ ์ž์ฒด๋ฅผ ํ‘œ์‹œ
// ์—ฌ๊ธฐ์„œ๋Š” ์ฝ”๋“œ ์ž์ฒด๋ฅผ ํ‘œ์‹œํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋ฉด ์—๋””ํ„ฐ์— ๋„ฃ์–ด ํ…Œ์ŠคํŠธํ•˜๋„๋ก ์œ ๋„
codeDisplayPre.textContent = code;
codeDisplayPre.style.display = 'block';
// ๋งŒ์•ฝ iframe์˜ HTML์— ์ ์šฉํ•˜๋ ค๋ฉด:
// const doc = htmlPreviewIframe.contentWindow.document;
// let styleElement = doc.getElementById('ai-generated-style');
// if (!styleElement) {
// styleElement = doc.createElement('style');
// styleElement.id = 'ai-generated-style';
// doc.head.appendChild(styleElement);
// }
// styleElement.textContent = code;
// htmlPreviewIframe.style.display = 'block'; // iframe์„ ๋ณด์—ฌ์ค˜์•ผ ํ•จ
} else if (lang.toLowerCase() === 'javascript') {
// JS๋Š” ์‹คํ–‰ ์œ„ํ—˜ ๋•Œ๋ฌธ์— ์ฝ”๋“œ ์ž์ฒด๋งŒ ํ‘œ์‹œ
codeDisplayPre.textContent = code;
codeDisplayPre.style.display = 'block';
} else { // ๊ธฐํƒ€ ์–ธ์–ด
codeDisplayPre.textContent = code;
codeDisplayPre.style.display = 'block';
}
}
function parseAndAddCodeBlocks(rawText, sender) {
const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
let lastIndex = 0;
let match;
let hasCodeBlocks = false;
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', sender);
const avatarDiv = document.createElement('div');
avatarDiv.classList.add('avatar');
avatarDiv.textContent = sender === 'user' ? '๋‚˜' : 'AI';
messageDiv.appendChild(avatarDiv);
const contentDiv = document.createElement('div');
contentDiv.classList.add('message-content');
messageDiv.appendChild(contentDiv);
while ((match = codeBlockRegex.exec(rawText)) !== null) {
hasCodeBlocks = true;
// ์ฝ”๋“œ ๋ธ”๋ก ์ด์ „์˜ ํ…์ŠคํŠธ ์ถ”๊ฐ€ (์ผ๋ฐ˜ ๋ฉ”์‹œ์ง€)
if (match.index > lastIndex) {
const textNode = document.createTextNode(rawText.substring(lastIndex, match.index).trim());
if (textNode.textContent) { // ๋นˆ ํ…์ŠคํŠธ ๋…ธ๋“œ ์ถ”๊ฐ€ ๋ฐฉ์ง€
const p = document.createElement('p');
p.appendChild(textNode);
contentDiv.appendChild(p);
}
}
const lang = match[1] || 'plaintext';
const code = match[2].trim();
const codeBlockWrapper = document.createElement('div');
codeBlockWrapper.classList.add('code-block-wrapper');
const langHeader = document.createElement('div');
langHeader.classList.add('code-block-header');
langHeader.textContent = lang;
codeBlockWrapper.appendChild(langHeader);
const pre = document.createElement('pre');
pre.textContent = code;
codeBlockWrapper.appendChild(pre);
const insertButton = document.createElement('button');
insertButton.classList.add('insert-code-button');
insertButton.textContent = `${lang.toUpperCase()} ์—๋””ํ„ฐ์— ์‚ฝ์ž…`;
insertButton.onclick = () => {
if (editor) {
editor.setValue(code);
monaco.editor.setModelLanguage(editor.getModel(), lang);
languageSelect.value = lang.toLowerCase(); // ๋“œ๋กญ๋‹ค์šด๋„ ๋™๊ธฐํ™”
displayInPreview(code, lang); // ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋„ ์—…๋ฐ์ดํŠธ
}
};
codeBlockWrapper.appendChild(insertButton);
contentDiv.appendChild(codeBlockWrapper);
lastIndex = codeBlockRegex.lastIndex;
}
// ๋งˆ์ง€๋ง‰ ์ฝ”๋“œ ๋ธ”๋ก ์ดํ›„์˜ ํ…์ŠคํŠธ ์ถ”๊ฐ€
if (lastIndex < rawText.length && rawText.substring(lastIndex).trim()) {
const textNode = document.createTextNode(rawText.substring(lastIndex).trim());
const p = document.createElement('p');
p.appendChild(textNode);
contentDiv.appendChild(p);
}
// ์ฝ”๋“œ ๋ธ”๋ก์ด ํ•˜๋‚˜๋„ ์—†์—ˆ๊ณ , ์ „์ฒด๊ฐ€ ์ผ๋ฐ˜ ํ…์ŠคํŠธ์ธ ๊ฒฝ์šฐ
if (!hasCodeBlocks && rawText.trim()) {
contentDiv.textContent = rawText;
}
// ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ์ด ์žˆ์„ ๋•Œ๋งŒ ์ฑ„ํŒ…์ฐฝ์— ์ถ”๊ฐ€
if (contentDiv.hasChildNodes() || contentDiv.textContent.trim()) {
chatMessages.appendChild(messageDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}
function addTextMessage(text, sender) { // ์ผ๋ฐ˜ ํ…์ŠคํŠธ ๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€์šฉ (์ฝ”๋“œ ๋ธ”๋ก ํŒŒ์‹ฑ ์•ˆํ•จ)
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', sender);
const avatarDiv = document.createElement('div');
avatarDiv.classList.add('avatar');
avatarDiv.textContent = sender === 'user' ? '๋‚˜' : 'AI';
messageDiv.appendChild(avatarDiv);
const contentDiv = document.createElement('div');
contentDiv.classList.add('message-content');
contentDiv.textContent = text;
messageDiv.appendChild(contentDiv);
chatMessages.appendChild(messageDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
chatInput.addEventListener('input', () => { chatInput.style.height = 'auto'; chatInput.style.height = (chatInput.scrollHeight) + 'px'; });
chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } });
sendButton.addEventListener('click', handleSendMessage);
async function handleSendMessage() {
const messageText = chatInput.value.trim();
if (!messageText) return;
addTextMessage(messageText, 'user'); // ์‚ฌ์šฉ์ž์˜ ๋ฉ”์‹œ์ง€๋Š” ์ผ๋ฐ˜ ํ…์ŠคํŠธ๋กœ ๋ฐ”๋กœ ์ถ”๊ฐ€
chatInput.value = ''; chatInput.style.height = 'auto';
sendButton.disabled = true; showLoader(chatLoader);
let endpoint = '/api/generate-code';
let payload = { prompt: messageText, language: languageSelect.value };
let defaultResponseLang = languageSelect.value;
if (messageText.toLowerCase().includes('html') || messageText.toLowerCase().includes('์›นํŽ˜์ด์ง€')) {
endpoint = '/api/generate-html';
payload = { prompt: messageText };
defaultResponseLang = 'html';
}
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) { const errData = await response.json(); throw new Error(errData.error || `HTTP ${response.status}`); }
const data = await response.json();
let aiRawResponseText = '';
if (endpoint === '/api/generate-html') aiRawResponseText = data.html || "<!-- HTML ์ƒ์„ฑ ์‹คํŒจ -->";
else if (endpoint === '/api/generate-code') aiRawResponseText = data.code || `// ์ฝ”๋“œ ์ƒ์„ฑ ์‹คํŒจ (${defaultResponseLang})`;
parseAndAddCodeBlocks(aiRawResponseText, 'ai'); // AI ์‘๋‹ต์€ ์ฝ”๋“œ ๋ธ”๋ก ํŒŒ์‹ฑ
// ์ฒซ ๋ฒˆ์งธ ์ฝ”๋“œ ๋ธ”๋ก์„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์— ํ‘œ์‹œ (๋งŒ์•ฝ ์žˆ๋‹ค๋ฉด)
const firstCodeBlockMatch = aiRawResponseText.match(/```(\w*)\n([\s\S]*?)```/);
if (firstCodeBlockMatch) {
const lang = firstCodeBlockMatch[1] || defaultResponseLang;
const code = firstCodeBlockMatch[2].trim();
displayInPreview(code, lang);
} else if (endpoint === '/api/generate-html' && aiRawResponseText.startsWith("<!DOCTYPE html")) { // ์ˆœ์ˆ˜ HTML ์‘๋‹ต
displayInPreview(aiRawResponseText, 'html');
}
} catch (error) {
console.error('API Error:', error);
addTextMessage(`AI: ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`, 'ai');
} finally {
hideLoader(chatLoader); sendButton.disabled = false;
}
}
async function handleFIMRequest() {
if (!editor) return;
const model = editor.getModel(); const position = editor.getPosition();
const fullCode = model.getValue(); const offset = model.getOffsetAt(position);
const prefix = fullCode.substring(0, offset); const suffix = fullCode.substring(offset);
const language = languageSelect.value;
showLoader(completionLoader); getCompletionBtn.disabled = true;
try {
const response = await fetch('/api/complete-code', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prefix, suffix, language })
});
if (!response.ok) { const errData = await response.json(); throw new Error(errData.error || `HTTP ${response.status}`);}
const data = await response.json();
editor.executeEdits("api-fim", [{ range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), text: data.completion }]);
addTextMessage(`AI: ์ฝ”๋“œ๋ฅผ ์ฑ„์› ์Šต๋‹ˆ๋‹ค (์—๋””ํ„ฐ ํ™•์ธ).`, 'ai');
} catch (error) {
console.error('FIM Error:', error); addTextMessage(`AI: FIM ์˜ค๋ฅ˜: ${error.message}`, 'ai');
} finally {
hideLoader(completionLoader); getCompletionBtn.disabled = false;
}
}
displayInPreview("<!-- ์—ฌ๊ธฐ์— ์ฝ”๋“œ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค -->", "N/A"); // ์ดˆ๊ธฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ƒํƒœ
</script>
</body>
</html>