Skip to content

Commit

Permalink
Merge pull request #25 from SkywardAI/chat-improve
Browse files Browse the repository at this point in the history
Supports stream output from chatbot
  • Loading branch information
cbh778899 authored Jul 8, 2024
2 parents ea16fcf + 26277ae commit 18ce373
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 70 deletions.
129 changes: 101 additions & 28 deletions components/chat-page/chatMain.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
import useConversation from "../../global/useConversation.js";
import request from "../../tools/request.js";

let conversation = {}, main_elem, init = false;
let conversation = {}, main_elem;

const { componentDismount, sendMessage } = useConversation(c=>{
console.log(c)
const {
componetDismount, componentReMount,
sendMessage:appendConversationMessage
} = useConversation(c=>{
if(c.id === conversation.id) return;
conversation = c;
if(conversation.id === null) {
const conversation_main = document.getElementById('conversation-main');
if(conversation_main) conversation_main.innerHTML = "<div class='greeting'>Hi, how can I help you today?</div>"
}
if(conversation.id !== 'not_selected') {
buildForm();
}
if(!conversation.id) return;

updateConversation();
buildForm();
})

export default function createChatMain(main) {
main.insertAdjacentHTML('beforeend', `
<div id='chat-main'>
<div id='conversation-main'><div class='greeting'>Please select a ticket or start a new conversation on left.</div></div>
<div id='conversation-main'>
<div class='greeting'>
Please select a ticket or start a new conversation on left.
</div>
</div>
<form id='submit-chat' autocomplete="off"></form>
</div>`)

document.getElementById('submit-chat').onsubmit=submitContent;
main_elem = document.getElementById('conversation-main');
init = true;
updateConversation();

return componentDismount;
if(componentReMount() && conversation.id) {
updateConversation();
buildForm();
}

return componetDismount;
}

function buildForm() {
Expand All @@ -43,38 +50,104 @@ function submitContent(evt) {
evt.preventDefault();

const content = evt.target['send-content'].value;
content && sendMessage(content);
content && (
conversation.stream_response ?
sendMessageStream(content) :
sendMessageWaiting(content)
)
evt.target['send-content'].value = ''
}

async function sendMessage(message, send) {
if(!conversation.history.length) {
main_elem.innerHTML = ''
}
main_elem.appendChild(createBlock('out', message)[0]);
const [bot_answer, bot_answer_message] = createBlock('in');
main_elem.appendChild(bot_answer);
bot_answer.focus();

const response = await request('chat', {
method: 'POST',
body: { sessionUuid: conversation.id || "uuid", message }
})

const content = await send(response, bot_answer_message);

appendConversationMessage([
{ type: 'out', message },
{ type: 'in', message: content}
], conversation.id)
}

function sendMessageWaiting(msg) {
return sendMessage(msg, async (response, pending_elem) => {
const { message } = await response.json();
pending_elem.textContent = message;
return message;
})
}

async function sendMessageStream(msg) {
return sendMessage(msg, async (response, pending_elem) => {
let resp_content = ''
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
let pending_content = ''
while(true) {
const {value, done} = await reader.read();
if(done) break;
pending_content += value;
if(pending_content.includes('\n\n')) {
const splitted_content = pending_content.split('\n\n')
try {
const json = JSON.parse(splitted_content.shift().replace('data: ', ''))
resp_content += json.content;
pending_elem.textContent = resp_content;
pending_content = splitted_content.join('')
if(json.stop) break;
} catch(error) {
console.error(error);
}
}
}
})
}

function updateConversation() {
if(!init || !conversation.history.length) return;
if(!conversation.history) return;
if(!conversation.history.length && main_elem) {
main_elem.innerHTML = "<div class='greeting'>Hi, how can I help you today?</div>"
return;
}

main_elem.innerHTML = ''
conversation.history.forEach(({type, message})=>{
const block = createBlock(type, message);
main_elem.appendChild(block)
main_elem.appendChild(createBlock(type, message)[0])
})

if(conversation.history.slice(-1)[0].type === 'out') {
main_elem.appendChild(createBlock('in'))
main_elem.appendChild(createBlock('in')[0])
}
}

function createBlock(type, message=null) {
const block = document.createElement('div');
block.className = `conversation-block sender-${type}`;

block.innerHTML = `
<div class='content'>
<div class='sender-name'>
From: ${type === 'in' ? 'AI' : 'You'}
</div>
<div class='message'>
${message || "<img class='loading' src='/medias/arrow-clockwise.svg'>"}
</div>
const content = document.createElement('div')
content.className = 'content';
content.innerHTML = `
<div class='sender-name'>
From: ${type === 'in' ? 'AI' : 'You'}
</div>`

const message_elem = document.createElement('div');
message_elem.className = 'message';
message_elem.innerHTML = message || "<img class='loading' src='/medias/arrow-clockwise.svg'>"

content.insertAdjacentElement("beforeend", message_elem);
block.appendChild(content);

const avatar = `
<div class='avatar'>
${type === 'in' ? '<img src="/medias/robot.svg">' : '<img src="/medias/person.svg">'}
Expand All @@ -83,5 +156,5 @@ function createBlock(type, message=null) {
if(type === 'in') block.insertAdjacentHTML("afterbegin", avatar);
else if(type === 'out') block.insertAdjacentHTML("beforeend", avatar);

return block;
return [block, message_elem];
}
2 changes: 1 addition & 1 deletion components/chat-page/history.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import useHistory from "../../global/useHistory.js";
let history = [], history_elem = null, last_selected_id;

const { componetDismount:historyDismount, componentReMount: historyRemount } = useHistory(h=>{
history = h;
history = structuredClone(h);
updateHistoryList();
})

Expand Down
2 changes: 1 addition & 1 deletion components/chat-page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ export default function createChatPage() {
dismount_components.push(createModelSettings(chatPage));

return () => {
dismount_components.forEach(e=>e&&e());
dismount_components.forEach(e=>e());
}
}
4 changes: 2 additions & 2 deletions components/chat-page/modelSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const fields = {
n_predict: { title: 'N-Predict', valueRange: { min: 0, max: 128 } }
}

const { componentDismount, updateSetting, componentReMount } = useConversation(c=>{
const { componetDismount, updateSetting, componentReMount } = useConversation(c=>{
settings = c;
loadSettings();
})
Expand Down Expand Up @@ -47,7 +47,7 @@ export default function createModelSettings(main) {

init = true;
loadSettings();
return componentDismount;
return componetDismount;
}

function loadSettings() {
Expand Down
2 changes: 1 addition & 1 deletion global/createHook.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function createHook() {

function remount(key) {
return () => {
const need_unfreeze = !updatesList[key].frozen
const need_unfreeze = updatesList[key].frozen
updatesList[key].frozen = false;
return need_unfreeze;
}
Expand Down
46 changes: 16 additions & 30 deletions global/useConversation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import useHistory from "./useHistory.js";

const defaultConversationSetting = {
id: null,
stream_response: true,
temperature: 0.2,
top_k: 40,
top_p: 0.9,
Expand All @@ -13,49 +14,34 @@ const defaultConversationSetting = {

let currentConversation = {
...defaultConversationSetting,
id: 'not_selected'
history: []
};


const { onmount, remount, dismount, updateAll } = createHook();
const { addHistory:addUserHistoryTicket } = useHistory(null);
const { addHistory } = useHistory(null);

export default function useConversation(updated) {
const mount_key = onmount(updated);

function addHistory(histories) {
currentConversation.history.push(...histories);
async function startNewConversation() {
const { sessionUuid } = await (await request('chat/seesionuuid')).json();
currentConversation = {
...defaultConversationSetting,
id: sessionUuid, history: []
};
addHistory({
id: currentConversation.id,
name: 'New Session',
createdAt: new Date().toUTCString()
})
updateAll(currentConversation);
}

function startNewConversation() {
currentConversation.id = null;
currentConversation.history = [];
async function sendMessage(messages) {
currentConversation.history.push(...messages);
updateAll(currentConversation);
}

async function sendMessage(message) {
addHistory([{ type: 'out', message }]);

const { sessionUuid, message:botResponse } =
await (await request('chat', {
method: 'POST',
body: {
sessionUuid: currentConversation.id || "uuid",
message
}
})).json()

if(currentConversation.id === null) {
addUserHistoryTicket({
id: sessionUuid, name: 'New Conversation',
createdAt: new Date().toUTCString()
})
currentConversation.id = sessionUuid;
}
addHistory([{type: 'in', message: botResponse}])
}

async function selectConversation(id, settings = null) {
const conversation_history = [];
await (await request(`chat/history/${id}`))
Expand Down
4 changes: 1 addition & 3 deletions settings.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
export const VERSION = '0.1.3'
// export const API_ADDRESS = 'http://
// kirin is the name of the API aggregator server container
export const LOCAL_API_ADDRESS = '/api'
export const API_ADDRESS = '/api'
1 change: 1 addition & 0 deletions styles/chat_page.css
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
color: gray;
font-size: 20px;
user-select: none;
max-width: 80%;
}

#chat-page #chat-main #conversation-main .conversation-block {
Expand Down
15 changes: 11 additions & 4 deletions tools/request.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { LOCAL_API_ADDRESS } from "../settings.js";
import { API_ADDRESS } from "../settings.js";
import useSessionId from "../global/useSessionId.js";

let session_id = '';
useSessionId(id=>{session_id = id});

export default function request(url, options={}) {
return fetch(reqAddress(url), generateRequest(url, options))
}

export function reqAddress(url) {
return `${API_ADDRESS}/${url}`
}

function generateRequest(url, options={}) {
const headers = {
Accept: 'application/json'
}
Expand All @@ -19,9 +27,8 @@ export default function request(url, options={}) {
options.body = JSON.stringify(options.body)
}

url = `${LOCAL_API_ADDRESS}/${url}`
return fetch(url, {
return {
headers,
...options
})
}
}

0 comments on commit 18ce373

Please sign in to comment.