summaryrefslogtreecommitdiffstats
path: root/assets
diff options
context:
space:
mode:
authorsigoden <sigoden@gmail.com>2024-08-31 17:53:57 +0800
committerGitHub <noreply@github.com>2024-08-31 17:53:57 +0800
commited242c65f0e92298d45e61d519a086af32a0eba4 (patch)
treeb5bb2e193b87ab54cf13edc914484c1454453503 /assets
parenta204a4b1894bff869a1f96ae48a54e7d9728e461 (diff)
downloadaichat-ed242c65f0e92298d45e61d519a086af32a0eba4.tar.gz
feat: webui use querystring as settings (#814)
Diffstat (limited to 'assets')
-rw-r--r--assets/arena.html62
-rw-r--r--assets/playground.html100
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) {