diff --git a/components/account-page/index.js b/components/account-page/index.js index 5b4e415..608d5e1 100644 --- a/components/account-page/index.js +++ b/components/account-page/index.js @@ -3,6 +3,7 @@ import capitalizeFirstLetter from "../../tools/capitalizeFirstLetter.js"; import getSVG from "../../tools/svgs.js"; import showMessage from "../../tools/message.js"; import createDialog from '../../tools/dialog.js'; +import { email_not_valid, password_not_valid, username_not_valid, validateEmail, validatePassword, validateUsername } from '../../tools/validators.js'; let input_details_main = null, init = false, toggleDialog; let current_user = {}, isRegister = false, user_info_saved = localStorage.getItem('saved-user-login-info'); @@ -11,8 +12,8 @@ if(user_info_saved) user_info_saved = JSON.parse(user_info_saved); const { register, login, logout, updateUserInfo } = useUser(user=>{ - if(!input_details_main) return; current_user = user; + if(!input_details_main) return; createInputDetailsPage(); }); @@ -127,11 +128,18 @@ function submitDetails(evt) { const repeat_new_password = evt.target['repeat-new-password'].value; const submit_values = {} if(email && email !== current_user.email) { + if(!validateEmail(email)) { + showMessage(email_not_valid, { type: "error" }) + return; + } submit_values.email = email; } if(new_password) { - if(repeat_new_password !== new_password) { - showMessage("Passwords are not same!", { type: 'err' }) + if(!validatePassword(new_password)) { + showMessage(password_not_valid, { type: 'err' }) + return; + } else if(repeat_new_password !== new_password) { + showMessage("Passwords are not same!", { type: 'err' }) return; } submit_values.password = new_password; @@ -152,6 +160,17 @@ function submitDetails(evt) { const keep_login = evt.target['keep-login'].checked; if(isRegister) { const email = evt.target.email.value; + if(!validateUsername(username)) { + showMessage(username_not_valid, { type: 'err' }) + return; + } else if(!validateEmail(email)) { + showMessage(email_not_valid, { type: 'err' }) + return; + } else if(!validatePassword(password)) { + showMessage(password_not_valid, { type: 'err' }) + return; + } + const repeat_password = evt.target['repeat-password'].value; if(password !== repeat_password) { diff --git a/components/chat-page/chatMain.js b/components/chat-page/chatMain.js index 15aee14..3daf522 100644 --- a/components/chat-page/chatMain.js +++ b/components/chat-page/chatMain.js @@ -6,19 +6,21 @@ import showMessage from "../../tools/message.js"; import request from "../../tools/request.js"; import getSVG from "../../tools/svgs.js"; -let conversation = {}, model_settings = {}, +let conversation = {pending: false}, model_settings = {}, main_elem, toggle_expand, stream_response=true; +let abort_controller; + const { componetDismount: conversationDismount, componentReMount: conversationReMount, - togglePending, + togglePending, rename, sendMessage:appendConversationMessage } = useConversation(c=>{ conversation.pending = c.pending; - const submit_icon = document.querySelector('#submit-chat .send svg.submit-icon'); - submit_icon && submit_icon.classList.toggle('pending', conversation.pending) + const submit_chat_form = document.getElementById('submit-chat') + submit_chat_form && submit_chat_form.classList.toggle('pending', conversation.pending); if(c.id === conversation.id) return; conversation = c; if(!conversation.id) { @@ -43,7 +45,7 @@ const { model_settings = s; }) -const { getHistory, updateHistoryName } = useHistory(); +const { getHistory } = useHistory(); export default function createChatMain(main, toggleExpand, openModelSetting) { main.insertAdjacentHTML('beforeend', ` @@ -102,9 +104,9 @@ export default function createChatMain(main, toggleExpand, openModelSetting) { function buildForm() { const submit_chat = document.getElementById('submit-chat') submit_chat.innerHTML = ` -
- - ${getSVG('send', 'submit-icon')} +
+ + ${getSVG('send', 'icon send')}
`; const input = document.createElement('input'); @@ -114,7 +116,15 @@ function buildForm() { submit_chat.insertAdjacentElement("afterbegin", input); submit_chat.clientHeight; - + + const abortMessage = document.createElement('div'); + abortMessage.className = 'right-button abort-message clickable' + abortMessage.onclick = () => { + conversation.pending && abort_controller.abort(); + } + abortMessage.innerHTML = getSVG('stop-circle-fill', 'icon'); + submit_chat.appendChild(abortMessage); + if(!conversation.history.length) { toggle_expand(); input.focus(); @@ -125,18 +135,24 @@ function submitContent(evt) { evt.preventDefault(); if(conversation.pending) { showMessage( - "Please wait until assistant finished response.", + "Please wait until assistant finished response or abort manually.", { type: 'warn' } ) return; } const content = evt.target['send-content'].value; - content && ( + if(content) { stream_response ? sendMessageStream(content) : sendMessageWaiting(content) - ) + } else { + showMessage( + "Message is empty, feel free to ask anything!", + { type: 'warn' } + ) + return; + } evt.target['send-content'].value = '' } @@ -145,8 +161,7 @@ async function sendMessage(message, send) { if(!conversation.history.length) { main_elem.innerHTML = '' const message_len = message.length; - updateHistoryName(conversation.id, - `${message.substring(0, 25)}${message_len > 25 ? '...' : ''}`) + await rename(`${message.substring(0, 25)}${message_len > 25 ? '...' : ''}`) } main_elem.appendChild(createBlock('user', message)[0]); main_elem.scrollTo({ @@ -156,21 +171,34 @@ async function sendMessage(message, send) { const [bot_answer, updateMessage] = createBlock('assistant'); main_elem.appendChild(bot_answer); - const response = await request('chat', { - method: 'POST', - body: { - sessionUuid: conversation.id || "uuid", - message, ...model_settings - } - }, true) - - const content = await send(response, updateMessage); - togglePending(); - - appendConversationMessage([ - { role: 'user', message }, - { role: 'assistant', message: content} - ], conversation.id) + let content = '' + try { + abort_controller = new AbortController(); + const response = await request('chat', { + method: 'POST', + signal: abort_controller.signal, + body: { + sessionUuid: conversation.id || "uuid", + message, ...model_settings + } + }, true) + + await send(response, msg=>{ + content = msg; + updateMessage(msg); + }); + } catch(error) { + error; + if(content) content+=' ...' + content += '(Message Abroted)' + updateMessage(content) + } finally { + appendConversationMessage([ + { role: 'user', message }, + { role: 'assistant', message: content} + ], conversation.id); + togglePending(); + } } function sendMessageWaiting(msg) { diff --git a/components/chat-page/history.js b/components/chat-page/history.js index ac9f292..ceceea6 100644 --- a/components/chat-page/history.js +++ b/components/chat-page/history.js @@ -1,7 +1,7 @@ import useConversation from "../../global/useConversation.js"; import useHistory from "../../global/useHistory.js"; -let history = [], history_elem = null, last_selected_id; +let history = [], history_elem = null, last_selected_id=null; const { componetDismount:historyDismount, componentReMount: historyRemount } = useHistory(h=>{ history = structuredClone(h); @@ -25,6 +25,8 @@ export default function createChatHistory(main) { history_elem.id = 'chat-history'; main.insertAdjacentElement('beforeend', history_elem); + last_selected_id = null; + // re-mount update listeners historyRemount(); conversationRemount(); @@ -62,7 +64,7 @@ function updateHistoryList() {
${createdAt}
` ticket.onclick = () => { - selectConversation(id); + selectConversation(id, name); } tickets_list.appendChild(ticket) diff --git a/components/chat-page/index.js b/components/chat-page/index.js index 398876c..a89e4ba 100644 --- a/components/chat-page/index.js +++ b/components/chat-page/index.js @@ -1,10 +1,9 @@ import createDialog from "../../tools/dialog.js"; import createChatMain from "./chatMain.js"; import createChatHistory from "./history.js"; -import createModelSettings from "./modelSettings.js"; +import createChatSettingsPage from "./settings.js"; const [settings_main, { toggleModal }] = createDialog(); -document.body.appendChild(settings_main) export default function createChatPage() { const chatPage = document.createElement('div'); @@ -20,7 +19,7 @@ export default function createChatPage() { dismount_components.push(createChatHistory(chatPage)); dismount_components.push(createChatMain(chatPage, toggleExpand, toggleModal)); - dismount_components.push(createModelSettings(settings_main)); + createChatSettingsPage(settings_main); return () => { dismount_components.forEach(e=>e()); diff --git a/components/chat-page/model-settings.js b/components/chat-page/model-settings.js new file mode 100644 index 0000000..290fb1c --- /dev/null +++ b/components/chat-page/model-settings.js @@ -0,0 +1,44 @@ +import useModelSettings from "../../global/useModelSettings.js"; +import rangeSettingSection from "./range-setting-section.js"; + +let settings = {}; + +const { + updateSettings:updateModelSettings, setToDefault +} = useModelSettings(s=>settings = s); + +const fields = { + temperature: { title: 'Temperature', valueRange: { min: 0, max: 2, is_100_times: true } }, + top_k: { title: 'Top-K', valueRange: { min: 0, max: 40 } }, + top_p: { title: 'Top-P', valueRange: { min: 0, max: 0.9, is_100_times: true } }, + n_predict: { title: 'N-Predict', valueRange: { min: 128, max: 512 } } +} + +export default function createModelSettings(main) { + const model_settings = document.createElement('div'); + + model_settings.innerHTML = ` +
Adjust Model Settings
+
Settings will be saved automatically
+ ` + + for(const key in fields) { + const { title, valueRange } = fields[key]; + const [component, setter] = rangeSettingSection( + title, valueRange, + () => { setToDefault(key) && loadSettings() }, + value=>updateModelSettings(key, value) + ) + model_settings.appendChild(component); + fields[key].setValue = setter; + } + + main.appendChild(model_settings); + loadSettings(); +} + +function loadSettings() { + for(const key in fields) { + fields[key].setValue(settings[key]); + } +} \ No newline at end of file diff --git a/components/chat-page/modelSettings.js b/components/chat-page/modelSettings.js deleted file mode 100644 index 0b776ab..0000000 --- a/components/chat-page/modelSettings.js +++ /dev/null @@ -1,54 +0,0 @@ -import useModelSettings from "../../global/useModelSettings.js"; -import settingSection from "./settingSection.js"; - -let settings = {}, init = false; - -const { - componentReMount, componetDismount, - updateSettings:updateModelSettings, setToDefault -} = useModelSettings(s=>settings = s); - -const fields = { - temperature: { title: 'Temperature', valueRange: { min: 0, max: 2, is_100_times: true } }, - top_k: { title: 'Top-K', valueRange: { min: 0, max: 40 } }, - top_p: { title: 'Top-P', valueRange: { min: 0, max: 0.9, is_100_times: true } }, - n_predict: { title: 'N-Predict', valueRange: { min: 128, max: 512 } } -} - -export default function createModelSettings(main) { - componentReMount(); - - if(!init) { - const model_settings = document.createElement('div'); - model_settings.className = 'model-settings'; - model_settings.onclick = event => event.stopPropagation(); - - model_settings.insertAdjacentHTML('afterbegin', ` -
Adjust Model Settings
-
Settings will be saved automatically
- `) - - for(const key in fields) { - const { title, valueRange } = fields[key]; - const [component, setter] = settingSection( - title, valueRange, - () => { setToDefault(key) && loadSettings() }, - value=>updateModelSettings(key, value) - ) - model_settings.appendChild(component); - fields[key].setValue = setter; - } - - main.appendChild(model_settings); - loadSettings(); - init = true; - } - - return componetDismount; -} - -function loadSettings() { - for(const key in fields) { - fields[key].setValue(settings[key]); - } -} \ No newline at end of file diff --git a/components/chat-page/settingSection.js b/components/chat-page/range-setting-section.js similarity index 96% rename from components/chat-page/settingSection.js rename to components/chat-page/range-setting-section.js index c4b5c59..81ce246 100644 --- a/components/chat-page/settingSection.js +++ b/components/chat-page/range-setting-section.js @@ -1,6 +1,6 @@ import getSVG from "../../tools/svgs.js"; -export default function settingSection(title, valueRange, setToDefault, updateSetting) { +export default function rangeSettingSection(title, valueRange, setToDefault, updateSetting) { const { max, min, from_range, to_range } = rangeValueConverter(valueRange) diff --git a/components/chat-page/session-settings.js b/components/chat-page/session-settings.js new file mode 100644 index 0000000..bfb2001 --- /dev/null +++ b/components/chat-page/session-settings.js @@ -0,0 +1,41 @@ +import useConversation from '../../global/useConversation.js'; +import showMessage from '../../tools/message.js'; +import textSettingSection from './text-setting-section.js'; + +let current_conversation = {}, session_settings, name_setter; + +const { rename } = useConversation(c=>{ + if(session_settings) { + session_settings.classList.toggle('disabled', !c.id) + } + if(c.name && name_setter && current_conversation.name !== c.name) { + name_setter(c.name) + } + current_conversation = c; +}); + +export default function createSessionSettings(main) { + session_settings = document.createElement('div'); + session_settings.className = 'session-settings disabled' + session_settings.innerHTML = ` +
Adjust Session Settings
+
* Cannot change RAG settings after session started *
+ ` + + const [rename_elem, setName] = textSettingSection('Rename Session', new_name=>{ + if(!new_name) { + setName(current_conversation.name) + } else if(new_name === current_conversation.name) { + return; + } else { + rename(new_name).then(success=>{ + if(success) showMessage(`Session renamed to
${new_name}`, { type: 'success' }); + else showMessage('Rename session failed!', { type: 'err' }) + }) + } + }) + session_settings.appendChild(rename_elem); + name_setter = setName; + + main.appendChild(session_settings); +} \ No newline at end of file diff --git a/components/chat-page/settings.js b/components/chat-page/settings.js new file mode 100644 index 0000000..12948dd --- /dev/null +++ b/components/chat-page/settings.js @@ -0,0 +1,20 @@ +import createModelSettings from "./model-settings.js"; +import createSessionSettings from "./session-settings.js"; + +let init = false + +export default function createChatSettingsPage(main) { + if(!init) { + const setting_main = document.createElement('div'); + setting_main.className = 'chat-settings' + setting_main.onclick = evt => evt.stopPropagation(); + + createSessionSettings(setting_main); + createModelSettings(setting_main); + + main.appendChild(setting_main); + init = true; + } + + return null; +} \ No newline at end of file diff --git a/components/chat-page/text-setting-section.js b/components/chat-page/text-setting-section.js new file mode 100644 index 0000000..9de75f6 --- /dev/null +++ b/components/chat-page/text-setting-section.js @@ -0,0 +1,35 @@ +import getSVG from "../../tools/svgs.js"; + +export default function textSettingSection(title, callback) { + const section = document.createElement('form'); + section.className = 'setting-section' + section.innerHTML = `
${title}
`; + + const input_section = document.createElement('div'); + input_section.className = 'input-with-confirm'; + + const input = document.createElement('input'); + input.type = 'text'; + + function setter(value) { + input.value = value; + } + + input_section.appendChild(input); + + const confirm_btn = document.createElement('div'); + confirm_btn.className = 'confirm-input clickable'; + confirm_btn.innerHTML = getSVG('check'); + confirm_btn.onclick = () => callback(input.value); + + + section.onsubmit = evt => { + evt.preventDefault(); + callback(input.value); + } + + input_section.appendChild(confirm_btn); + section.appendChild(input_section) + + return [section, setter]; +} \ No newline at end of file diff --git a/global/createHook.js b/global/createHook.js index ac92fe6..6b81e30 100644 --- a/global/createHook.js +++ b/global/createHook.js @@ -13,9 +13,13 @@ export default function createHook() { } function remount(key) { - return () => { + return value => { const need_unfreeze = updatesList[key].frozen updatesList[key].frozen = false; + if(need_unfreeze) { + const callback = updatesList[key].callback; + callback && callback(value); + } return need_unfreeze; } } diff --git a/global/useConversation.js b/global/useConversation.js index 2288177..79b0862 100644 --- a/global/useConversation.js +++ b/global/useConversation.js @@ -6,6 +6,7 @@ import useUser from "./useUser.js"; let currentConversation = { id: null, pending: false, + name: '', history: [] }; @@ -14,7 +15,7 @@ const conversation_histories = {} let currentUser; const { onmount, remount, dismount, updateAll } = createHook(); -const { addHistory } = useHistory(h=>{ +const { addHistory, updateHistoryName } = useHistory(h=>{ if(currentConversation.id) { if(!h.filter(e=>e.id === currentConversation.id).length) { currentConversation = { @@ -26,61 +27,71 @@ const { addHistory } = useHistory(h=>{ }); useUser(user=>currentUser = user); -export default function useConversation(updated) { - const mount_key = onmount(updated); +function storeHistory() { + const id = currentConversation.id; + if(id) conversation_histories[id] = currentConversation.history; +} - function storeHistory() { - const id = currentConversation.id; - if(id) conversation_histories[id] = currentConversation.history; - } +async function rename(name) { + currentConversation.name = name; + return await updateHistoryName(currentConversation.id, name); +} - async function startNewConversation() { - storeHistory(); - const { sessionUuid } = await request('chat/seesionuuid'); - currentConversation = { - id: sessionUuid, history: [] - }; - addHistory({ - id: currentConversation.id, - name: 'New Session', - createdAt: new Date().toUTCString() - }) - updateAll(currentConversation); - } +async function startNewConversation() { + storeHistory(); + const { sessionUuid } = await request('chat/seesionuuid'); + currentConversation = { + pending: false, id: sessionUuid, history: [], name: 'New Session' + }; + addHistory({ + id: currentConversation.id, + name: currentConversation.name, + createdAt: new Date().toUTCString() + }) + updateAll(currentConversation); +} - function togglePending() { - currentConversation.pending = !currentConversation.pending; - updateAll(currentConversation); - } +function togglePending() { + currentConversation.pending = !currentConversation.pending; + updateAll(currentConversation); +} - async function sendMessage(messages) { - await request('chat/save', { - method: 'POST', - body: { - sessionUuid: currentConversation.id, - chats: messages - } - }) - currentConversation.history.push(...messages); - updateAll(currentConversation); +async function sendMessage(messages) { + await request('chat/save', { + method: 'POST', + body: { + sessionUuid: currentConversation.id, + chats: messages + } + }) + currentConversation.history.push(...messages); + updateAll(currentConversation); +} + +async function selectConversation(id, name) { + let history; + if(currentUser.logged_in) { + history = await request(`chat/history/${id}`); + } else { + storeHistory(); + history = conversation_histories[id]; } + currentConversation = { id, history, pending: false, name }; + updateAll(currentConversation); +} - async function selectConversation(id) { - let history; - if(currentUser.logged_in) { - history = await request(`chat/history/${id}`); - } else { - storeHistory(); - history = conversation_histories[id]; - } - currentConversation = { id, history }; - updateAll(currentConversation); +export default function useConversation(updated) { + const mount_key = onmount(updated); + + function componentReMount() { + return remount(mount_key)(currentConversation) } updated && updated(currentConversation); return { - selectConversation, startNewConversation, sendMessage, togglePending, - componetDismount:dismount(mount_key), componentReMount:remount(mount_key) + selectConversation, startNewConversation, + sendMessage, togglePending, rename, + componetDismount:dismount(mount_key), componentReMount } } \ No newline at end of file diff --git a/global/useHistory.js b/global/useHistory.js index 7254870..8d19d7a 100644 --- a/global/useHistory.js +++ b/global/useHistory.js @@ -6,68 +6,57 @@ const history = [ // {id: 0, name: 'New Conversation', createdAt: new Date().toUTCString()}, // {id: 1, name: 'New Conversation', createdAt: new Date().toUTCString()}, ]; -let init = false; let currentSession; const { onmount, remount, dismount, updateAll } = createHook(); + +async function requestUpdateHistory() { + if(!currentSession) return; + + const chat_history = await request('chat'); + + history.length = 0; + chat_history.forEach(({sessionUuid, name, createdAt}) => { + history.push({id: sessionUuid, name, createdAt}); + }); + updateAll(history); +} + +function addHistory(new_ticket) { + history.push(new_ticket); + updateAll(history); +} + +async function updateHistoryName(id, name) { + history[history.findIndex(h=>h.id === id)].name = name; + const { http_error } = await request('chat/session', { + method: 'PATCH', + body: { sessionUuid: id, name } + }) + const success = !http_error; + if(success) updateAll(history); + return success; +} + +function getHistory(id) { + return history.filter(e=>e.id === id).pop(); +} useSessionId(id=>{ currentSession = id; + requestUpdateHistory(); }) -/** - * Hook for sync history - * @param {Function} updated callback function when history updated - * @returns {Object} List of history operators - */ export default function useHistory(updated = null) { const mount_key = onmount(updated) - async function requestUpdateHistory() { - if(!currentSession) return; - - const chat_history = await request('chat'); - - history.length = 0; - chat_history.forEach(({sessionUuid, name, createdAt}) => { - history.push({id: sessionUuid, name, createdAt}); - }); - updateAll(history); - } - - function addHistory(new_ticket) { - history.push(new_ticket); - updateAll(history); - } - - function clearHistory() { - history.length = 0; - init = false; - } - - function updateHistoryName(id, name) { - for(const index in history) { - if(history[index].id === id) { - history[index].name = name; - // TODO: tell backend - } - } - updateAll(history); - } - - function getHistory(id) { - return history.filter(e=>e.id === id).pop(); - } - - if(!init) { - init = true; - requestUpdateHistory(); + function componentReMount() { + return remount(mount_key)(history) } - // send history use callback function updated && updated(history); return { - requestUpdateHistory, addHistory, clearHistory, getHistory, updateHistoryName, - componetDismount: dismount(mount_key), componentReMount:remount(mount_key) + requestUpdateHistory, addHistory, getHistory, updateHistoryName, + componetDismount: dismount(mount_key), componentReMount } } \ No newline at end of file diff --git a/global/useModelSettings.js b/global/useModelSettings.js index 016b59f..7cd176b 100644 --- a/global/useModelSettings.js +++ b/global/useModelSettings.js @@ -24,31 +24,35 @@ const writeSettingsToLocal = debounce(()=>{ const { onmount, remount, dismount, updateAll } = createHook(); -export default function useModelSettings(updated) { - const mount_key = onmount(updated) +function updateSettings(key, value) { + currentSettings = { + ...currentSettings, + [key]: value + } + updateAll(currentSettings); + writeSettingsToLocal(); +} - function updateSettings(key, value) { - currentSettings = { - ...currentSettings, - [key]: value - } - updateAll(currentSettings); - writeSettingsToLocal(); +function setToDefault(key) { + if(key in defaultSettings) { + updateSettings(key, defaultSettings[key]) + return true; + } else { + return false; } +} + +export default function useModelSettings(updated) { + const mount_key = onmount(updated) - function setToDefault(key) { - if(key in defaultSettings) { - updateSettings(key, defaultSettings[key]) - return true; - } else { - return false; - } + function componentReMount() { + return remount(mount_key)(currentSettings) } - updated(currentSettings); + updated && updated(currentSettings); return { updateSettings, setToDefault, - componetDismount: dismount(mount_key), componentReMount:remount(mount_key) + componetDismount: dismount(mount_key), componentReMount } } \ No newline at end of file diff --git a/global/useSessionId.js b/global/useSessionId.js index 8f12638..6c4daad 100644 --- a/global/useSessionId.js +++ b/global/useSessionId.js @@ -1,31 +1,42 @@ import request from "../tools/request.js"; import createHook from "./createHook.js"; +import cookies from "../tools/cookies.js"; -let currentSession = null, init = false; +const cookie = cookies(); +let currentSession = cookie.getItem('current-session') || null; const { onmount, remount, dismount, updateAll } = createHook(); +function updateCookie() { + cookie.setItem('current-session', currentSession); +} + +async function genSession() { + const { token } = await request('auth/token'); + currentSession = token; + updateCookie(); + updateAll(currentSession); +} + +function manualUpdateSession(sessionId) { + currentSession = sessionId; + updateCookie(); + updateAll(currentSession); +} + +if(!currentSession) genSession(); + export default function useSessionId(updated) { const mount_key = onmount(updated) - async function genSession() { - const { token } = await request('auth/token'); - currentSession = token; - updateAll(currentSession); + function componentReMount() { + return remount(mount_key)(currentSession) } - function manualUpdateSession(sessionId) { - currentSession = sessionId; - updateAll(currentSession); - } - - if(!currentSession && !init) { - init = true; - genSession(); - } + updated && updated(currentSession); return { manualUpdateSession, genSession, - componetDismount: dismount(mount_key), componentReMount:remount(mount_key) + componetDismount: dismount(mount_key), componentReMount } } \ No newline at end of file diff --git a/global/useUser.js b/global/useUser.js index f9b7938..0674a43 100644 --- a/global/useUser.js +++ b/global/useUser.js @@ -26,79 +26,84 @@ const { manualUpdateSession, genSession } = useSessionId(); const { onmount, remount, dismount, updateAll } = createHook(); -export default function useUser(updated) { - const mount_key = onmount(updated) - - function setLoggedIn(id, username, email, token) { - userInfo = { - ...userInfo, username, email, - id, logged_in: true - } - manualUpdateSession(token); - cookie.setItem('current-user', { id, username, email, token }) - updateAll(userInfo); - requestUpdateHistory(); +function setLoggedIn(id, username, email, token) { + userInfo = { + ...userInfo, username, email, + id, logged_in: true } + manualUpdateSession(token); + cookie.setItem('current-user', { id, username, email }) + updateAll(userInfo); + requestUpdateHistory(); +} - async function login(username, password) { - const {id, authorizedAccount, http_error} = await request('auth/signin', { - method: 'POST', - body: { username, password } - }); +async function login(username, password) { + const {id, authorizedAccount, http_error} = await request('auth/signin', { + method: 'POST', + body: { username, password } + }); - if(http_error) return false; + if(http_error) return false; - const { token, email } = authorizedAccount; - setLoggedIn(id, username, email, token); - return true; - } + const { token, email } = authorizedAccount; + setLoggedIn(id, username, email, token); + return true; +} - async function register(username, email, password) { - const {id, authorizedAccount, http_error} = await request('auth/signup', { - method: 'POST', - body: { username, email, password } - }) +async function register(username, email, password) { + const {id, authorizedAccount, http_error} = await request('auth/signup', { + method: 'POST', + body: { username, email, password } + }) - if(http_error) return false; + if(http_error) return false; - const { token } = authorizedAccount; - setLoggedIn(id, username, email, token); - return true; + const { token } = authorizedAccount; + setLoggedIn(id, username, email, token); + return true; +} + +async function logout() { + userInfo = { + logged_in: false, + id: null, + token: "", + username: "", + email: "" } + cookie.removeItem('current-user'); + cookie.removeItem('current-session'); + updateAll(userInfo); + await genSession(); + requestUpdateHistory(); +} - async function logout() { +async function updateUserInfo(fields) { + const {id, authorizedAccount, http_error} = await request('accounts', { + method: 'PATCH', + body: fields + }) + if(!http_error) { userInfo = { - logged_in: false, - id: null, - token: "", - username: "", - email: "" + ...userInfo, + id, email: authorizedAccount.email } - cookie.removeItem('current-user'); updateAll(userInfo); - await genSession(); - requestUpdateHistory(); - } + return true; + } return false; +} + +export default function useUser(updated) { + const mount_key = onmount(updated) - async function updateUserInfo(fields) { - const {id, authorizedAccount, http_error} = await request('accounts', { - method: 'PATCH', - body: fields - }) - if(!http_error) { - userInfo = { - ...userInfo, - id, email: authorizedAccount.email - } - updateAll(userInfo); - return true; - } return false; + function componentReMount() { + return remount(mount_key)(userInfo) } updated && updated(userInfo); return { login, register, logout, updateUserInfo, - componetDismount: dismount(mount_key), componentReMount:remount(mount_key) + componetDismount: dismount(mount_key), componentReMount } } \ No newline at end of file diff --git a/styles/account_page.css b/styles/account_page.css index e6e92e0..799c5e2 100644 --- a/styles/account_page.css +++ b/styles/account_page.css @@ -76,7 +76,7 @@ .input-details-main .account-field-container .title { position: absolute; color: gray; - font-size: 18px; + font-size: 17px; width: 100%; height: 100%; line-height: var(--item-height); diff --git a/styles/chat_page.css b/styles/chat_page.css index 017790a..9252484 100644 --- a/styles/chat_page.css +++ b/styles/chat_page.css @@ -267,7 +267,7 @@ } #chat-page #chat-main #submit-chat input:focus { outline: none; } -#chat-page #chat-main #submit-chat .send { +#chat-page #chat-main #submit-chat .right-button { position: absolute; right: 0; width: var(--send-section-size); @@ -276,7 +276,7 @@ align-items: center; } -#chat-page #chat-main #submit-chat .send .submit-btn { +#chat-page #chat-main #submit-chat .right-button .btn { z-index: 2; width: 100%; height: 100%; @@ -284,18 +284,28 @@ position: absolute; } -#chat-page #chat-main #submit-chat .send .submit-icon { +#chat-page #chat-main #submit-chat .right-button .icon { z-index: 1; width: 50%; height: 50%; margin: auto; + color: black; +} +#chat-page #chat-main #submit-chat .right-button .icon.send { transform-origin: center; transform: rotate(45deg); color: dodgerblue; } -#chat-page #chat-main #submit-chat .send .submit-icon.pending { - color: gray; +#chat-page #chat-main #submit-chat .abort-message { + display: none; +} + +#submit-chat.pending .right-button:not(.abort-message) { + display: none !important; +} +#submit-chat.pending .abort-message { + display: flex !important; } /* @@ -306,7 +316,7 @@ ===================================================== */ -.model-settings { +.chat-settings { --model-setting-width: 400px; --model-setting-height: 500px; @@ -321,74 +331,103 @@ border-radius: 10px; } -.model-settings .title { +.chat-settings .session-settings.disabled { + display: none; +} + +.chat-settings .title { margin: 10px 20px; font-size: 25px; font-weight: bolder; text-align: center; } -.model-settings .sub-title { +.chat-settings .sub-title { text-align: center; font-size: 13px; color: gray; margin-bottom: 7px; } -.model-settings .setting-section { +.chat-settings .setting-section { + --input-height: 40px; + width: 100%; padding: 15px 20px; + box-sizing: border-box; + display: block; } -.model-settings .setting-section .title { +.chat-settings .setting-section .title { margin: unset; - margin-bottom: 3px; + margin-bottom: 6px; font-size: 17px; font-weight: unset; text-align: left; } -.model-settings .setting-section input[type="text"] { - height: 40px; +.chat-settings .setting-section input[type="text"] { + height: var(--input-height); box-sizing: border-box; - border-radius: 10px; + border-radius: 5px; padding: 0px 10px; border: 1px solid gray; - width: 50%; + width: 100%; } -.model-settings .setting-section input[type="text"]:focus { +.chat-settings .setting-section input[type="text"]:focus { outline: none; border-width: 2px; } -.model-settings .setting-section .combined-section { +.chat-settings .setting-section .input-with-confirm { + position: relative; +} + +.chat-settings .setting-section .input-with-confirm .confirm-input { + height: var(--input-height); + width: var(--input-height); + position: absolute; + right: 0; + top: 0; + display: flex; + align-items: center; + color: dodgerblue; +} +.chat-settings .setting-section .input-with-confirm .confirm-input svg { + margin: auto; + width: 50%; + height: 50%; +} + +.chat-settings .setting-section .combined-section { --item-margin: 10px; display: flex; align-items: center; } -.model-settings .setting-section .combined-section * { margin: auto; margin-right: var(--item-margin); } -.model-settings .setting-section .combined-section *:last-child { margin-right: unset; } +.chat-settings .setting-section .combined-section * { margin: auto; margin-right: var(--item-margin); } +.chat-settings .setting-section .combined-section *:last-child { margin-right: unset; } -.model-settings .setting-section .combined-section.range { +.chat-settings .setting-section .combined-section.range { width: 100%; --restore-default-icon-size: 20px; --range-input-width: calc(75% - var(--restore-default-icon-size)); --text-input-width: 25%; } -.model-settings .setting-section .combined-section.range input[type="range"] { +.chat-settings .setting-section .combined-section.range input[type="range"] { width: var(--range-input-width); } -.model-settings .setting-section .combined-section.range input[type="text"] { +.chat-settings .setting-section .combined-section.range input[type="text"] { width: var(--text-input-width); + border-radius: 10px; text-align: center; } -.model-settings .setting-section .combined-section.range .restore-default-icon { +.chat-settings .setting-section .combined-section.range .restore-default-icon { width: var(--restore-default-icon-size); height: var(--restore-default-icon-size); color: dodgerblue; } -.model-settings .setting-section .combined-section.range .restore-default-icon svg { +.chat-settings .setting-section .combined-section.range .restore-default-icon svg { width: 100%; height: 100%; } \ No newline at end of file diff --git a/styles/message.css b/styles/message.css index 808af34..4468e6f 100644 --- a/styles/message.css +++ b/styles/message.css @@ -64,4 +64,6 @@ font-size: 17px; padding: 5px 0px; color: black; + max-width: 300px; + word-wrap: break-word; } \ No newline at end of file diff --git a/tools/cookies.js b/tools/cookies.js index aca4b65..25f8053 100644 --- a/tools/cookies.js +++ b/tools/cookies.js @@ -1,8 +1,7 @@ function writeObjToCookie(cookie_items) { - document.cookie = - Object.entries(cookie_items).map(([key, value])=>{ - return `${key}=${value}` - }).join('; '); + Object.entries(cookie_items).forEach(([key, value])=>{ + document.cookie=`${key}=${value}`; + }); } function loadObjFromCookie() { @@ -15,8 +14,9 @@ function loadObjFromCookie() { return cookie_items; } +const cookie_items = loadObjFromCookie(); + export default function cookies() { - const cookie_items = loadObjFromCookie(); function getItem(key) { return cookie_items[key]; @@ -43,8 +43,8 @@ export default function cookies() { function removeItem(key) { const value = cookie_items[key]; - delete cookie_items[key]; - writeObjToCookie(cookie_items); + document.cookie=`${key}=` + delete cookie_items[key] return value; } diff --git a/tools/dialog.js b/tools/dialog.js index fe8e27e..5775f56 100644 --- a/tools/dialog.js +++ b/tools/dialog.js @@ -12,6 +12,7 @@ document.addEventListener("keydown", closeDialogListener) export default function createDialog(modal_close_on_click = true) { const dialog = document.createElement('div'); dialog.className = 'mock-dialog'; + document.body.appendChild(dialog); let open_status = false, opened_idx = -1; diff --git a/tools/svgs.js b/tools/svgs.js index a775442..172abc3 100644 --- a/tools/svgs.js +++ b/tools/svgs.js @@ -134,6 +134,16 @@ const svgs = { "x-circle-fill": ` + `, + + "stop-circle-fill": ` + + + `, + + "check": ` + + ` } diff --git a/tools/validators.js b/tools/validators.js new file mode 100644 index 0000000..90908c4 --- /dev/null +++ b/tools/validators.js @@ -0,0 +1,21 @@ +export const email_not_valid = 'Email pattern not valid!'; +export const password_not_valid = 'Password should have at least 8 characters combines capital letters, lowercase letters, numbers and special characters.'; +export const username_not_valid = 'Username should includes only letters, numbers, -, _ and white space, no less than 4 characters and no more than 20 characters.'; + +export function validateEmail(string) { + return ( + /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(string) + ) +} + +export function validatePassword(string) { + return ( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()-_+=])[a-zA-Z\d!@#$%^&*()-_+=]{8,}$/.test(string) + ) +} + +export function validateUsername(string) { + return ( + /^[a-zA-Z0-9-_ ]{4,20}$/.test(string) + ) +} \ No newline at end of file