Skip to content
4 changes: 2 additions & 2 deletions src/lib/common/ProfileDropdown.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from '@sveltestrap/sveltestrap';
import { resetLocalStorage } from '$lib/helpers/store';
import { resetStorage } from '$lib/helpers/store';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { userStore } from '$lib/helpers/store';
Expand All @@ -15,7 +15,7 @@

function logout() {
if (browser){
resetLocalStorage(true);
resetStorage(true);
}

const chatFrame = document.getElementById(CHAT_FRAME_ID);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/common/StateModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<i
class="bx bx-no-entry clickable"
class="bx bxs-no-entry clickable"
class:hide={states.length === 1}
on:click={() => remove(idx)}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/common/StateSearch.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<i
class="bx bx-no-entry text-danger clickable"
class="bx bxs-no-entry text-danger clickable"
class:hide={states.length === 1}
on:click={() => removeState(idx)}
/>
Expand Down
8 changes: 5 additions & 3 deletions src/lib/helpers/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ export const FILE_EDITORS = [
EditorType.File
];

export const LEARNER_ID = "01acc3e5-0af7-49e6-ad7a-a760bd12dc40";
export const EVALUATOR_ID = "2cd4b805-7078-4405-87e9-2ec9aadf8a11";
export const TRAINING_MODE = "training";
export const LEARNER_AGENT_ID = "01acc3e5-0af7-49e6-ad7a-a760bd12dc40";
export const EVALUATOR_AGENT_ID = "2cd4b805-7078-4405-87e9-2ec9aadf8a11";
export const AI_PROGRAMMER_AGENT_ID = "c2a2faf6-b8b5-47fe-807b-f4714cf25dd4";
export const RULE_TRIGGER_CODE_GENERATE_TEMPLATE = "rule-trigger-code-generate_instruction";

export const TRAINING_MODE = "training";
export const DEFAULT_KNOWLEDGE_COLLECTION = "BotSharp";
export const IMAGE_DATA_PREFIX = 'data:image';

Expand Down
138 changes: 118 additions & 20 deletions src/lib/helpers/http.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,95 @@
import axios from 'axios';
import { getUserStore, globalErrorStore, loaderStore } from '$lib/helpers/store.js';
import { renewToken } from '$lib/services/auth-service';
import { delay } from './utils/common';


const retryQueue = {
/** @type {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void}[]} */
queue: [],

/** @type {boolean} */
isRefreshingToken: false,

/** @type {number} */
timeout: 20,

/**
* refresh access token
* @param {string} token
* @returns {Promise<string>}
*/
refreshAccessToken(token) {
return new Promise((resolve, reject) => {
renewToken(token, (newToken) => resolve(newToken), () => reject(new Error('Failed to refresh token')));
});
},

/** @param {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void}} item */
enqueue(item) {
this.queue.push(item);

if (!this.isRefreshingToken) {
const user = getUserStore();
if (!isTokenExired(user.expires)) {
this.dequeue(user.token);
} else {
this.isRefreshingToken = true;
this.refreshAccessToken(user?.token || '')
.then((newToken) => {
this.isRefreshingToken = false;
const promise = this.dequeue(newToken);
return promise;
})
.catch((err) => {
this.isRefreshingToken = false;
// Reject all queued requests
while (this.queue.length > 0) {
const item = this.queue.shift();
if (item) {
item.reject(err);
}
}
redirectToLogin();
});
}
}
},

/**
* @param {string} newToken
* @returns {Promise<void>}
*/
dequeue(newToken) {
let chain = Promise.resolve();
while (this.queue.length > 0) {
const item = this.queue.shift();
if (!item?.config) {
continue;
}

const { config } = item;
// @ts-ignore
config.headers = config.headers || {};
// @ts-ignore
config.headers.Authorization = `Bearer ${newToken}`;

chain = chain.then(() => delay(this.timeout))
.then(() => {
return new Promise((resolve) => {
axios(config).then((response) => {
resolve();
item.resolve(response);
}).catch((err) => {
resolve();
item.reject(err);
});
});
});
}
return chain;
}
};

// Add a request interceptor to attach authentication tokens or headers
axios.interceptors.request.use(
Expand All @@ -9,9 +99,10 @@ axios.interceptors.request.use(
if (!skipLoader(config)) {
loaderStore.set(true);
}
// For example, attach an authentication token to the request headers
if (user.token)
// Attach an authentication token to the request headers
if (user.token) {
config.headers.Authorization = `Bearer ${user.token}`;
}
return config;
},
(error) => {
Expand All @@ -23,25 +114,24 @@ axios.interceptors.request.use(
// Add a response interceptor to handle 401 errors globally
axios.interceptors.response.use(
(response) => {
// If the request was successful, return the response
loaderStore.set(false);
const user = getUserStore();
const isExpired = Date.now() / 1000 > user.expires;
if (isExpired || response?.status === 401) {
redirectToLogin();
return Promise.reject('user token expired!');
}
return response;
},
(error) => {
loaderStore.set(false);
const originalRequest = error?.config || {};
const user = getUserStore();

const isExpired = Date.now() / 1000 > user.expires;
if (isExpired || error?.response?.status === 401) {
redirectToLogin();
return Promise.reject(error);
} else if (!skipGlobalError(error.config)) {

// If token expired or 401 returned, attempt a single token refresh and retry requests in queue.
if ((error?.response?.status === 401 || isTokenExired(user.expires))
&& originalRequest
&& !originalRequest._retried
&& !originalRequest.url.includes('renew-token')) {
originalRequest._retried = true;
return new Promise((resolve, reject) => {
retryQueue.enqueue({ config: originalRequest, resolve, reject });
});
} else if (!skipGlobalError(originalRequest)) {
globalErrorStore.set(true);
setTimeout(() => {
globalErrorStore.set(false);
Expand All @@ -53,6 +143,12 @@ axios.interceptors.response.use(
}
);

/**
* @param {number} expires
*/
function isTokenExired(expires) {
return Date.now() / 1000 > expires;
}

function redirectToLogin() {
const curUrl = window.location.pathname + window.location.search;
Expand All @@ -75,7 +171,8 @@ function skipLoader(config) {
new RegExp('http(s*)://(.*?)/knowledge/document/(.*?)/page', 'g'),
new RegExp('http(s*)://(.*?)/users', 'g'),
new RegExp('http(s*)://(.*?)/instruct/chat-completion', 'g'),
new RegExp('http(s*)://(.*?)/agent/(.*?)/code-scripts', 'g')
new RegExp('http(s*)://(.*?)/agent/(.*?)/code-scripts', 'g'),
new RegExp('http(s*)://(.*?)/agent/(.*?)/code-script/generate', 'g')
];

/** @type {RegExp[]} */
Expand Down Expand Up @@ -111,7 +208,8 @@ function skipLoader(config) {
new RegExp('http(s*)://(.*?)/logger/instruction/log/keys', 'g'),
new RegExp('http(s*)://(.*?)/logger/conversation/(.*?)/content-log', 'g'),
new RegExp('http(s*)://(.*?)/logger/conversation/(.*?)/state-log', 'g'),
new RegExp('http(s*)://(.*?)/mcp/server-configs', 'g')
new RegExp('http(s*)://(.*?)/mcp/server-configs', 'g'),
new RegExp('http(s*)://(.*?)/agent/(.*?)/code-scripts', 'g')
];

if (config.method === 'post' && postRegexes.some(regex => regex.test(config.url || ''))) {
Expand Down Expand Up @@ -153,7 +251,7 @@ function skipGlobalError(config) {
new RegExp('http(s*)://(.*?)/conversation/(.*?)/update-message', 'g'),
new RegExp('http(s*)://(.*?)/conversation/(.*?)/update-tags', 'g')
];

/** @type {RegExp[]} */
const deleteRegexes = [
new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/delete-collection', 'g'),
Expand Down Expand Up @@ -199,7 +297,7 @@ export function replaceUrl(url, args) {

/**
* Replace new line as <br>
* @param {string} text
* @param {string} text
* @returns string
*/
export function replaceNewLine(text) {
Expand All @@ -208,7 +306,7 @@ export function replaceNewLine(text) {

/**
* Replace unnecessary markdown
* @param {string} text
* @param {string} text
* @returns {string}
*/
export function replaceMarkdown(text) {
Expand Down
28 changes: 23 additions & 5 deletions src/lib/helpers/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const userStore = writable({ id: "", full_name: "", expires: 0, token: nu
export function getUserStore() {
if (browser) {
// Access localStorage only if in the browser context
let json = localStorage.getItem(userKey);
let json = sessionStorage.getItem(userKey);
if (json)
return JSON.parse(json);
else
Expand All @@ -54,7 +54,7 @@ export function getUserStore() {

userStore.subscribe(value => {
if (browser && value.token) {
localStorage.setItem(userKey, JSON.stringify(value));
sessionStorage.setItem(userKey, JSON.stringify(value));
}
});

Expand Down Expand Up @@ -219,13 +219,31 @@ const createKnowledgeBaseDocumentStore = () => {
export const knowledgeBaseDocumentStore = createKnowledgeBaseDocumentStore();


export function resetLocalStorage(resetUser = false) {
export function resetStorage(resetUser = false) {
conversationUserStateStore.resetAll();
conversationUserMessageStore.reset();
conversationUserAttachmentStore.reset();
localStorage.removeItem('conversation');
clearLocalStorage(['message']);

if (resetUser) {
localStorage.removeItem('user');
sessionStorage.removeItem(userKey);
}
}

/** @param {string[]?} keyPrefixes */
function clearLocalStorage(keyPrefixes = null) {
if (!keyPrefixes) {
localStorage.clear();
return;
}

for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (!key) continue;

const found = keyPrefixes.find(x => key.startsWith(x));
if (found) {
localStorage.removeItem(key);
}
}
}
28 changes: 28 additions & 0 deletions src/lib/helpers/types/agentTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,31 @@
* @property {AgentCodeScriptUpdateOptions?} [options]
*/

/**
* @typedef {Object} AgentCodeScriptGenerateModel
* @property {string?} [text]
* @property {CodeProcessOptions?} [options]
*/

/**
* @typedef {Object} CodeProcessOptions
* @property {boolean?} [save_to_db] - Whether to save the generated code to database.
* @property {string?} [script_name] - The code script name.
* @property {string?} [script_type] - The code script type.
* @property {string?} [agent_id] - The agent id.
* @property {string?} [template_name] - The template name.
* @property {any?} [data] - The template data.
* @property {string?} [provider] - The llm provider.
* @property {string?} [model] - The llm model.
*/

/**
* @typedef {Object} CodeGenerationResult
* @property {boolean?} [success]
* @property {string?} [content]
* @property {string?} [language]
* @property {string?} [error_message]
*/

/**
* @typedef {Object} ChannelInstruction
Expand Down Expand Up @@ -204,6 +229,9 @@
* @property {string} criteria
* @property {string?} [displayName]
* @property {boolean} disabled
* @property {any?} [output_args]
* @property {string?} [json_args]
* @property {string?} [statement]
*/


Expand Down
10 changes: 10 additions & 0 deletions src/lib/helpers/types/instructTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* @property {string?} [provider] - The LLM provider.
* @property {string?} [model] - The LLM model.
* @property {import('$conversationTypes').ConversationStateModel[]} [states]
* @property {CodeInstructOptions?} [codeOptions]
*/

/**
Expand All @@ -27,6 +28,7 @@
* @property {string[]?} [providers]
* @property {string[]?} [models]
* @property {string[]?} [templateNames]
* @property {string?} [similarTemplateName]
* @property {string?} [startTime]
* @property {string?} [endTime]
* @property {{key: string, value: string}[]?} [states]
Expand Down Expand Up @@ -59,4 +61,12 @@
* @property {string?} [endTime]
*/

/**
* @typedef {Object} CodeInstructOptions
* @property {string?} [processor] - The code processor.
* @property {string?} [script_name] - The code script name.
* @property {string?} [script_type] - The code script type: src or test.
* @property {{key: string, value: string}[]?} [arguments] - The arguments.
*/

export default {};
Loading