index-d3.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /**
  2. * chatUI constructor
  3. * @constructor
  4. * @param {d3-selection} container - Container for the chat interface.
  5. * @return {object} chatUI object
  6. */
  7. var chatUI = (function (container) {
  8. var module = {};
  9. module.chatbot = container.append('div').attr('id', 'cb-chatbot');
  10. module.chatbotButton = container.append('button').attr('id', 'cb-chatbot-button');
  11. module.chatbotButton.append('div').attr('class', 'cb-sc-krNlru');
  12. module.container = module.chatbot.append('div').attr('id', 'cb-container');
  13. module.config = null;
  14. module.bubbles = [];
  15. module.ID = 0;
  16. module.keys = {};
  17. module.types = {};
  18. module.inputState = false;
  19. module.height = 0;
  20. module.container.html(`
  21. <div class="cb-top">
  22. <button type="button" id="zoom-btn"><img id="cb-zoom-img" src="/chat-ui/build/assets/icn_big.svg" alt="big"></button>
  23. <button type="button" id="close-btn"><img src="/chat-ui/build/assets/icn_cls.svg" alt="close"></button>
  24. </div>
  25. <div id="cb-header">
  26. <div id="cb-title">
  27. <div class="cb-icon"></div>
  28. <p id="cb-text">다보리 AI 매니저</p></div>
  29. <div class="cb-setting cb-topicn">
  30. <div class="cb-dropdown">
  31. <button type="button" id="select-ai-btn"><img src="/chat-ui/build/assets/icn_gpt.svg" alt="chat gpt"></button>
  32. <div class="cb-dropmenu" style="display: none;">
  33. <ul>
  34. <li class="active">
  35. <div class="cb-avatar">
  36. <img src="/chat-ui/build/assets/icn_none.svg">
  37. </div>
  38. <div class="cb-conts">
  39. <a href="#" class="cb-tit">ChatGPT</a>
  40. <span class="cb-text">Lead web developer</span>
  41. </div>
  42. <div class="cb-badge"><span class="cb-success"></span></div>
  43. </li>
  44. <li class="">
  45. <div class="cb-avatar">
  46. <img src="/chat-ui/build/assets/icn_none.svg">
  47. </div>
  48. <div class="cb-conts">
  49. <a href="#" class="cb-tit">Bard</a>
  50. <span class="cb-text">Lead web developer</span>
  51. </div>
  52. <div class="cb-badge"><span></span></div>
  53. </li>
  54. <li class="">
  55. <div class="cb-avatar">
  56. <img src="/chat-ui/build/assets/icn_none.svg">
  57. </div>
  58. <div class="cb-conts">
  59. <a href="#" class="cb-tit">LLAMA</a>
  60. <span class="cb-text">Lead web developer</span>
  61. </div>
  62. <div class="cb-badge"><span></span></div>
  63. </li>
  64. <li class="">
  65. <div class="cb-avatar">
  66. <img src="/chat-ui/build/assets/icn_none.svg">
  67. </div>
  68. <div class="cb-conts">
  69. <a href="#" class="cb-tit">HyperClovaX</a>
  70. <span class="cb-text">Lead web developer</span>
  71. </div>
  72. <div class="cb-badge"><span></span></div>
  73. </li>
  74. </ul>
  75. </div>
  76. </div>
  77. <div>
  78. <button type="button"><img src="/chat-ui/build/assets/icn_bard.svg" alt="bard"></button>
  79. </div>
  80. <div>
  81. <button type="button"><img src="/chat-ui/build/assets/icn_chat.svg" alt="chat"></button>
  82. </div>
  83. </div>
  84. </div>
  85. </div>
  86. `)
  87. //
  88. // module.top = module.container.append('div').attr('class', 'cb-top');
  89. // module.top.button1 = module.top.append('button');
  90. // module.top.button1.append('img').attr('id', 'cb-zoom-img').attr('src', '/chat-ui/build/assets/icn_big.svg');
  91. // module.top.button2 = module.top.append('button');
  92. // module.top.button2.append('img').attr('src', '/chat-ui/build/assets/icn_cls.svg');
  93. //
  94. // module.header = module.container.append('div').attr('id', 'cb-header');
  95. // module.header.title = module.header.append('div').attr('id', 'cb-title');
  96. // module.header.title.logo = module.header.title.append('div').attr('class', 'cb-icon');
  97. // module.header.title.text = module.header.title.append('p').attr('id', 'cb-text').text('다보리 AI 매니저');
  98. //
  99. // module.setting = module.header.append('div').attr('class', 'cb-setting cb-topicn');
  100. //
  101. // module.setting.dropdown1 = module.setting.append('div').attr('class', 'cb-dropdown');
  102. // module.setting.dropdown1.button1 = module.setting.dropdown1.append('button');
  103. // module.setting.dropdown1.dropmenu = module.setting.dropdown1.append('div').attr('class', 'cb-dropmenu').append('ul');
  104. // module.setting.dropdown1.dropmenu.li = module.setting.dropdown1.dropmenu.append('li');
  105. // module.setting.dropdown1.dropmenu.li.append('div').attr('class', 'cb-avatar').append('img')
  106. // .attr('src', '/chat-ui/build/assets/icn_none.svg');
  107. // module.setting.dropdown1.dropmenu.li.append('div').attr('class', 'cb-conts').html('<a href="#" class="cb-tit"></a><span class="cb-text"></span>')
  108. //
  109. // module.setting.dropdown1.button1.append('img').attr('src', '/chat-ui/build/assets/icn_gpt.svg');
  110. //
  111. // module.setting.dropdown2 = module.setting.append('div').attr('class', 'cb-dropdown');
  112. // module.setting.dropdown2.button2 = module.setting.dropdown2.append('button');
  113. // module.setting.dropdown2.button2.append('img').attr('src', '/chat-ui/build/assets/icn_bard.svg')
  114. //
  115. // module.setting.dropdown3 = module.setting.append('div').attr('class', 'cb-dropdown');
  116. // module.setting.dropdown3.button3 = module.setting.dropdown3.append('button');
  117. // module.setting.dropdown3.button3.append('img').attr('src', '/chat-ui/build/assets/icn_chat.svg')
  118. // module.setting.select = module.setting.append('select').attr('class', 'cb-setting-select');
  119. // module.setting.select.append('option').text('ChatGPT');
  120. // module.setting.select.append('option').text('Bard');
  121. // module.setting.select.append('option').text('LLaMA');
  122. // module.setting.select.append('option').text('HyperClovaX');
  123. // module.setting.select.append('option').text('KoGPT');
  124. module.scroll = module.container.append('div').attr('id', 'cb-flow');
  125. module.flow = module.scroll.append('div').attr('class', 'cb-inner');
  126. module.input = module.container.append('div').attr('id', 'cb-input').style('display', 'none');
  127. module.input.append('div').attr('id', 'cb-input-container').append('input').attr('type', 'text');
  128. module.input.append('button').text('+');
  129. /**
  130. * updateContainer should be called when height or width changes of the container changes
  131. * @memberof chatUI
  132. */
  133. module.updateContainer = function () {
  134. module.height = module.container.node().offsetHeight;
  135. module.flow.style('padding-top', module.height + 'px');
  136. module.scroll.style('height', (module.height - ((module.inputState == true) ? 77 : 0)) + 'px');
  137. module.scrollTo('end');
  138. };
  139. /**
  140. * @memberof chatUI
  141. * @param {object} options - object containing configs {type:string (e.g. 'text' or 'select'), class:string ('human' || 'bot'), value:depends on type}
  142. * @param {function} callback - function to be called after everything is done
  143. * @return {integer} id - id of the bubble
  144. */
  145. module.addBubble = function (options, callback) {
  146. callback = callback || function () {
  147. };
  148. if (!(options.type in module.types)) {
  149. throw 'Unknown bubble type';
  150. } else {
  151. module.ID++;
  152. var id = module.ID;
  153. module.bubbles.push({
  154. id: id,
  155. type: options.type
  156. //additional info
  157. });
  158. module.keys[id] = module.bubbles.length - 1;
  159. //segment container
  160. var outer = module.flow.append('div')
  161. .attr('class', 'cb-segment cb-' + options.class + ' cb-bubble-type-' + options.type)
  162. .attr('id', 'cb-segment-' + id);
  163. //speaker icon
  164. outer.append('div').attr('class', 'cb-icon');
  165. var bubble = outer.append('div')
  166. .attr('class', 'cb-bubble ' + options.class)
  167. // .style("height", "50px")
  168. .append('div')
  169. .attr('class', 'cb-inner');
  170. outer.append('hr');
  171. module.types[options.type](bubble, options, callback);
  172. module.scrollTo('end');
  173. return module.ID;
  174. }
  175. };
  176. module.addApiBubble = function (bubble, options, callback) {
  177. callback = callback || function () {
  178. };
  179. if (!(options.type in module.types)) {
  180. throw 'Unknown bubble type';
  181. } else {
  182. module.ID++;
  183. var id = module.ID;
  184. module.bubbles.push({
  185. id: id,
  186. type: options.type
  187. //additional info
  188. });
  189. module.keys[id] = module.bubbles.length - 1;
  190. module.types[options.type](bubble, options, callback);
  191. module.scrollTo('end');
  192. return module.ID;
  193. }
  194. };
  195. /**
  196. * @memberof chatUI
  197. * @param {d3-selection} bubble - d3 selection of the bubble container
  198. * @param {object} options - object containing configs {type:'text', class:string ('human' || 'bot'), value:array of objects (e.g. [{label:'yes'}])}
  199. * @param {function} callback - function to be called after everything is done
  200. */
  201. module.types.select = function (bubble, options, callback) {
  202. bubble.selectAll('.cb-choice').data(options.value).enter().append('div')
  203. .attr('class', 'cb-choice')
  204. .text(function (d) {
  205. return d.label;
  206. })
  207. .on('click', function (d) {
  208. d3.select(this).classed('cb-active', true);
  209. d3.select(this.parentNode).selectAll('.cb-choice').on('click', function () {
  210. });
  211. callback(d);
  212. });
  213. };
  214. /**
  215. * @memberof chatUI
  216. * @param {d3-selection} bubble - d3 selection of the bubble container
  217. * @param {object} options - object containing configs {type:'text', class:string ('human' || 'bot'), value:string (e.g. 'Hello World')}
  218. * @param {function} callback - function to be called after everything is done
  219. */
  220. module.types.text = function (bubble, options, callback) {
  221. if (('delay' in options) && options.delay) {
  222. // var animatedCircles = '<div class="circle"></div><div class="circle"></div><div class="circle"></div>';
  223. // bubble.append('div')
  224. // .attr('class', 'cb-waiting')
  225. // .html(animatedCircles);
  226. setTimeout(function () {
  227. bubble.select(".cb-waiting").remove();
  228. module.appendText(bubble, options, callback);
  229. }, (isNaN(options.delay) ? 1000 : options.delay));
  230. } else {
  231. module.appendText(bubble, options, callback);
  232. }
  233. };
  234. /**
  235. * Helper Function for adding text to a bubble
  236. * @memberof chatUI
  237. * @param {d3-selection} bubble - d3 selection of the bubble container
  238. * @param {object} options - object containing configs {type:'text', class:string ('human' || 'bot'), value:string (e.g. 'Hello World')}
  239. * @param {function} callback - function to be called after everything is done
  240. */
  241. module.appendText = function (bubble, options, callback) {
  242. bubble.attr('class', 'bubble-ctn-' + options.class).append('p')
  243. .html(options.value)
  244. .transition()
  245. .duration(200)
  246. .style("width", "auto")
  247. .style('opacity', 1);
  248. chat.scrollTo('end');
  249. callback();
  250. };
  251. /**
  252. * Showing the input module and set cursor into input field
  253. * @memberof chatUI
  254. * @param {function} submitCallback - function to be called when user presses enter or submits through the submit-button
  255. * @param {function} typeCallback - function to when user enters text (on change)
  256. */
  257. module.showInput = function (submitCallback, typeCallback) {
  258. module.inputState = true;
  259. if (typeCallback) {
  260. module.input.select('input')
  261. .on('change', function () {
  262. typeCallback(d3.select(this).node().value);
  263. });
  264. } else {
  265. module.input.select('input').on('change', function () {
  266. });
  267. }
  268. module.input.select('input').on('keyup', function () {
  269. const val = module.input.select('input').node().value
  270. if (d3.event.keyCode == 13 && val != '') {
  271. submitCallback(val);
  272. module.input.select('input').node().value = '';
  273. }
  274. });
  275. module.input.select('button')
  276. .on('click', function () {
  277. const val = module.input.select('input').node().value
  278. if (val != '') {
  279. submitCallback(val);
  280. module.input.select('input').node().value = '';
  281. }
  282. });
  283. module.input.style('display', 'block');
  284. module.updateContainer();
  285. module.input.select('input').node().focus();
  286. module.scrollTo('end');
  287. };
  288. /**
  289. * Hide the input module
  290. */
  291. module.hideInput = function () {
  292. module.input.select('input').node().blur();
  293. module.input.style('display', 'none');
  294. module.inputState = false;
  295. module.updateContainer();
  296. module.scrollTo('end');
  297. };
  298. /**
  299. * Remove a bubble from the chat
  300. * @memberof chatUI
  301. * @param {integer} id - id of bubble provided by addBubble
  302. */
  303. module.removeBubble = function (id) {
  304. module.flow.select('#cb-segment-' + id).remove();
  305. module.bubbles.splice(module.keys[id], 1);
  306. delete module.keys[id];
  307. };
  308. /**
  309. * Remove all bubbles until the bubble with 'id' from the chat
  310. * @memberof chatUI
  311. * @param {integer} id - id of bubble provided by addBubble
  312. */
  313. module.removeBubbles = function (id) {
  314. for (var i = module.bubbles.length - 1; i >= module.keys[id]; i--) {
  315. module.removeBubble(module.bubbles[i].id);
  316. }
  317. };
  318. /**
  319. * Remove all bubbles until the bubble with 'id' from the chat
  320. * @memberof chatUI
  321. * @param {integer} id - id of bubble provided by addBubble
  322. * @return {object} obj - {el:d3-selection, obj:bubble-data}
  323. */
  324. module.getBubble = function (id) {
  325. return {
  326. el: module.flow.select('#cb-segment-' + id),
  327. obj: module.bubbles[module.keys[id]]
  328. };
  329. };
  330. module.clearBubbles = function () {
  331. const cbInnerElements = document.getElementsByClassName('cb-inner');
  332. for (let i = 0; i < cbInnerElements.length; i++) {
  333. const cbInnerElement = cbInnerElements[i];
  334. // Remove all child elements of each 'cb-inner' element
  335. while (cbInnerElement.firstChild) {
  336. cbInnerElement.removeChild(cbInnerElement.firstChild);
  337. }
  338. }
  339. };
  340. /**
  341. * Scroll chat flow
  342. * @memberof chatUI
  343. * @param {string} position - where to scroll either 'start' or 'end'
  344. */
  345. module.scrollTo = function (position) {
  346. //start
  347. var s = 0;
  348. //end
  349. if (position == 'end') {
  350. const innerHeight = d3.select("#cb-flow").node().clientHeight;
  351. s = module.scroll.property('scrollHeight') - (innerHeight - 77);
  352. }
  353. d3.select('#cb-flow').transition()
  354. .duration(300)
  355. .tween("scroll", scrollTween(s));
  356. };
  357. function initChat() {
  358. var conversation = {};
  359. conversation.init = function () {
  360. module.addBubble({
  361. type: 'text',
  362. value: '안녕하세요, 어떻게 도와드릴까요 ?',
  363. class: 'bot',
  364. delay: 0
  365. }, function () {
  366. //Show the input container
  367. module.showInput(conversation.nameResponse);
  368. });
  369. };
  370. conversation.nameResponse = function (message) {
  371. module.addBubble({type: 'text', value: message, class: 'human', delay: 0});
  372. const outer = module.flow.append('div')
  373. .attr('class', 'cb-segment cb-' + 'bot' + ' cb-bubble-type-' + 'text')
  374. .attr('id', 'cb-segment-' + (module.ID + 1));
  375. outer.append('div').attr('class', 'cb-icon');
  376. const bubble = outer.append('div')
  377. .attr('class', 'cb-bubble ' + 'bot')
  378. // .style("height", "50px")
  379. .append('div')
  380. .attr('class', 'cb-inner');
  381. outer.append('hr');
  382. const animatedCircles = '<div class="circle"></div><div class="circle"></div><div class="circle"></div>';
  383. bubble.append('div')
  384. .attr('class', 'cb-waiting')
  385. .html(animatedCircles);
  386. d3.request('http://34.64.58.79:8080/chatgpt-ask2')
  387. .header("Content-Type", "application/json")
  388. .post(JSON.stringify({
  389. message: message
  390. }), function(error, data) {
  391. if (error) {
  392. module.addBubble({type: 'text', value: 'API 호출 중 오류 발생', class: 'bot', delay: 0});
  393. outer.remove();
  394. } else {
  395. const content = JSON.parse(data.responseText)['content'].replace(/\n/g, "<br>");
  396. module.addApiBubble(bubble,{type: 'text', value: content, class: 'bot', delay: 500});
  397. }
  398. });
  399. };
  400. conversation.init();
  401. }
  402. function initDaboryChat() {
  403. var conversation = {};
  404. conversation.init = function () {
  405. module.addBubble({
  406. type: 'text',
  407. value: '안녕하세요. setup-get api 테스트!',
  408. class: 'bot',
  409. delay: 1000
  410. }, function () {
  411. //After initial bubble is presented, ask the user for her name
  412. module.addBubble({
  413. type: 'text',
  414. value: '가져올 setup 설정코드와 브랜드코드를 콤마로 구분해서 입력하세요.(ex: contact-us,main)',
  415. class: 'bot',
  416. delay: 0
  417. });
  418. //Show the input container
  419. module.showInput(conversation.nameResponse);
  420. });
  421. };
  422. conversation.nameResponse = function (setup) {
  423. //As input arrives, present the input
  424. module.addBubble({type: 'text', value: setup, class: 'human', delay: 0});
  425. //Hide the input container
  426. // module.hideInput();
  427. const [setupCode, brandCode] = setup.split(',')
  428. $.fn.dataLinker.api23Js('setup-get', {
  429. SetupCode: setupCode,
  430. BrandCode: brandCode,
  431. }, function (response) {
  432. console.log(response)
  433. module.addBubble({type: 'text', value: JSON.stringify(response), class: 'bot', delay: 500});
  434. })
  435. //And welcome the user with her name
  436. // module.addBubble({ type: 'text', value: 'Hello '+userName, class: 'bot', delay: 500 }, function(){
  437. // module.addBubble({ type: 'text', value: 'Do you want to know my name?', class: 'bot', delay:500 }, function(){
  438. //
  439. // module.addBubble({ type: 'select', value:[{label:'Yes'}, {label:'No'}], class: 'human', delay: 0 }, conversation.questResponse);
  440. //
  441. // });
  442. // });
  443. };
  444. // conversation.questResponse = function(mood){
  445. // switch(mood.label){
  446. // case 'Yes':
  447. // module.addBubble({ type: 'text', value: 'My name is BOT.', class: 'bot', delay: 500 });
  448. // break;
  449. // case 'No':
  450. // module.addBubble({ type: 'text', value: 'OK :(', class: 'bot', delay: 500 });
  451. // break;
  452. // }
  453. // };
  454. conversation.init();
  455. }
  456. function scrollTween(offset) {
  457. return function () {
  458. var i = d3.interpolateNumber(module.scroll.property('scrollTop'), offset);
  459. return function (t) {
  460. module.scroll.property('scrollTop', i(t));
  461. };
  462. };
  463. }
  464. function debouncer(func, _timeout) {
  465. var timeoutID, timeout = _timeout || 200;
  466. return function () {
  467. var scope = this, args = arguments;
  468. clearTimeout(timeoutID);
  469. timeoutID = setTimeout(function () {
  470. func.apply(scope, Array.prototype.slice.call(args));
  471. }, timeout);
  472. };
  473. }
  474. function closeChat() {
  475. const chatbotElement = document.getElementById('cb-chatbot');
  476. const chatbotBtnElement = document.getElementById('cb-chatbot-button');
  477. if (chatbotBtnElement.classList.contains('active')) {
  478. // If the class is already present, remove it
  479. chatbotBtnElement.classList.remove('active');
  480. chatbotElement.style.display = 'none';
  481. } else {
  482. module.clearBubbles()
  483. initChat()
  484. chatbotBtnElement.classList.add('active');
  485. chatbotElement.style.display = 'block';
  486. d3.select("#cb-input-container input").node().focus();
  487. }
  488. }
  489. module.container.select('#zoom-btn').on('click', function () {
  490. const chatbotElement = d3.select('#cb-chatbot');
  491. const zoomImgElement = d3.select('#cb-zoom-img');
  492. if (chatbotElement.classed('zoom-in')) {
  493. // 'zoom-in' 클래스가 있으면 제거하고 이미지 소스 변경
  494. chatbotElement.classed('zoom-in', false);
  495. zoomImgElement.attr('src', '/chat-ui/build/assets/icn_big.svg');
  496. } else {
  497. chatbotElement.classed('zoom-in', true);
  498. zoomImgElement.attr('src', '/chat-ui/build/assets/icn_small.svg');
  499. }
  500. module.scrollTo('end');
  501. });
  502. module.container.select('#select-ai-btn').on('click', function () {
  503. const dropmenuElement = d3.select(this)
  504. .select(function() {
  505. return this.closest('.cb-dropdown').querySelector('.cb-dropmenu');
  506. });
  507. if (dropmenuElement.classed('active')) {
  508. dropmenuElement.classed('active', false);
  509. dropmenuElement.style('display', 'none');
  510. } else {
  511. dropmenuElement.classed('active', true);
  512. dropmenuElement.style('display', 'block');
  513. }
  514. });
  515. module.container.selectAll('.cb-dropmenu li').on('click', function () {
  516. d3.selectAll('.cb-dropmenu li').each(function () {
  517. d3.select(this).classed('active', false);
  518. d3.select(this).select('.cb-badge span').classed('cb-success', false);
  519. });
  520. d3.select(this).classed('active', true);
  521. d3.select(this).select('.cb-badge span').classed('cb-success', true);
  522. });
  523. module.container.select('#close-btn').on('click', function () {
  524. closeChat()
  525. });
  526. module.chatbotButton.on('click', function () {
  527. closeChat()
  528. });
  529. //On Resize scroll to end
  530. d3.select(window).on('resize', debouncer(function (e) {
  531. module.updateContainer();
  532. }, 200));
  533. module.updateContainer();
  534. return module;
  535. });