Spaces:
Running
Running
<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) ; | |
color: var(--code-text-color) ; | |
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% ; height: 100% ; 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> |