add jsdoc types
This commit is contained in:
parent
fa225a423a
commit
7b3ac17c7b
1 changed files with 103 additions and 31 deletions
|
@ -17,6 +17,11 @@ import { asyncIterator } from '@sec-ant/readable-stream/ponyfill/asyncIterator';
|
||||||
|
|
||||||
const isDev = import.meta.env.MODE === 'development';
|
const isDev = import.meta.env.MODE === 'development';
|
||||||
|
|
||||||
|
// types
|
||||||
|
/** @typedef {{ id: number, role: 'user' | 'assistant', content: string, timings: any }} Message */
|
||||||
|
/** @typedef {{ role: 'user' | 'assistant', content: string }} APIMessage */
|
||||||
|
/** @typedef {{ id: string, lastModified: number, messages: Array<Message> }} Conversation */
|
||||||
|
|
||||||
// utility functions
|
// utility functions
|
||||||
const isString = (x) => !!x.toLowerCase;
|
const isString = (x) => !!x.toLowerCase;
|
||||||
const isBoolean = (x) => x === true || x === false;
|
const isBoolean = (x) => x === true || x === false;
|
||||||
|
@ -235,7 +240,10 @@ const MessageBubble = defineComponent({
|
||||||
// format: { [convId]: { id: string, lastModified: number, messages: [...] } }
|
// format: { [convId]: { id: string, lastModified: number, messages: [...] } }
|
||||||
// convId is a string prefixed with 'conv-'
|
// convId is a string prefixed with 'conv-'
|
||||||
const StorageUtils = {
|
const StorageUtils = {
|
||||||
// manage conversations
|
/**
|
||||||
|
* manage conversations
|
||||||
|
* @returns {Array<Conversation>}
|
||||||
|
*/
|
||||||
getAllConversations() {
|
getAllConversations() {
|
||||||
const res = [];
|
const res = [];
|
||||||
for (const key in localStorage) {
|
for (const key in localStorage) {
|
||||||
|
@ -246,11 +254,19 @@ const StorageUtils = {
|
||||||
res.sort((a, b) => b.lastModified - a.lastModified);
|
res.sort((a, b) => b.lastModified - a.lastModified);
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
// can return null if convId does not exist
|
/**
|
||||||
|
* can return null if convId does not exist
|
||||||
|
* @param {string} convId
|
||||||
|
* @returns {Conversation | null}
|
||||||
|
*/
|
||||||
getOneConversation(convId) {
|
getOneConversation(convId) {
|
||||||
return JSON.parse(localStorage.getItem(convId) || 'null');
|
return JSON.parse(localStorage.getItem(convId) || 'null');
|
||||||
},
|
},
|
||||||
// if convId does not exist, create one
|
/**
|
||||||
|
* if convId does not exist, create one
|
||||||
|
* @param {string} convId
|
||||||
|
* @param {Message} msg
|
||||||
|
*/
|
||||||
appendMsg(convId, msg) {
|
appendMsg(convId, msg) {
|
||||||
if (msg.content === null) return;
|
if (msg.content === null) return;
|
||||||
const conv = StorageUtils.getOneConversation(convId) || {
|
const conv = StorageUtils.getOneConversation(convId) || {
|
||||||
|
@ -262,12 +278,24 @@ const StorageUtils = {
|
||||||
conv.lastModified = Date.now();
|
conv.lastModified = Date.now();
|
||||||
localStorage.setItem(convId, JSON.stringify(conv));
|
localStorage.setItem(convId, JSON.stringify(conv));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Get new conversation id
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
getNewConvId() {
|
getNewConvId() {
|
||||||
return `conv-${Date.now()}`;
|
return `conv-${Date.now()}`;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* remove conversation by id
|
||||||
|
* @param {string} convId
|
||||||
|
*/
|
||||||
remove(convId) {
|
remove(convId) {
|
||||||
localStorage.removeItem(convId);
|
localStorage.removeItem(convId);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* remove all conversations
|
||||||
|
* @param {string} convId
|
||||||
|
*/
|
||||||
filterAndKeepMsgs(convId, predicate) {
|
filterAndKeepMsgs(convId, predicate) {
|
||||||
const conv = StorageUtils.getOneConversation(convId);
|
const conv = StorageUtils.getOneConversation(convId);
|
||||||
if (!conv) return;
|
if (!conv) return;
|
||||||
|
@ -275,6 +303,11 @@ const StorageUtils = {
|
||||||
conv.lastModified = Date.now();
|
conv.lastModified = Date.now();
|
||||||
localStorage.setItem(convId, JSON.stringify(conv));
|
localStorage.setItem(convId, JSON.stringify(conv));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* remove last message from conversation
|
||||||
|
* @param {string} convId
|
||||||
|
* @returns {Message | undefined}
|
||||||
|
*/
|
||||||
popMsg(convId) {
|
popMsg(convId) {
|
||||||
const conv = StorageUtils.getOneConversation(convId);
|
const conv = StorageUtils.getOneConversation(convId);
|
||||||
if (!conv) return;
|
if (!conv) return;
|
||||||
|
@ -349,10 +382,12 @@ const mainApp = createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
conversations: StorageUtils.getAllConversations(),
|
conversations: StorageUtils.getAllConversations(),
|
||||||
messages: [], // { id: number, role: 'user' | 'assistant', content: string }
|
/** @type {Array<Message>} */
|
||||||
|
messages: [],
|
||||||
viewingConvId: StorageUtils.getNewConvId(),
|
viewingConvId: StorageUtils.getNewConvId(),
|
||||||
inputMsg: '',
|
inputMsg: '',
|
||||||
isGenerating: false,
|
isGenerating: false,
|
||||||
|
/** @type {Array<Message> | null} */
|
||||||
pendingMsg: null, // the on-going message from assistant
|
pendingMsg: null, // the on-going message from assistant
|
||||||
stopGeneration: () => {},
|
stopGeneration: () => {},
|
||||||
selectedTheme: StorageUtils.getTheme(),
|
selectedTheme: StorageUtils.getTheme(),
|
||||||
|
@ -360,6 +395,7 @@ const mainApp = createApp({
|
||||||
showConfigDialog: false,
|
showConfigDialog: false,
|
||||||
// const
|
// const
|
||||||
themes: THEMES,
|
themes: THEMES,
|
||||||
|
/** @type {CONFIG_DEFAULT} */
|
||||||
configDefault: {...CONFIG_DEFAULT},
|
configDefault: {...CONFIG_DEFAULT},
|
||||||
configInfo: {...CONFIG_INFO},
|
configInfo: {...CONFIG_INFO},
|
||||||
isDev,
|
isDev,
|
||||||
|
@ -452,42 +488,50 @@ const mainApp = createApp({
|
||||||
this.isGenerating = true;
|
this.isGenerating = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
/** @type {CONFIG_DEFAULT} */
|
||||||
|
const config = this.config;
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
this.stopGeneration = () => abortController.abort();
|
this.stopGeneration = () => abortController.abort();
|
||||||
|
/** @type {Array<APIMessage>} */
|
||||||
|
let messages = [
|
||||||
|
{ role: 'system', content: config.systemMessage },
|
||||||
|
...normalizeMsgsForAPI(this.messages),
|
||||||
|
];
|
||||||
|
if (config.excludeThoughtOnReq) {
|
||||||
|
messages = filterThoughtFromMsgs(messages);
|
||||||
|
}
|
||||||
|
if (isDev) console.log({messages});
|
||||||
const params = {
|
const params = {
|
||||||
messages: [
|
messages,
|
||||||
{ role: 'system', content: this.config.systemMessage },
|
|
||||||
...this.messages,
|
|
||||||
],
|
|
||||||
stream: true,
|
stream: true,
|
||||||
cache_prompt: true,
|
cache_prompt: true,
|
||||||
samplers: this.config.samplers,
|
samplers: config.samplers,
|
||||||
temperature: this.config.temperature,
|
temperature: config.temperature,
|
||||||
dynatemp_range: this.config.dynatemp_range,
|
dynatemp_range: config.dynatemp_range,
|
||||||
dynatemp_exponent: this.config.dynatemp_exponent,
|
dynatemp_exponent: config.dynatemp_exponent,
|
||||||
top_k: this.config.top_k,
|
top_k: config.top_k,
|
||||||
top_p: this.config.top_p,
|
top_p: config.top_p,
|
||||||
min_p: this.config.min_p,
|
min_p: config.min_p,
|
||||||
typical_p: this.config.typical_p,
|
typical_p: config.typical_p,
|
||||||
xtc_probability: this.config.xtc_probability,
|
xtc_probability: config.xtc_probability,
|
||||||
xtc_threshold: this.config.xtc_threshold,
|
xtc_threshold: config.xtc_threshold,
|
||||||
repeat_last_n: this.config.repeat_last_n,
|
repeat_last_n: config.repeat_last_n,
|
||||||
repeat_penalty: this.config.repeat_penalty,
|
repeat_penalty: config.repeat_penalty,
|
||||||
presence_penalty: this.config.presence_penalty,
|
presence_penalty: config.presence_penalty,
|
||||||
frequency_penalty: this.config.frequency_penalty,
|
frequency_penalty: config.frequency_penalty,
|
||||||
dry_multiplier: this.config.dry_multiplier,
|
dry_multiplier: config.dry_multiplier,
|
||||||
dry_base: this.config.dry_base,
|
dry_base: config.dry_base,
|
||||||
dry_allowed_length: this.config.dry_allowed_length,
|
dry_allowed_length: config.dry_allowed_length,
|
||||||
dry_penalty_last_n: this.config.dry_penalty_last_n,
|
dry_penalty_last_n: config.dry_penalty_last_n,
|
||||||
max_tokens: this.config.max_tokens,
|
max_tokens: config.max_tokens,
|
||||||
timings_per_token: !!this.config.showTokensPerSecond,
|
timings_per_token: !!config.showTokensPerSecond,
|
||||||
...(this.config.custom.length ? JSON.parse(this.config.custom) : {}),
|
...(config.custom.length ? JSON.parse(config.custom) : {}),
|
||||||
};
|
};
|
||||||
const chunks = sendSSEPostRequest(`${BASE_URL}/v1/chat/completions`, {
|
const chunks = sendSSEPostRequest(`${BASE_URL}/v1/chat/completions`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
...(this.config.apiKey ? {'Authorization': `Bearer ${this.config.apiKey}`} : {})
|
...(config.apiKey ? {'Authorization': `Bearer ${config.apiKey}`} : {})
|
||||||
},
|
},
|
||||||
body: JSON.stringify(params),
|
body: JSON.stringify(params),
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
|
@ -504,7 +548,7 @@ const mainApp = createApp({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const timings = chunk.timings;
|
const timings = chunk.timings;
|
||||||
if (timings && this.config.showTokensPerSecond) {
|
if (timings && config.showTokensPerSecond) {
|
||||||
// only extract what's really needed, to save some space
|
// only extract what's really needed, to save some space
|
||||||
this.pendingMsg.timings = {
|
this.pendingMsg.timings = {
|
||||||
prompt_n: timings.prompt_n,
|
prompt_n: timings.prompt_n,
|
||||||
|
@ -625,3 +669,31 @@ try {
|
||||||
<button class="btn" onClick="localStorage.clear(); window.location.reload();">Clear localStorage</button>
|
<button class="btn" onClick="localStorage.clear(); window.location.reload();">Clear localStorage</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* filter out redundant fields upon sending to API
|
||||||
|
* @param {Array<APIMessage>} messages
|
||||||
|
* @returns {Array<APIMessage>}
|
||||||
|
*/
|
||||||
|
function normalizeMsgsForAPI(messages) {
|
||||||
|
return messages.map((msg) => {
|
||||||
|
return {
|
||||||
|
role: msg.role,
|
||||||
|
content: msg.content,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* recommended for DeepsSeek-R1, filter out content between <think> and </think> tags
|
||||||
|
* @param {Array<APIMessage>} messages
|
||||||
|
* @returns {Array<APIMessage>}
|
||||||
|
*/
|
||||||
|
function filterThoughtFromMsgs(messages) {
|
||||||
|
return messages.map((msg) => {
|
||||||
|
return {
|
||||||
|
role: msg.role,
|
||||||
|
content: msg.content.split('</think>').at(-1).trim(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue