chat.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. export function initChat() {
  2. if (!isNeedChat()) {
  3. return
  4. }
  5. // Your CSS as text
  6. let styles = `
  7. body {
  8. background-color: #f5f5f5;
  9. }
  10. #chat-assistant-container {
  11. position: fixed;
  12. right: 40px;
  13. bottom: 86px;
  14. z-index:9990;
  15. }
  16. #chat-assistant-button {
  17. width: 50px;
  18. height: 50px;
  19. border-radius: 50%;
  20. border: none;
  21. display: flex;
  22. justify-content: center;
  23. align-items: center;
  24. cursor: pointer;
  25. background: linear-gradient(135deg, rgb(215 98 150 / 55%),rgb(34 78 139 / 71%), rgb(114 222 172));
  26. box-shadow: 0px 0px 8px 1px rgb(0 0 0 / 22%);
  27. color: #fff;
  28. text-shadow: 1px 1px 3px rgb(0 0 0 / 56%);
  29. }
  30. #chat-container {
  31. position: fixed;
  32. padding: 10px;
  33. top: 45%;
  34. left: 50%;
  35. z-index:9990;
  36. transform: translate(-50%, -50%);
  37. display: none;
  38. border-radius: 5px;
  39. width: 40%;
  40. background: linear-gradient(135deg, rgb(215 98 150 / 92%),rgb(34 78 139 / 93%), rgb(114 222 172 / 94%));
  41. box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
  42. }
  43. #chat-header {
  44. display: flex;
  45. align-items: center;
  46. justify-content: space-between;
  47. padding: 0 10px 10px 0;
  48. border-radius: 5px 5px 0 0;
  49. cursor: move;
  50. }
  51. #loading-indicator {
  52. width: 14px;
  53. height: 14px;
  54. margin: 0 10px 0 10px;
  55. border: 2px solid #ccc;
  56. border-top-color: #4caf50;
  57. border-radius: 50%;
  58. animation: spin 2s linear infinite;
  59. visibility: hidden;
  60. }
  61. @keyframes spin {
  62. 0% {
  63. transform: rotate(0deg);
  64. }
  65. 100% {
  66. transform: rotate(360deg);
  67. }
  68. }
  69. #chat-header .show-loading {
  70. visibility: visible;
  71. }
  72. #chat-header .hide-loading {
  73. visibility: hidden;
  74. }
  75. #circle-button {
  76. padding: 0;
  77. border: none;
  78. background-color: transparent;
  79. font-size: 16px;
  80. user-select: none;
  81. display: flex;
  82. align-items: center;
  83. color: #fff;
  84. text-shadow: 1px 1px 3px black;
  85. }
  86. #close-button {
  87. cursor: pointer;
  88. padding: 0;
  89. border: none;
  90. background-color: transparent;
  91. font-size: 24px;
  92. color: #fff;
  93. text-shadow: 1px 1px 3px black;
  94. }
  95. #send-button {
  96. cursor: pointer;
  97. padding: 0;
  98. border: none;
  99. background-color: transparent;
  100. font-size: 16px;
  101. }
  102. #close-button:hover,
  103. #send-button:hover {
  104. color: #888;
  105. }
  106. #chat-input-container,
  107. #chat-input {
  108. border: none;
  109. }
  110. #chat-input-container {
  111. display: flex;
  112. align-items: center;
  113. border-radius: 5px;
  114. background-color: #fff;
  115. padding: 10px;
  116. }
  117. #chat-input {
  118. flex: 1;
  119. padding: 0;
  120. margin-right: 5px;
  121. border-radius: 5px;
  122. overflow-y: auto;
  123. height: 24px;
  124. font-size: 1rem;
  125. outline: none;
  126. resize: none;
  127. background: transparent;
  128. }
  129. #send-button {
  130. background-color: transparent;
  131. border: none;
  132. border-radius: 5px;
  133. cursor: pointer;
  134. padding: 5px;
  135. display: flex;
  136. align-items: center;
  137. justify-content: center;
  138. height: 32px;
  139. width: 32px;
  140. }
  141. #send-button>span {
  142. height: 16px;
  143. width: 16px;
  144. }
  145. #send-button:enabled {
  146. background-color: rgb(120,198,174);
  147. }
  148. #send-button:enabled svg path {
  149. fill: #fff;
  150. }
  151. `
  152. let styleSheet = document.createElement("style")
  153. styleSheet.innerText = styles
  154. document.head.appendChild(styleSheet)
  155. const html = `<div id="chat-assistant-container">
  156. <button id="chat-assistant-button">🤖AI</button>
  157. </div>
  158. <div id="chat-container">
  159. <div id="chat-header">
  160. <span id="circle-button">Univer AI 助手<div id="loading-indicator"></div></span>
  161. <button id="close-button">×</button>
  162. </div>
  163. <div id="chat-input-container">
  164. <textarea id="chat-input" placeholder="请输入问题"></textarea>
  165. <!-- <textarea id="chat-input" placeholder="请输入问题"></textarea> -->
  166. <button id="send-button" disabled>
  167. <span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" class="h-4 w-4 m-1 md:m-0"
  168. stroke-width="2">
  169. <path
  170. d="M.5 1.163A1 1 0 0 1 1.97.28l12.868 6.837a1 1 0 0 1 0 1.766L1.969 15.72A1 1 0 0 1 .5 14.836V10.33a1 1 0 0 1 .816-.983L8.5 8 1.316 6.653A1 1 0 0 1 .5 5.67V1.163Z"
  171. fill="currentColor"></path>
  172. </svg></span>
  173. </button>
  174. </div>
  175. </div>`;
  176. document.body.insertAdjacentHTML('beforeend', html)
  177. const assistantButton = document.getElementById('chat-assistant-button');
  178. const chatContainer = document.getElementById('chat-container');
  179. const closeButton = document.getElementById('close-button');
  180. const chatInput = document.getElementById('chat-input');
  181. const sendButton = document.getElementById('send-button');
  182. const loadingIndicator = document.getElementById('loading-indicator');
  183. assistantButton.addEventListener('click', function () {
  184. chatContainer.style.display = 'block';
  185. });
  186. closeButton.addEventListener('click', function () {
  187. chatContainer.style.display = 'none';
  188. });
  189. sendButton.addEventListener('click', function () {
  190. const message = chatInput.value;
  191. if (message.trim() !== '') {
  192. // 处理发送消息的逻辑
  193. chatInput.value = '';
  194. resetButton(chatInput)
  195. // 显示 Loading
  196. loadingIndicator.classList.add('show-loading');
  197. setTimeout(() => {
  198. setFormuala(message);
  199. // 隐藏 Loading
  200. loadingIndicator.classList.remove('show-loading');
  201. }, 1000);
  202. }
  203. });
  204. chatInput.addEventListener('input', function () {
  205. inputHandler(this)
  206. });
  207. function inputHandler(input) {
  208. if (input.scrollHeight > 24) {
  209. input.style.height = 'auto'
  210. }
  211. input.style.height = input.scrollHeight + 'px'; // 根据内容高度设置 textarea 高度
  212. if (input.scrollHeight > 200) {
  213. input.style.overflowY = 'scroll'
  214. } else {
  215. input.style.overflowY = 'hidden'
  216. }
  217. resetButton(input)
  218. }
  219. function resetButton(input) {
  220. if (input.value.trim() !== '') {
  221. sendButton.disabled = false;
  222. sendButton.classList.add('enabled');
  223. } else {
  224. input.style.height = '24px'; // 重置高度为一行
  225. sendButton.disabled = true;
  226. sendButton.classList.remove('enabled');
  227. }
  228. }
  229. // 快捷键
  230. let isComposing = false;
  231. chatInput.addEventListener('compositionstart', function () {
  232. isComposing = true;
  233. });
  234. chatInput.addEventListener('compositionend', function () {
  235. isComposing = false;
  236. });
  237. chatInput.addEventListener('keydown', function (event) {
  238. const isWindows = navigator.platform.includes('Win');
  239. const isMac = navigator.platform.includes('Mac');
  240. const key = event.key;
  241. if (isWindows && event.key === 'Enter' && !isComposing && !event.altKey) {
  242. // Windows 上的 Enter 键触发发送
  243. event.preventDefault();
  244. sendButton.click();
  245. } else if (isWindows && event.key === 'Enter' && !isComposing && event.altKey) {
  246. // Windows 上的 Alt+Enter 键触发换行
  247. event.preventDefault();
  248. this.value += '\n';
  249. } else if (isMac && event.key === 'Enter' && !isComposing && !event.metaKey) {
  250. // Mac 上的 Enter 键触发发送
  251. event.preventDefault();
  252. sendButton.click();
  253. } else if (isMac && event.key === 'Enter' && !isComposing && event.metaKey) {
  254. // Mac 上的 Command+Enter 键触发换行
  255. event.preventDefault();
  256. this.value += '\n';
  257. } else if (!isComposing && (key === "Backspace" || key === "Delete")) {
  258. }
  259. inputHandler(this)
  260. });
  261. // 添加拖拽功能
  262. let isDragging = false;
  263. let offset = { x: 0, y: 0 };
  264. const chatHeader = document.getElementById('chat-header');
  265. chatHeader.addEventListener('mousedown', function (event) {
  266. isDragging = true;
  267. offset.x = event.clientX - chatContainer.offsetLeft;
  268. offset.y = event.clientY - chatContainer.offsetTop;
  269. });
  270. document.addEventListener('mousemove', function (event) {
  271. if (isDragging) {
  272. chatContainer.style.left = `${event.clientX - offset.x}px`;
  273. chatContainer.style.top = `${event.clientY - offset.y}px`;
  274. }
  275. });
  276. document.addEventListener('mouseup', function () {
  277. isDragging = false;
  278. });
  279. }
  280. const needChatHosts = [
  281. 'crm.lashuju.com',
  282. 'localhost:3000'
  283. ]
  284. function isNeedChat() {
  285. const host = location.host;
  286. if (needChatHosts.includes(host)) {
  287. return true
  288. }
  289. return false
  290. }
  291. function setFormuala(sentence = '') {
  292. let link = getLink(sentence)
  293. if (link !== '') {
  294. setGET_AIRTABLE(link)
  295. } else {
  296. setASK_AI(sentence)
  297. }
  298. }
  299. function setASK_AI(sentence = '') {
  300. let range = getRange(sentence);
  301. range = range === '' ? '' : ',' + range
  302. const data = [
  303. [
  304. {
  305. "f": "=ASK_AI(\"" + sentence + "\"" + range + ")"
  306. }
  307. ]
  308. ]
  309. luckysheet.setRangeValue(data)
  310. }
  311. function setGET_AIRTABLE(link) {
  312. const data = [
  313. [
  314. {
  315. "f": "=GET_AIRTABLE_DATA(\"" + link + "\")"
  316. }
  317. ]
  318. ]
  319. luckysheet.setRangeValue(data)
  320. }
  321. function getLink(sentence = '') {
  322. const regex = /(https?:\/\/(?:www\.)?airtable\.com\/\S+)/gi;
  323. const matches = sentence.match(regex);
  324. if (matches) {
  325. return matches[0];
  326. }
  327. return ''
  328. }
  329. function getRange(text) {
  330. const regex = /([A-Z]+[0-9]*):([A-Z]+[0-9]*)/g;
  331. const matche = text.match(regex);
  332. if (matche) {
  333. return matche[0]
  334. }
  335. return ''
  336. }