Spaces:
Running
Running
<html lang="ko"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI μ½λ© ννΈλ</title> | |
<!-- Monaco Editor λ‘λ CSS --> | |
<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"> | |
<!-- Google Fonts (macOS λλ ν°νΈ) --> | |
<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> | |
:root { | |
--bg-color: #f0f2f5; /* λ°μ νμ λ°°κ²½ */ | |
--app-bg-color: #ffffff; /* μ± λ°°κ²½ */ | |
--sidebar-bg-color: #e8e8e8; /* μ¬μ΄λλ° λ°°κ²½ (λ λ°κ²) */ | |
--text-color: #1c1e21; /* κΈ°λ³Έ ν μ€νΈ */ | |
--text-secondary-color: #606770; /* 보쑰 ν μ€νΈ */ | |
--accent-color: #007aff; /* macOS λΈλ£¨ */ | |
--border-color: #d1d1d7; | |
--editor-bg: #282c34; /* μ΄λμ΄ μλν° λ°°κ²½ */ | |
--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; | |
} | |
/* μ¬μ΄λλ° (μ ν μ¬ν - μ¬κΈ°μλ μ±ν , μλν°, 미리보기λ₯Ό μ£Ό μμμΌλ‘) */ | |
/* .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; } */ | |
.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; /* AI λ©μμ§ λ°°κ²½ */ | |
color: var(--text-color); | |
word-wrap: break-word; /* κΈ΄ λ¨μ΄ μ€λ°κΏ */ | |
} | |
.message.user .message-content { background-color: var(--accent-color); color: white; } | |
.message-content pre { /* μ½λ λΈλ‘ μ€νμΌ */ | |
background-color: var(--editor-bg) ; /* Monaco ν λ§μ μ μ¬νκ² */ | |
color: #abb2bf ; /* Monaco κΈ°λ³Έ ν μ€νΈ μμ */ | |
padding: 10px; | |
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; /* κΈ΄ μ½λ μ€λ°κΏ */ | |
} | |
/* Monaco μλν°κ° λ λλ§ν μ½λ λΈλ‘μ λν μ€νμΌ (νμ μ) */ | |
.monaco-editor-code-block { | |
border-radius: 8px ; | |
box-shadow: var(--box-shadow-light); | |
} | |
.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; /* 1μ€ λμ΄ */ | |
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; /* Monaco μλν°μ μ체 μ€ν¬λ‘€ μ¬μ© */ | |
} | |
.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; /* λ΄λΆ iframe ν¬κΈ° μ‘°μ μν΄ */ | |
flex-direction: column; | |
} | |
.preview-wrapper h3 { | |
margin: 0 0 8px 0; | |
font-size: 14px; | |
font-weight: 500; | |
color: var(--text-secondary-color); | |
} | |
#html-preview { | |
flex-grow: 1; /* λ¨μ κ³΅κ° λͺ¨λ μ°¨μ§ */ | |
width: 100%; | |
border: 1px solid var(--border-color); | |
border-radius: var(--border-radius); | |
background-color: white; | |
} | |
/* λ‘λ© μ€νΌλ */ | |
.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="sidebar">Sidebar Content (Optional)</div> --> | |
<div class="main-content"> | |
<div class="chat-editor-area"> | |
<div class="chat-panel"> | |
<div class="chat-messages" id="chatMessages"> | |
<!-- AI μμ λ©μμ§ --> | |
<div class="message ai"> | |
<div class="avatar">AI</div> | |
<div class="message-content"> | |
μλ νμΈμ! 무μμ λμλ릴κΉμ? HTML μμ±, μ½λ μμ±, λλ κΈ°μ‘΄ μ½λ μλ μμ±μ μμ²νμ€ μ μμ΅λλ€. | |
</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"> | |
<h3>HTML 미리보기</h3> | |
<iframe id="html-preview" sandbox="allow-scripts allow-same-origin"></iframe> | |
</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 htmlPreviewEl = document.getElementById('html-preview'); | |
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'; } | |
// --- Monaco Editor μ΄κΈ°ν --- | |
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μκ² μμ²νμΈμ.\n// μ±ν μ°½μ "μ¬κ°ν λ²νΌ λ§λ€μ΄μ€" κ°μ΄ μ λ ₯ν΄λ³΄μΈμ.', | |
language: languageSelect.value, | |
theme: 'vs-dark', // μ΄λμ΄ ν λ§ | |
automaticLayout: true, | |
wordWrap: 'on', | |
minimap: { enabled: true, scale: 1 }, | |
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); | |
}); | |
// FIM λ²νΌ ν΄λ¦ μ΄λ²€νΈ | |
getCompletionBtn.addEventListener('click', handleFIMRequest); | |
}); | |
// --- μ±ν λ©μμ§ μ²λ¦¬ --- | |
function addMessage(text, sender, isCode = false, lang = 'plaintext') { | |
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'); | |
if (isCode) { | |
// μ½λλ₯Ό Monaco Editorλ₯Ό μ¬μ©ν΄ λ λλ§ (μ½κΈ° μ μ©) | |
const pre = document.createElement('pre'); | |
// κ°λ¨ν ν μ€νΈλ‘ λ£κ±°λ, μλ‘μ΄ Monaco μΈμ€ν΄μ€λ₯Ό λ§λ€ μ μμ. | |
// μ¬κΈ°μλ κ°λ¨ν pre νκ·Έλ‘ μ²λ¦¬νκ³ , CSSλ‘ μ€νμΌλ§. | |
// μ€μ λ‘λ AI μλ΅μμ ```html ... ``` κ°μ λ§ν¬λ€μ΄ μ½λλΈλ‘ νμ± νμ | |
let codeContent = text; | |
const codeBlockMatch = text.match(/```(\w*)\n([\s\S]*?)```/); | |
if (codeBlockMatch) { | |
lang = codeBlockMatch[1] || lang; | |
codeContent = codeBlockMatch[2]; | |
} | |
pre.textContent = codeContent; // μ€μ λ‘λ highlight.js λ±μΌλ‘ νμ΄λΌμ΄ν μ μ© | |
contentDiv.appendChild(pre); | |
// μ½λ μλν°μ μ½λ μ½μ λ²νΌ μΆκ° (μ ν μ¬ν) | |
const insertButton = document.createElement('button'); | |
insertButton.textContent = 'μλν°μ μ½μ '; | |
insertButton.style.marginTop = '8px'; | |
insertButton.style.padding = '5px 10px'; | |
insertButton.style.fontSize = '12px'; | |
insertButton.style.backgroundColor = '#f0f0f0'; | |
insertButton.style.color = '#333'; | |
insertButton.style.border = '1px solid #ccc'; | |
insertButton.style.borderRadius = '4px'; | |
insertButton.style.cursor = 'pointer'; | |
insertButton.onclick = () => { | |
if (editor) { | |
editor.setValue(codeContent); | |
monaco.editor.setModelLanguage(editor.getModel(), lang); | |
if (lang.toLowerCase() === 'html') { | |
htmlPreviewEl.srcdoc = codeContent; | |
} | |
} | |
}; | |
contentDiv.appendChild(insertButton); | |
} else { | |
contentDiv.textContent = text; | |
} | |
messageDiv.appendChild(contentDiv); | |
chatMessages.appendChild(messageDiv); | |
chatMessages.scrollTop = chatMessages.scrollHeight; // νμ μ΅μ λ©μμ§ λ³΄μ΄λλ‘ μ€ν¬λ‘€ | |
} | |
// --- μ±ν μ λ ₯ μ²λ¦¬ --- | |
chatInput.addEventListener('input', () => { // textarea λμ΄ μλ μ‘°μ | |
chatInput.style.height = 'auto'; | |
chatInput.style.height = (chatInput.scrollHeight) + 'px'; | |
}); | |
chatInput.addEventListener('keypress', (e) => { | |
if (e.key === 'Enter' && !e.shiftKey) { // Shift+Enterλ μ€λ°κΏ | |
e.preventDefault(); | |
handleSendMessage(); | |
} | |
}); | |
sendButton.addEventListener('click', handleSendMessage); | |
async function handleSendMessage() { | |
const messageText = chatInput.value.trim(); | |
if (!messageText) return; | |
addMessage(messageText, 'user'); | |
chatInput.value = ''; | |
chatInput.style.height = 'auto'; // λμ΄ μ΄κΈ°ν | |
sendButton.disabled = true; | |
showLoader(chatLoader); | |
// AI μλ΅ μ²λ¦¬ (κ°λ¨ν κ·μΉ κΈ°λ°μΌλ‘ μ΄λ€ API νΈμΆν μ§ κ²°μ ) | |
let endpoint = '/api/generate-code'; // κΈ°λ³Έμ μ½λ μμ± | |
let payload = { prompt: messageText, language: languageSelect.value }; | |
let responseIsCode = true; | |
let responseLang = languageSelect.value; | |
if (messageText.toLowerCase().includes('html λ§λ€μ΄μ€') || messageText.toLowerCase().includes('μΉνμ΄μ§ λ§λ€μ΄μ€') || messageText.toLowerCase().includes('htmlλ‘')) { | |
endpoint = '/api/generate-html'; | |
payload = { prompt: messageText }; | |
responseLang = 'html'; | |
} else if (messageText.toLowerCase().includes('λ€μ μ½λ μμ±ν΄μ€') || messageText.toLowerCase().includes('μ½λ μ±μμ€')) { | |
// μ΄ κ²½μ°λ FIM λ²νΌμ μ¬μ©νλλ‘ μ λνκ±°λ, μλν° λ΄μ©μ κ°μ ΈμμΌ ν¨. | |
// μ¬κΈ°μλ μΌλ° μ½λ μμ±μΌλ‘ μ²λ¦¬. | |
addMessage("AI: μ½λ μλ μμ±(FIM)μ μλν° μμ 'AI μ½λ μ±μ°κΈ°' λ²νΌμ μ¬μ©ν΄μ£ΌμΈμ.", 'ai'); | |
hideLoader(chatLoader); | |
sendButton.disabled = false; | |
return; | |
} | |
try { | |
const response = await fetch(endpoint, { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify(payload) | |
}); | |
if (!response.ok) { | |
const errorData = await response.json(); | |
throw new Error(errorData.error || `HTTP error! status: ${response.status}`); | |
} | |
const data = await response.json(); | |
let aiResponseText = ''; | |
if (endpoint === '/api/generate-html') { | |
aiResponseText = data.html; | |
addMessage("AI: μμ²νμ HTML μ½λμ λλ€.", 'ai'); | |
addMessage(aiResponseText, 'ai', true, 'html'); | |
if (editor && confirm("μμ±λ HTMLμ μλν°μ νμνκ³ λ―Έλ¦¬λ³΄κΈ°λ₯Ό μ λ°μ΄νΈν κΉμ?")) { | |
editor.setValue(aiResponseText); | |
monaco.editor.setModelLanguage(editor.getModel(), 'html'); | |
htmlPreviewEl.srcdoc = aiResponseText; | |
} else { | |
htmlPreviewEl.srcdoc = aiResponseText; // 미리보기λ νμ μ λ°μ΄νΈ | |
} | |
} else if (endpoint === '/api/generate-code') { | |
aiResponseText = data.code; | |
addMessage(`AI: μμ²νμ ${responseLang} μ½λμ λλ€.`, 'ai'); | |
addMessage(aiResponseText, 'ai', true, responseLang); | |
if (editor && confirm(`μμ±λ ${responseLang} μ½λλ₯Ό μλν°μ νμν κΉμ?`)) { | |
editor.setValue(aiResponseText); | |
monaco.editor.setModelLanguage(editor.getModel(), responseLang); | |
} | |
} | |
} catch (error) { | |
console.error('API Error:', error); | |
addMessage(`AI: μ£μ‘ν©λλ€. μμ² μ²λ¦¬ μ€ μ€λ₯κ° λ°μνμ΅λλ€: ${error.message}`, 'ai'); | |
} finally { | |
hideLoader(chatLoader); | |
sendButton.disabled = false; | |
} | |
} | |
// --- FIM μμ² μ²λ¦¬ --- | |
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 errorData = await response.json(); | |
throw new Error(errorData.error || `HTTP error! status: ${response.status}`); | |
} | |
const data = await response.json(); | |
editor.executeEdits("api-fim-completion", [{ | |
range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), | |
text: data.completion | |
}]); | |
addMessage(`AI: μ½λλ₯Ό μ±μ λ£μμ΅λλ€. (μλν° νμΈ)`, 'ai'); | |
} catch (error) | |
{ | |
console.error('FIM Error:', error); | |
addMessage(`AI: μ½λ μλ μμ± μ€ μ€λ₯: ${error.message}`, 'ai'); | |
alert(`μ½λ μλ μμ± μ€ μ€λ₯: ${error.message}`); | |
} finally { | |
hideLoader(completionLoader); | |
getCompletionBtn.disabled = false; | |
} | |
} | |
// μ΄κΈ° λ©μμ§ μ΄ν 첫 μ λ ₯ μ μλ΄ (μ ν) | |
// chatInput.addEventListener('focus', () => { ... }, { once: true }); | |
</script> | |
</body> | |
</html> |