|
|
|
const md = window.markdownit({ |
|
linkify: true, |
|
highlight(code, lang) { |
|
const language = hljs.getLanguage(lang) ? lang : 'plaintext'; |
|
const html = hljs.highlight(code, {language: language, ignoreIllegals: true }).value |
|
return `<pre class="hljs-code-container my-3"><div class="hljs-code-header"><span>${language}</span><button class="hljs-copy-button">Copy</button></div><code class="hljs language-${language}">${html}</code></pre>` |
|
}, |
|
}); |
|
|
|
new ClipboardJS('.hljs-copy-button', { |
|
target: function(trigger) { |
|
console.log(trigger.parentNode.nextElementSibling) |
|
return trigger.parentNode.nextElementSibling; |
|
} |
|
}); |
|
|
|
async function getApiUrl() { |
|
if (getApiUrl.url) return getApiUrl.url; |
|
try { |
|
const response = await fetch("/v1/chat/completions", { |
|
method: "OPTIONS", |
|
}); |
|
if (response.status !== 200) throw new Error(); |
|
getApiUrl.url = "/v1/chat/completions"; |
|
const corsHeaders = ( |
|
response.headers.get("Access-Control-Allow-Headers") || "" |
|
).toLowerCase(); |
|
getApiUrl.tokenRequired = corsHeaders.includes("authorization"); |
|
} catch (e) { |
|
getApiUrl.url = "https://api.openai.com/v1/chat/completions"; |
|
getApiUrl.tokenRequired = true; |
|
} |
|
return getApiUrl.url; |
|
} |
|
|
|
async function postRequest(url, headers, body) { |
|
const response = await fetch(url, { |
|
method: "POST", |
|
headers: headers, |
|
body: JSON.stringify(body), |
|
}); |
|
if (!response.ok) { |
|
throw new Error(await response.text()); |
|
} |
|
return response; |
|
} |
|
|
|
async function readStream(stream, progressCallback) { |
|
const reader = stream.getReader(); |
|
const textDecoder = new TextDecoder('utf-8'); |
|
let responseObj = {}; |
|
|
|
while (true) { |
|
const { done, value } = await reader.read(); |
|
if (done) break; |
|
|
|
const lines = textDecoder.decode(value).split("\n"); |
|
processLines(lines, responseObj, progressCallback); |
|
} |
|
|
|
return responseObj; |
|
} |
|
|
|
function processLines(lines, responseObj, progressCallback) { |
|
for (const line of lines) { |
|
if (line.startsWith("data: ")) { |
|
if (line.includes("[DONE]")) { |
|
return responseObj; |
|
} |
|
try { |
|
const data = JSON.parse(line.slice(6)); |
|
const delta = data.choices[0].delta; |
|
|
|
Object.keys(delta).forEach(key => { |
|
responseObj[key] = (responseObj[key] || "") + delta[key]; |
|
progressCallback(responseObj); |
|
}); |
|
|
|
} catch (e) { |
|
console.log("Error parsing line:", line); |
|
} |
|
} |
|
} |
|
} |
|
|
|
async function complete(messages, token, progressCallback) { |
|
const apiUrl = await getApiUrl(); |
|
const headers = { "Content-Type": "application/json" }; |
|
if (getApiUrl.tokenRequired) { |
|
headers.Authorization = `Bearer ${token}`; |
|
} |
|
|
|
const body = { |
|
model: "gpt-3.5-turbo", |
|
messages: messages, |
|
stream: true, |
|
}; |
|
|
|
const response = await postRequest(apiUrl, headers, body); |
|
return readStream(response.body, progressCallback); |
|
} |
|
|
|
function chatMessage(message) { |
|
return { |
|
scrollToBottom() { |
|
const chatContainer = document.getElementById('chatContainer'); |
|
const main = chatContainer.parentElement; |
|
main.scrollTop = main.scrollHeight; |
|
}, |
|
watchEffect(message) |
|
{ |
|
this.$nextTick(() => { |
|
this.$el.innerHTML = md.render(message); |
|
this.scrollToBottom() |
|
}); |
|
} |
|
} |
|
} |
|
|
|
function chatApp() { |
|
return { |
|
messages: [], |
|
newMessage: '', |
|
|
|
init() { |
|
this.messages = [ |
|
{ role: 'system', content: 'You are a programing assistant. Answer using markdown.' } |
|
]; |
|
|
|
hljs.configure({ |
|
'cssSelector' : 'pre code' |
|
}); |
|
}, |
|
|
|
sendMessage() { |
|
if (this.newMessage.trim() === '') return; |
|
|
|
const userMessage = { role: 'user', content: this.newMessage }; |
|
|
|
|
|
this.messages.push(userMessage); |
|
this.messages.push({ role: 'assistant', content: '' }); |
|
const lastMsgIndex = this.messages.length - 1; |
|
|
|
try { |
|
complete( |
|
this.messages, |
|
'no-token', |
|
(message) => { |
|
if (message.content) |
|
this.messages[lastMsgIndex].content = message.content; |
|
} |
|
); |
|
|
|
} catch (error) { |
|
console.log(error.message); |
|
return; |
|
} finally { |
|
this.newMessage = ''; |
|
} |
|
}, |
|
|
|
clearMessages() { |
|
this.messages = []; |
|
}, |
|
}; |
|
} |
|
|
|
|
|
|