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