/**
* chatUI constructor
* @constructor
* @param {d3-selection} container - Container for the chat interface.
* @return {object} chatUI object
*/
var chatUI = (function (container) {
var module = {};
module.chatbot = container.append('div').attr('id', 'cb-chatbot');
module.chatbotButton = container.append('button').attr('id', 'cb-chatbot-button');
module.chatbotButton.append('div').attr('class', 'cb-sc-krNlru');
module.container = module.chatbot.append('div').attr('id', 'cb-container');
module.config = null;
module.bubbles = [];
module.ID = 0;
module.keys = {};
module.types = {};
module.inputState = false;
module.height = 0;
module.container.html(`
`)
//
// module.top = module.container.append('div').attr('class', 'cb-top');
// module.top.button1 = module.top.append('button');
// module.top.button1.append('img').attr('id', 'cb-zoom-img').attr('src', '/chat-ui/build/assets/icn_big.svg');
// module.top.button2 = module.top.append('button');
// module.top.button2.append('img').attr('src', '/chat-ui/build/assets/icn_cls.svg');
//
// module.header = module.container.append('div').attr('id', 'cb-header');
// module.header.title = module.header.append('div').attr('id', 'cb-title');
// module.header.title.logo = module.header.title.append('div').attr('class', 'cb-icon');
// module.header.title.text = module.header.title.append('p').attr('id', 'cb-text').text('다보리 AI 매니저');
//
// module.setting = module.header.append('div').attr('class', 'cb-setting cb-topicn');
//
// module.setting.dropdown1 = module.setting.append('div').attr('class', 'cb-dropdown');
// module.setting.dropdown1.button1 = module.setting.dropdown1.append('button');
// module.setting.dropdown1.dropmenu = module.setting.dropdown1.append('div').attr('class', 'cb-dropmenu').append('ul');
// module.setting.dropdown1.dropmenu.li = module.setting.dropdown1.dropmenu.append('li');
// module.setting.dropdown1.dropmenu.li.append('div').attr('class', 'cb-avatar').append('img')
// .attr('src', '/chat-ui/build/assets/icn_none.svg');
// module.setting.dropdown1.dropmenu.li.append('div').attr('class', 'cb-conts').html('')
//
// module.setting.dropdown1.button1.append('img').attr('src', '/chat-ui/build/assets/icn_gpt.svg');
//
// module.setting.dropdown2 = module.setting.append('div').attr('class', 'cb-dropdown');
// module.setting.dropdown2.button2 = module.setting.dropdown2.append('button');
// module.setting.dropdown2.button2.append('img').attr('src', '/chat-ui/build/assets/icn_bard.svg')
//
// module.setting.dropdown3 = module.setting.append('div').attr('class', 'cb-dropdown');
// module.setting.dropdown3.button3 = module.setting.dropdown3.append('button');
// module.setting.dropdown3.button3.append('img').attr('src', '/chat-ui/build/assets/icn_chat.svg')
// module.setting.select = module.setting.append('select').attr('class', 'cb-setting-select');
// module.setting.select.append('option').text('ChatGPT');
// module.setting.select.append('option').text('Bard');
// module.setting.select.append('option').text('LLaMA');
// module.setting.select.append('option').text('HyperClovaX');
// module.setting.select.append('option').text('KoGPT');
module.scroll = module.container.append('div').attr('id', 'cb-flow');
module.flow = module.scroll.append('div').attr('class', 'cb-inner');
module.input = module.container.append('div').attr('id', 'cb-input').style('display', 'none');
module.input.append('div').attr('id', 'cb-input-container').append('input').attr('type', 'text');
module.input.append('button').text('+');
/**
* updateContainer should be called when height or width changes of the container changes
* @memberof chatUI
*/
module.updateContainer = function () {
module.height = module.container.node().offsetHeight;
module.flow.style('padding-top', module.height + 'px');
module.scroll.style('height', (module.height - ((module.inputState == true) ? 77 : 0)) + 'px');
module.scrollTo('end');
};
/**
* @memberof chatUI
* @param {object} options - object containing configs {type:string (e.g. 'text' or 'select'), class:string ('human' || 'bot'), value:depends on type}
* @param {function} callback - function to be called after everything is done
* @return {integer} id - id of the bubble
*/
module.addBubble = function (options, callback) {
callback = callback || function () {
};
if (!(options.type in module.types)) {
throw 'Unknown bubble type';
} else {
module.ID++;
var id = module.ID;
module.bubbles.push({
id: id,
type: options.type
//additional info
});
module.keys[id] = module.bubbles.length - 1;
//segment container
var outer = module.flow.append('div')
.attr('class', 'cb-segment cb-' + options.class + ' cb-bubble-type-' + options.type)
.attr('id', 'cb-segment-' + id);
//speaker icon
outer.append('div').attr('class', 'cb-icon');
var bubble = outer.append('div')
.attr('class', 'cb-bubble ' + options.class)
// .style("height", "50px")
.append('div')
.attr('class', 'cb-inner');
outer.append('hr');
module.types[options.type](bubble, options, callback);
module.scrollTo('end');
return module.ID;
}
};
module.addApiBubble = function (bubble, options, callback) {
callback = callback || function () {
};
if (!(options.type in module.types)) {
throw 'Unknown bubble type';
} else {
module.ID++;
var id = module.ID;
module.bubbles.push({
id: id,
type: options.type
//additional info
});
module.keys[id] = module.bubbles.length - 1;
module.types[options.type](bubble, options, callback);
module.scrollTo('end');
return module.ID;
}
};
/**
* @memberof chatUI
* @param {d3-selection} bubble - d3 selection of the bubble container
* @param {object} options - object containing configs {type:'text', class:string ('human' || 'bot'), value:array of objects (e.g. [{label:'yes'}])}
* @param {function} callback - function to be called after everything is done
*/
module.types.select = function (bubble, options, callback) {
bubble.selectAll('.cb-choice').data(options.value).enter().append('div')
.attr('class', 'cb-choice')
.text(function (d) {
return d.label;
})
.on('click', function (d) {
d3.select(this).classed('cb-active', true);
d3.select(this.parentNode).selectAll('.cb-choice').on('click', function () {
});
callback(d);
});
};
/**
* @memberof chatUI
* @param {d3-selection} bubble - d3 selection of the bubble container
* @param {object} options - object containing configs {type:'text', class:string ('human' || 'bot'), value:string (e.g. 'Hello World')}
* @param {function} callback - function to be called after everything is done
*/
module.types.text = function (bubble, options, callback) {
if (('delay' in options) && options.delay) {
// var animatedCircles = '';
// bubble.append('div')
// .attr('class', 'cb-waiting')
// .html(animatedCircles);
setTimeout(function () {
bubble.select(".cb-waiting").remove();
module.appendText(bubble, options, callback);
}, (isNaN(options.delay) ? 1000 : options.delay));
} else {
module.appendText(bubble, options, callback);
}
};
/**
* Helper Function for adding text to a bubble
* @memberof chatUI
* @param {d3-selection} bubble - d3 selection of the bubble container
* @param {object} options - object containing configs {type:'text', class:string ('human' || 'bot'), value:string (e.g. 'Hello World')}
* @param {function} callback - function to be called after everything is done
*/
module.appendText = function (bubble, options, callback) {
bubble.attr('class', 'bubble-ctn-' + options.class).append('p')
.html(options.value)
.transition()
.duration(200)
.style("width", "auto")
.style('opacity', 1);
chat.scrollTo('end');
callback();
};
/**
* Showing the input module and set cursor into input field
* @memberof chatUI
* @param {function} submitCallback - function to be called when user presses enter or submits through the submit-button
* @param {function} typeCallback - function to when user enters text (on change)
*/
module.showInput = function (submitCallback, typeCallback) {
module.inputState = true;
if (typeCallback) {
module.input.select('input')
.on('change', function () {
typeCallback(d3.select(this).node().value);
});
} else {
module.input.select('input').on('change', function () {
});
}
module.input.select('input').on('keyup', function () {
const val = module.input.select('input').node().value
if (d3.event.keyCode == 13 && val != '') {
submitCallback(val);
module.input.select('input').node().value = '';
}
});
module.input.select('button')
.on('click', function () {
const val = module.input.select('input').node().value
if (val != '') {
submitCallback(val);
module.input.select('input').node().value = '';
}
});
module.input.style('display', 'block');
module.updateContainer();
module.input.select('input').node().focus();
module.scrollTo('end');
};
/**
* Hide the input module
*/
module.hideInput = function () {
module.input.select('input').node().blur();
module.input.style('display', 'none');
module.inputState = false;
module.updateContainer();
module.scrollTo('end');
};
/**
* Remove a bubble from the chat
* @memberof chatUI
* @param {integer} id - id of bubble provided by addBubble
*/
module.removeBubble = function (id) {
module.flow.select('#cb-segment-' + id).remove();
module.bubbles.splice(module.keys[id], 1);
delete module.keys[id];
};
/**
* Remove all bubbles until the bubble with 'id' from the chat
* @memberof chatUI
* @param {integer} id - id of bubble provided by addBubble
*/
module.removeBubbles = function (id) {
for (var i = module.bubbles.length - 1; i >= module.keys[id]; i--) {
module.removeBubble(module.bubbles[i].id);
}
};
/**
* Remove all bubbles until the bubble with 'id' from the chat
* @memberof chatUI
* @param {integer} id - id of bubble provided by addBubble
* @return {object} obj - {el:d3-selection, obj:bubble-data}
*/
module.getBubble = function (id) {
return {
el: module.flow.select('#cb-segment-' + id),
obj: module.bubbles[module.keys[id]]
};
};
module.clearBubbles = function () {
const cbInnerElements = document.getElementsByClassName('cb-inner');
for (let i = 0; i < cbInnerElements.length; i++) {
const cbInnerElement = cbInnerElements[i];
// Remove all child elements of each 'cb-inner' element
while (cbInnerElement.firstChild) {
cbInnerElement.removeChild(cbInnerElement.firstChild);
}
}
};
/**
* Scroll chat flow
* @memberof chatUI
* @param {string} position - where to scroll either 'start' or 'end'
*/
module.scrollTo = function (position) {
//start
var s = 0;
//end
if (position == 'end') {
const innerHeight = d3.select("#cb-flow").node().clientHeight;
s = module.scroll.property('scrollHeight') - (innerHeight - 77);
}
d3.select('#cb-flow').transition()
.duration(300)
.tween("scroll", scrollTween(s));
};
function initChat() {
var conversation = {};
conversation.init = function () {
module.addBubble({
type: 'text',
value: '안녕하세요, 어떻게 도와드릴까요 ?',
class: 'bot',
delay: 0
}, function () {
//Show the input container
module.showInput(conversation.nameResponse);
});
};
conversation.nameResponse = function (message) {
module.addBubble({type: 'text', value: message, class: 'human', delay: 0});
const outer = module.flow.append('div')
.attr('class', 'cb-segment cb-' + 'bot' + ' cb-bubble-type-' + 'text')
.attr('id', 'cb-segment-' + (module.ID + 1));
outer.append('div').attr('class', 'cb-icon');
const bubble = outer.append('div')
.attr('class', 'cb-bubble ' + 'bot')
// .style("height", "50px")
.append('div')
.attr('class', 'cb-inner');
outer.append('hr');
const animatedCircles = '';
bubble.append('div')
.attr('class', 'cb-waiting')
.html(animatedCircles);
d3.request('http://34.64.58.79:8080/chatgpt-ask2')
.header("Content-Type", "application/json")
.post(JSON.stringify({
message: message
}), function(error, data) {
if (error) {
module.addBubble({type: 'text', value: 'API 호출 중 오류 발생', class: 'bot', delay: 0});
outer.remove();
} else {
const content = JSON.parse(data.responseText)['content'].replace(/\n/g, "
");
module.addApiBubble(bubble,{type: 'text', value: content, class: 'bot', delay: 500});
}
});
};
conversation.init();
}
function initDaboryChat() {
var conversation = {};
conversation.init = function () {
module.addBubble({
type: 'text',
value: '안녕하세요. setup-get api 테스트!',
class: 'bot',
delay: 1000
}, function () {
//After initial bubble is presented, ask the user for her name
module.addBubble({
type: 'text',
value: '가져올 setup 설정코드와 브랜드코드를 콤마로 구분해서 입력하세요.(ex: contact-us,main)',
class: 'bot',
delay: 0
});
//Show the input container
module.showInput(conversation.nameResponse);
});
};
conversation.nameResponse = function (setup) {
//As input arrives, present the input
module.addBubble({type: 'text', value: setup, class: 'human', delay: 0});
//Hide the input container
// module.hideInput();
const [setupCode, brandCode] = setup.split(',')
$.fn.dataLinker.api23Js('setup-get', {
SetupCode: setupCode,
BrandCode: brandCode,
}, function (response) {
console.log(response)
module.addBubble({type: 'text', value: JSON.stringify(response), class: 'bot', delay: 500});
})
//And welcome the user with her name
// module.addBubble({ type: 'text', value: 'Hello '+userName, class: 'bot', delay: 500 }, function(){
// module.addBubble({ type: 'text', value: 'Do you want to know my name?', class: 'bot', delay:500 }, function(){
//
// module.addBubble({ type: 'select', value:[{label:'Yes'}, {label:'No'}], class: 'human', delay: 0 }, conversation.questResponse);
//
// });
// });
};
// conversation.questResponse = function(mood){
// switch(mood.label){
// case 'Yes':
// module.addBubble({ type: 'text', value: 'My name is BOT.', class: 'bot', delay: 500 });
// break;
// case 'No':
// module.addBubble({ type: 'text', value: 'OK :(', class: 'bot', delay: 500 });
// break;
// }
// };
conversation.init();
}
function scrollTween(offset) {
return function () {
var i = d3.interpolateNumber(module.scroll.property('scrollTop'), offset);
return function (t) {
module.scroll.property('scrollTop', i(t));
};
};
}
function debouncer(func, _timeout) {
var timeoutID, timeout = _timeout || 200;
return function () {
var scope = this, args = arguments;
clearTimeout(timeoutID);
timeoutID = setTimeout(function () {
func.apply(scope, Array.prototype.slice.call(args));
}, timeout);
};
}
function closeChat() {
const chatbotElement = document.getElementById('cb-chatbot');
const chatbotBtnElement = document.getElementById('cb-chatbot-button');
if (chatbotBtnElement.classList.contains('active')) {
// If the class is already present, remove it
chatbotBtnElement.classList.remove('active');
chatbotElement.style.display = 'none';
} else {
module.clearBubbles()
initChat()
chatbotBtnElement.classList.add('active');
chatbotElement.style.display = 'block';
d3.select("#cb-input-container input").node().focus();
}
}
module.container.select('#zoom-btn').on('click', function () {
const chatbotElement = d3.select('#cb-chatbot');
const zoomImgElement = d3.select('#cb-zoom-img');
if (chatbotElement.classed('zoom-in')) {
// 'zoom-in' 클래스가 있으면 제거하고 이미지 소스 변경
chatbotElement.classed('zoom-in', false);
zoomImgElement.attr('src', '/chat-ui/build/assets/icn_big.svg');
} else {
chatbotElement.classed('zoom-in', true);
zoomImgElement.attr('src', '/chat-ui/build/assets/icn_small.svg');
}
module.scrollTo('end');
});
module.container.select('#select-ai-btn').on('click', function () {
const dropmenuElement = d3.select(this)
.select(function() {
return this.closest('.cb-dropdown').querySelector('.cb-dropmenu');
});
if (dropmenuElement.classed('active')) {
dropmenuElement.classed('active', false);
dropmenuElement.style('display', 'none');
} else {
dropmenuElement.classed('active', true);
dropmenuElement.style('display', 'block');
}
});
module.container.selectAll('.cb-dropmenu li').on('click', function () {
d3.selectAll('.cb-dropmenu li').each(function () {
d3.select(this).classed('active', false);
d3.select(this).select('.cb-badge span').classed('cb-success', false);
});
d3.select(this).classed('active', true);
d3.select(this).select('.cb-badge span').classed('cb-success', true);
});
module.container.select('#close-btn').on('click', function () {
closeChat()
});
module.chatbotButton.on('click', function () {
closeChat()
});
//On Resize scroll to end
d3.select(window).on('resize', debouncer(function (e) {
module.updateContainer();
}, 200));
module.updateContainer();
return module;
});