|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function loadLLMs() { |
|
try { |
|
console.log('LLM λͺ©λ‘ λ‘λ μμ'); |
|
|
|
|
|
const response = await fetch('/api/llm'); |
|
|
|
if (!response.ok) { |
|
throw new Error(`HTTP error! status: ${response.status}`); |
|
} |
|
|
|
const data = await response.json(); |
|
supportedLLMs = data.supported_llms; |
|
currentLLM = data.current_llm.id; |
|
|
|
console.log(`λ‘λλ LLM μ: ${supportedLLMs.length}, νμ¬ LLM: ${currentLLM}`); |
|
|
|
|
|
llmSelect.innerHTML = ''; |
|
supportedLLMs.forEach(llm => { |
|
const option = document.createElement('option'); |
|
option.value = llm.id; |
|
option.textContent = llm.name; |
|
option.selected = llm.current; |
|
llmSelect.appendChild(option); |
|
}); |
|
|
|
|
|
updateCurrentLLMInfo(data.current_llm); |
|
console.log('LLM λͺ©λ‘ λ‘λ μλ£'); |
|
} catch (error) { |
|
console.error('LLM λͺ©λ‘ λ‘λ μ€ν¨:', error); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
async function changeLLM(llmId) { |
|
try { |
|
console.log(`LLM λ³κ²½ μμ: ${llmId}`); |
|
|
|
|
|
const response = await fetch('/api/llm', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ llm_id: llmId }) |
|
}); |
|
|
|
if (!response.ok) { |
|
throw new Error(`HTTP error! status: ${response.status}`); |
|
} |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
currentLLM = llmId; |
|
updateCurrentLLMInfo(data.current_llm); |
|
console.log(`LLM λ³κ²½ μ±κ³΅: ${data.current_llm.name}`); |
|
|
|
|
|
const systemMessage = `LLMμ΄ ${data.current_llm.name}(μΌ)λ‘ λ³κ²½λμμ΅λλ€. λͺ¨λΈ: ${data.current_llm.model}`; |
|
addSystemNotification(systemMessage); |
|
} else if (data.error) { |
|
console.error('LLM λ³κ²½ μ€λ₯:', data.error); |
|
alert(`LLM λ³κ²½ μ€λ₯: ${data.error}`); |
|
} |
|
} catch (error) { |
|
console.error('LLM λ³κ²½ μ€ν¨:', error); |
|
alert('LLM λ³κ²½ μ€ μ€λ₯κ° λ°μνμ΅λλ€.'); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function updateCurrentLLMInfo(llmInfo) { |
|
console.log(`νμ¬ LLM μ 보 μ
λ°μ΄νΈ: ${llmInfo.name} (${llmInfo.model})`); |
|
|
|
if (currentLLMInfo) { |
|
currentLLMInfo.textContent = `${llmInfo.name} (${llmInfo.model})`; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
async function sendMessage() { |
|
const message = userInput.value.trim(); |
|
if (!message) return; |
|
|
|
console.log(`λ©μμ§ μ μ‘: "${message}"`); |
|
|
|
|
|
addMessage(message, 'user'); |
|
userInput.value = ''; |
|
adjustTextareaHeight(); |
|
|
|
|
|
const loadingMessageId = addLoadingMessage(); |
|
|
|
try { |
|
console.log('μ±ν
API μμ² μμ'); |
|
|
|
|
|
const response = await fetch('/api/chat', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ |
|
query: message, |
|
llm_id: currentLLM |
|
}) |
|
}); |
|
|
|
if (!response.ok) { |
|
throw new Error(`HTTP error! status: ${response.status}`); |
|
} |
|
|
|
const data = await response.json(); |
|
console.log('μ±ν
API μλ΅ μμ μλ£'); |
|
|
|
|
|
removeLoadingMessage(loadingMessageId); |
|
|
|
|
|
if (data.error) { |
|
console.error('μ±ν
μλ΅ μ€λ₯:', data.error); |
|
addErrorMessage(data.error); |
|
} else { |
|
|
|
if (data.llm) { |
|
updateCurrentLLMInfo(data.llm); |
|
} |
|
console.log('λ΄ μλ΅ νμ'); |
|
addMessage(data.answer, 'bot', null, data.sources); |
|
} |
|
} catch (error) { |
|
console.error('μ±ν
μμ² μ€λ₯:', error); |
|
removeLoadingMessage(loadingMessageId); |
|
addErrorMessage('μ€λ₯κ° λ°μνμ΅λλ€. λ€μ μλν΄ μ£ΌμΈμ.'); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
async function startRecording() { |
|
if (isRecording) return; |
|
|
|
console.log('μμ± λ
Ήμ μμ μμ²'); |
|
|
|
try { |
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); |
|
isRecording = true; |
|
audioChunks = []; |
|
|
|
mediaRecorder = new MediaRecorder(stream); |
|
|
|
mediaRecorder.addEventListener('dataavailable', (event) => { |
|
if (event.data.size > 0) audioChunks.push(event.data); |
|
console.log('μ€λμ€ λ°μ΄ν° μ²ν¬ μμ λ¨'); |
|
}); |
|
|
|
mediaRecorder.addEventListener('stop', sendAudioMessage); |
|
|
|
|
|
mediaRecorder.start(); |
|
console.log('MediaRecorder μμλ¨'); |
|
|
|
|
|
micButton.style.display = 'none'; |
|
recordingStatus.classList.remove('hidden'); |
|
} catch (error) { |
|
console.error('μμ± λ
Ήμ κΆνμ μ»μ μ μμ΅λλ€:', error); |
|
alert('λ§μ΄ν¬ μ κ·Ό κΆνμ΄ νμν©λλ€.'); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
function stopRecording() { |
|
if (!isRecording || !mediaRecorder) return; |
|
|
|
console.log('μμ± λ
Ήμ μ€μ§ μμ²'); |
|
|
|
mediaRecorder.stop(); |
|
isRecording = false; |
|
|
|
|
|
micButton.style.display = 'flex'; |
|
recordingStatus.classList.add('hidden'); |
|
|
|
console.log('MediaRecorder μ€μ§λ¨'); |
|
} |
|
|
|
|
|
|
|
|
|
async function sendAudioMessage() { |
|
if (audioChunks.length === 0) return; |
|
|
|
console.log(`μ€λμ€ λ©μμ§ μ μ‘ μ€λΉ, ${audioChunks.length}κ° μ²ν¬`); |
|
|
|
|
|
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); |
|
|
|
|
|
const loadingMessageId = addLoadingMessage(); |
|
|
|
try { |
|
|
|
const formData = new FormData(); |
|
formData.append('audio', audioBlob, 'recording.wav'); |
|
|
|
formData.append('llm_id', currentLLM); |
|
|
|
console.log('μμ± API μμ² μμ'); |
|
|
|
const response = await fetch('/api/voice', { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
if (!response.ok) { |
|
throw new Error(`HTTP error! status: ${response.status}`); |
|
} |
|
|
|
const data = await response.json(); |
|
console.log('μμ± API μλ΅ μμ μλ£'); |
|
|
|
|
|
removeLoadingMessage(loadingMessageId); |
|
|
|
|
|
if (data.error) { |
|
console.error('μμ± μλ΅ μ€λ₯:', data.error); |
|
addErrorMessage(data.error); |
|
} else { |
|
|
|
if (data.llm) { |
|
updateCurrentLLMInfo(data.llm); |
|
} |
|
|
|
|
|
if (data.transcription) { |
|
console.log(`μμ± μΈμ κ²°κ³Ό: "${data.transcription}"`); |
|
addMessage(data.transcription, 'user'); |
|
} |
|
|
|
|
|
console.log('λ΄ μλ΅ νμ'); |
|
addMessage(data.answer, 'bot', data.transcription, data.sources); |
|
} |
|
} catch (error) { |
|
console.error('μμ± μμ² μ€λ₯:', error); |
|
removeLoadingMessage(loadingMessageId); |
|
addErrorMessage('μ€λμ€ μ²λ¦¬ μ€ μ€λ₯κ° λ°μνμ΅λλ€. λ€μ μλν΄ μ£ΌμΈμ.'); |
|
} |
|
} |
|
|