diff options
| author | sigoden <sigoden@gmail.com> | 2024-08-31 17:53:57 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-08-31 17:53:57 +0800 |
| commit | ed242c65f0e92298d45e61d519a086af32a0eba4 (patch) | |
| tree | b5bb2e193b87ab54cf13edc914484c1454453503 /assets | |
| parent | a204a4b1894bff869a1f96ae48a54e7d9728e461 (diff) | |
| download | aichat-ed242c65f0e92298d45e61d519a086af32a0eba4.tar.gz | |
feat: webui use querystring as settings (#814)
Diffstat (limited to 'assets')
| -rw-r--r-- | assets/arena.html | 62 | ||||
| -rw-r--r-- | assets/playground.html | 100 |
2 files changed, 81 insertions, 81 deletions
diff --git a/assets/arena.html b/assets/arena.html index 4591abd..f6d779d 100644 --- a/assets/arena.html +++ b/assets/arena.html @@ -392,9 +392,9 @@ <template x-for="(chat, index) in chats" :key="index"> <div class="chat-panel"> <div class="chat-header"> - <select x-cloak id="model" x-model="chat.model_id" @change="handleModelIdChange"> - <template x-for="model in models" :key="model.id"> - <option :value="model.id" :selected="model.id == chat.model_id" x-text="model.id"></option> + <select x-cloak id="model" x-model="chat.model" @change="handleModelChange"> + <template x-for="model in chatModels" :key="model.id"> + <option :value="model.id" :selected="model.id == chat.model" x-text="model.id"></option> </template> </select> </div> @@ -548,13 +548,12 @@ <div id="toast" class="toast"></div> </div> <script> - const QUERY = parseQueryString(location.search); + const QUERY = parseQueryString(); const NUM = parseInt(QUERY.num) || 2 const API_BASE = QUERY.api_base || "./v1"; const API_KEY = QUERY.api_key || ""; const CHAT_COMPLETIONS_URL = API_BASE + "/chat/completions"; const MODELS_API = API_BASE + "/models"; - const MODEL_IDS_STORAGE_KEY = "__model_ids__"; document.addEventListener("alpine:init", () => { setupMarked(); @@ -568,12 +567,12 @@ let msgIdx = 0; Alpine.data("app", () => ({ - models: [], + chatModels: [], input: "", images: [], asking: 0, chats: Array.from(Array(NUM)).map(_ => ({ - model_id: "", + model: "", messages: [], hoveredMessageIndex: null, askAbortController: null, @@ -584,20 +583,20 @@ async init() { try { const models = await fetchJSON(MODELS_API); - this.models = models.filter(v => !v.type || v.type === "chat"); + this.chatModels = models.filter(v => !v.type || v.type === "chat"); } catch (err) { toast("No available model"); console.error("Failed to load models", err); } - let model_ids = [] - try { - model_ids = JSON.parse(localStorage.getItem(MODEL_IDS_STORAGE_KEY)) || []; - } catch { } + let models = [] + if (QUERY.models) { + models = QUERY.models.split(","); + } $chatPanels = document.querySelectorAll('.chat-panel'); $scrollToBottomBtns = document.querySelectorAll('.scroll-to-bottom-btn'); const offsets = calculateOffsets(NUM); for (let i = 0; i < NUM; i++) { - this.chats[i].model_id = model_ids[i] || "default"; + this.chats[i].model = models[i] || "default"; $chatPanels[i].style.width = (100 / NUM) + '%'; if (i > 0) { $chatPanels[i].style.borderLeft = '1px solid var(--border-primary)'; @@ -611,7 +610,7 @@ }, get supportsVision() { - return this.chats.every(v => !!retrieveModel(this.models, v.model_id)?.supports_vision) + return this.chats.every(v => !!retrieveModel(this.chatModels, v.model)?.supports_vision) }, handleAsk() { @@ -697,9 +696,8 @@ } }, - handleModelIdChange() { - let model_ids = this.chats.map(v => v.model_id); - localStorage.setItem(MODEL_IDS_STORAGE_KEY, JSON.stringify(model_ids)); + handleModelChange() { + this.updateUrl(); }, handleScrollChatBody(event, index) { @@ -772,6 +770,13 @@ event.target.value = ""; }, + updateUrl() { + const newUrl = new URL(location.href); + const models = this.chats.map(v => v.model).join(","); + newUrl.searchParams.set("models", models); + history.replaceState(null, '', newUrl.toString()); + }, + autoHeightChatPanel() { const height = $inputPanel.offsetHeight; for (let i = 0; i < this.chats.length; i++) { @@ -852,11 +857,11 @@ } } const body = { - model: chat.model_id, + model: chat.model, messages: messages, stream: true, }; - const { max_output_token, require_max_tokens } = retrieveModel(this.models, chat.model_id); + const { max_output_token, require_max_tokens } = retrieveModel(this.chatModels, chat.model); if (!body["max_tokens"] && require_max_tokens) { body["max_tokens"] = max_output_token; }; @@ -1005,20 +1010,13 @@ return input.replace(/([&<>'"])/g, char => escapeMap[char]); } - function parseQueryString(queryString) { - const params = {}; - if (!queryString) { - return params; - } - const queries = queryString[0] === '?' ? queryString.substring(1) : queryString; - const pairs = queries.split('&'); - - pairs.forEach(pair => { - const [key, value] = pair.split('='); - params[decodeURIComponent(key)] = decodeURIComponent(value ? value.replace(/\+/g, ' ') : ''); + function parseQueryString() { + const params = new URLSearchParams(location.search); + const queryObject = {}; + params.forEach((value, key) => { + queryObject[key] = value; }); - - return params; + return queryObject; } function chunkArray(array, chunkSize) { diff --git a/assets/playground.html b/assets/playground.html index 9ea0881..da2e32c 100644 --- a/assets/playground.html +++ b/assets/playground.html @@ -503,7 +503,7 @@ <div class="settings"> <div class="control"> <label for="role">Role</label> - <select id="role" x-model="settings.role" :disabled="sessionMode" @change="handleRoleChange"> + <select id="role" x-model="settings.role" :disabled="sessionMode"> <template x-for="role in roles"> <option :value="role.name" :selected="role.name == settings.role" x-text="role.name"></option> </template> @@ -517,7 +517,7 @@ <div class="control"> <label for="max_output_tokens" - x-text="'Max Output Tokens' + (currentModel.max_output_token ? ' [1, ' + currentModel.max_output_token + ']' : '')">Max + x-text="'Max Output Tokens' + (modelData.max_output_token ? ' [1, ' + modelData.max_output_token + ']' : '')">Max Output Tokens</label> <input type="number" id="max_output_tokens" x-model="settings.max_output_tokens"> </div> @@ -536,9 +536,9 @@ </div> <div class="main-panel" x-ref="main-panel"> <div class="chat-header"> - <select id="model" x-model="settings.model_id"> + <select id="model" x-model="settings.model"> <template x-for="model in models" :key="model.id"> - <option :value="model.id" :selected="model.id == settings.model_id" x-text="model.id"></option> + <option :value="model.id" :selected="model.id == settings.model" x-text="model.id"></option> </template> </select> <div class="toolbar"> @@ -671,7 +671,7 @@ </template> <template x-if="!asking"> <div class="input-toolbox"> - <div class="image-btn" x-show="currentModel.supports_vision"> + <div class="image-btn" x-show="modelData.supports_vision"> <input type="file" multiple accept=".jpg,.jpeg,.png,.webp" @change="handleImageUpload"> <svg fill="currentColor" viewBox="0 0 16 16"> <path d="M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0" /> @@ -694,13 +694,12 @@ <div id="toast" class="toast"></div> </div> <script> - const QUERY = parseQueryString(location.search); + const QUERY = parseQueryString(); const API_BASE = QUERY.api_base || "./v1"; const API_KEY = QUERY.api_key || ""; const CHAT_COMPLETIONS_URL = API_BASE + "/chat/completions"; const MODELS_API = API_BASE + "/models"; const ROLES_API = API_BASE + "/roles"; - const SETTINGS_STORAGE_KEY = "__settings__"; document.addEventListener("alpine:init", () => { setupMarked(); @@ -710,28 +709,18 @@ function setupApp() { let msgIdx = 0; let defaultSettings = { - model_id: "default", - role: "", + model: QUERY.model || "default", + role: QUERY.role || "", prompt: "", - max_output_tokens: null, - temperature: null, - top_p: null, + max_output_tokens: parseInt(QUERY.max_output_tokens) || null, + temperature: QUERY.temperature ? parseFloat(QUERY.temperature) : null, + top_p: QUERY.top_p ? parseFloat(QUERY.top_p) : null, }; - try { - const persistSettings = JSON.parse(localStorage.getItem(SETTINGS_STORAGE_KEY)) || {}; - const sanitizedPersistSettings = Object.keys(defaultSettings).reduce((acc, cur) => { - if (persistSettings.hasOwnProperty(cur)) { - acc[cur] = persistSettings[cur]; - } - return acc; - }, {}); - defaultSettings = { ...defaultSettings, ...sanitizedPersistSettings }; - } catch { } Alpine.data("app", () => ({ models: [], roles: [{ name: "", prompt: "" }], - currentModel: {}, + modelData: {}, messages: [], hoveredMessageIndex: null, input: "", @@ -757,15 +746,19 @@ }).catch(() => { }), ]) this.$watch("input", () => this.autosizeInput(this.$refs.input)); - this.$watch("settings", () => { - localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(this.settings)); - }); - this.$watch("settings.model_id", () => this.handleModelIdChange()); - this.handleModelIdChange(); - }, - - handleRoleChange(event) { - this.settings.prompt = this.roles.find(role => role.name === event.target.value).prompt; + this.$watch("settings", () => this.updateUrl()); + this.$watch("settings.model", () => this.handleModelChange()); + if (this.models.find(model => model.id === this.settings.model)) { + this.handleModelChange(); + } else { + this.settings.model = "default"; + } + this.$watch("settings.role", () => this.handleRoleChange()) + if (this.roles.find(role => role.name === this.settings.role)) { + this.handleRoleChange(); + } else { + this.settings.role = ""; + } }, handleAsk() { @@ -840,8 +833,12 @@ this.askAbortController?.abort(); }, - handleModelIdChange() { - this.currentModel = retrieveModel(this.models, this.settings.model_id); + handleModelChange() { + this.modelData = retrieveModel(this.models, this.settings.model); + }, + + handleRoleChange() { + this.settings.prompt = this.roles.find(role => role.name === this.settings.role).prompt; }, handleScrollChatBody(event) { @@ -923,6 +920,18 @@ event.target.value = ""; }, + updateUrl() { + const newUrl = new URL(location.href); + ["model", "role", "max_output_tokens", "temperature", "top_p"].forEach(key => { + if (this.settings[key] || typeof this.settings[key] === "number") { + newUrl.searchParams.set(key, this.settings[key]); + } else { + newUrl.searchParams.delete(key); + } + }); + history.replaceState(null, '', newUrl.toString()); + }, + autoScrollChatBodyToBottom() { if (this.shouldScrollChatBodyToBottom) { let $chatBody = this.$refs["chat-body"]; @@ -1022,7 +1031,7 @@ } } const body = { - model: this.settings.model_id, + model: this.settings.model, messages: messages, stream: true, }; @@ -1031,7 +1040,7 @@ body[body_key || setting_key] = this.settings[setting_key]; } }); - const { max_output_token, require_max_tokens } = this.currentModel; + const { max_output_token, require_max_tokens } = this.modelData; if (!body["max_tokens"] && require_max_tokens) { body["max_tokens"] = max_output_token; }; @@ -1228,20 +1237,13 @@ return input.replace(/([&<>'"])/g, char => escapeMap[char]); } - function parseQueryString(queryString) { - const params = {}; - if (!queryString) { - return params; - } - const queries = queryString[0] === '?' ? queryString.substring(1) : queryString; - const pairs = queries.split('&'); - - pairs.forEach(pair => { - const [key, value] = pair.split('='); - params[decodeURIComponent(key)] = decodeURIComponent(value ? value.replace(/\+/g, ' ') : ''); + function parseQueryString() { + const params = new URLSearchParams(location.search); + const queryObject = {}; + params.forEach((value, key) => { + queryObject[key] = value; }); - - return params; + return queryObject; } function chunkArray(array, chunkSize) { |
