Skip to content

Commit 52f03e1

Browse files
authored
Merge pull request #397 from iceljc/features/refine-agent-rule
Features/refine agent rule
2 parents fbe3334 + a3f2e5b commit 52f03e1

38 files changed

+888
-259
lines changed

src/lib/common/ProfileDropdown.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script>
22
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from '@sveltestrap/sveltestrap';
3-
import { resetLocalStorage } from '$lib/helpers/store';
3+
import { resetStorage } from '$lib/helpers/store';
44
import { goto } from '$app/navigation';
55
import { browser } from '$app/environment';
66
import { userStore } from '$lib/helpers/store';
@@ -15,7 +15,7 @@
1515
1616
function logout() {
1717
if (browser){
18-
resetLocalStorage(true);
18+
resetStorage(true);
1919
}
2020
2121
const chatFrame = document.getElementById(CHAT_FRAME_ID);

src/lib/common/StateModal.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@
201201
<!-- svelte-ignore a11y-click-events-have-key-events -->
202202
<!-- svelte-ignore a11y-no-static-element-interactions -->
203203
<i
204-
class="bx bx-no-entry clickable"
204+
class="bx bxs-no-entry clickable"
205205
class:hide={states.length === 1}
206206
on:click={() => remove(idx)}
207207
/>

src/lib/common/StateSearch.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
<!-- svelte-ignore a11y-click-events-have-key-events -->
5959
<!-- svelte-ignore a11y-no-static-element-interactions -->
6060
<i
61-
class="bx bx-no-entry text-danger clickable"
61+
class="bx bxs-no-entry text-danger clickable"
6262
class:hide={states.length === 1}
6363
on:click={() => removeState(idx)}
6464
/>

src/lib/helpers/constants.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ export const FILE_EDITORS = [
3737
EditorType.File
3838
];
3939

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

45+
export const TRAINING_MODE = "training";
4446
export const DEFAULT_KNOWLEDGE_COLLECTION = "BotSharp";
4547
export const IMAGE_DATA_PREFIX = 'data:image';
4648

src/lib/helpers/http.js

Lines changed: 118 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,95 @@
11
import axios from 'axios';
22
import { getUserStore, globalErrorStore, loaderStore } from '$lib/helpers/store.js';
3+
import { renewToken } from '$lib/services/auth-service';
4+
import { delay } from './utils/common';
5+
6+
7+
const retryQueue = {
8+
/** @type {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void}[]} */
9+
queue: [],
10+
11+
/** @type {boolean} */
12+
isRefreshingToken: false,
13+
14+
/** @type {number} */
15+
timeout: 20,
16+
17+
/**
18+
* refresh access token
19+
* @param {string} token
20+
* @returns {Promise<string>}
21+
*/
22+
refreshAccessToken(token) {
23+
return new Promise((resolve, reject) => {
24+
renewToken(token, (newToken) => resolve(newToken), () => reject(new Error('Failed to refresh token')));
25+
});
26+
},
27+
28+
/** @param {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void}} item */
29+
enqueue(item) {
30+
this.queue.push(item);
31+
32+
if (!this.isRefreshingToken) {
33+
const user = getUserStore();
34+
if (!isTokenExired(user.expires)) {
35+
this.dequeue(user.token);
36+
} else {
37+
this.isRefreshingToken = true;
38+
this.refreshAccessToken(user?.token || '')
39+
.then((newToken) => {
40+
this.isRefreshingToken = false;
41+
const promise = this.dequeue(newToken);
42+
return promise;
43+
})
44+
.catch((err) => {
45+
this.isRefreshingToken = false;
46+
// Reject all queued requests
47+
while (this.queue.length > 0) {
48+
const item = this.queue.shift();
49+
if (item) {
50+
item.reject(err);
51+
}
52+
}
53+
redirectToLogin();
54+
});
55+
}
56+
}
57+
},
58+
59+
/**
60+
* @param {string} newToken
61+
* @returns {Promise<void>}
62+
*/
63+
dequeue(newToken) {
64+
let chain = Promise.resolve();
65+
while (this.queue.length > 0) {
66+
const item = this.queue.shift();
67+
if (!item?.config) {
68+
continue;
69+
}
70+
71+
const { config } = item;
72+
// @ts-ignore
73+
config.headers = config.headers || {};
74+
// @ts-ignore
75+
config.headers.Authorization = `Bearer ${newToken}`;
76+
77+
chain = chain.then(() => delay(this.timeout))
78+
.then(() => {
79+
return new Promise((resolve) => {
80+
axios(config).then((response) => {
81+
resolve();
82+
item.resolve(response);
83+
}).catch((err) => {
84+
resolve();
85+
item.reject(err);
86+
});
87+
});
88+
});
89+
}
90+
return chain;
91+
}
92+
};
393

494
// Add a request interceptor to attach authentication tokens or headers
595
axios.interceptors.request.use(
@@ -9,9 +99,10 @@ axios.interceptors.request.use(
999
if (!skipLoader(config)) {
10100
loaderStore.set(true);
11101
}
12-
// For example, attach an authentication token to the request headers
13-
if (user.token)
102+
// Attach an authentication token to the request headers
103+
if (user.token) {
14104
config.headers.Authorization = `Bearer ${user.token}`;
105+
}
15106
return config;
16107
},
17108
(error) => {
@@ -23,25 +114,24 @@ axios.interceptors.request.use(
23114
// Add a response interceptor to handle 401 errors globally
24115
axios.interceptors.response.use(
25116
(response) => {
26-
// If the request was successful, return the response
27117
loaderStore.set(false);
28-
const user = getUserStore();
29-
const isExpired = Date.now() / 1000 > user.expires;
30-
if (isExpired || response?.status === 401) {
31-
redirectToLogin();
32-
return Promise.reject('user token expired!');
33-
}
34118
return response;
35119
},
36120
(error) => {
37121
loaderStore.set(false);
122+
const originalRequest = error?.config || {};
38123
const user = getUserStore();
39-
40-
const isExpired = Date.now() / 1000 > user.expires;
41-
if (isExpired || error?.response?.status === 401) {
42-
redirectToLogin();
43-
return Promise.reject(error);
44-
} else if (!skipGlobalError(error.config)) {
124+
125+
// If token expired or 401 returned, attempt a single token refresh and retry requests in queue.
126+
if ((error?.response?.status === 401 || isTokenExired(user.expires))
127+
&& originalRequest
128+
&& !originalRequest._retried
129+
&& !originalRequest.url.includes('renew-token')) {
130+
originalRequest._retried = true;
131+
return new Promise((resolve, reject) => {
132+
retryQueue.enqueue({ config: originalRequest, resolve, reject });
133+
});
134+
} else if (!skipGlobalError(originalRequest)) {
45135
globalErrorStore.set(true);
46136
setTimeout(() => {
47137
globalErrorStore.set(false);
@@ -53,6 +143,12 @@ axios.interceptors.response.use(
53143
}
54144
);
55145

146+
/**
147+
* @param {number} expires
148+
*/
149+
function isTokenExired(expires) {
150+
return Date.now() / 1000 > expires;
151+
}
56152

57153
function redirectToLogin() {
58154
const curUrl = window.location.pathname + window.location.search;
@@ -75,7 +171,8 @@ function skipLoader(config) {
75171
new RegExp('http(s*)://(.*?)/knowledge/document/(.*?)/page', 'g'),
76172
new RegExp('http(s*)://(.*?)/users', 'g'),
77173
new RegExp('http(s*)://(.*?)/instruct/chat-completion', 'g'),
78-
new RegExp('http(s*)://(.*?)/agent/(.*?)/code-scripts', 'g')
174+
new RegExp('http(s*)://(.*?)/agent/(.*?)/code-scripts', 'g'),
175+
new RegExp('http(s*)://(.*?)/agent/(.*?)/code-script/generate', 'g')
79176
];
80177

81178
/** @type {RegExp[]} */
@@ -111,7 +208,8 @@ function skipLoader(config) {
111208
new RegExp('http(s*)://(.*?)/logger/instruction/log/keys', 'g'),
112209
new RegExp('http(s*)://(.*?)/logger/conversation/(.*?)/content-log', 'g'),
113210
new RegExp('http(s*)://(.*?)/logger/conversation/(.*?)/state-log', 'g'),
114-
new RegExp('http(s*)://(.*?)/mcp/server-configs', 'g')
211+
new RegExp('http(s*)://(.*?)/mcp/server-configs', 'g'),
212+
new RegExp('http(s*)://(.*?)/agent/(.*?)/code-scripts', 'g')
115213
];
116214

117215
if (config.method === 'post' && postRegexes.some(regex => regex.test(config.url || ''))) {
@@ -153,7 +251,7 @@ function skipGlobalError(config) {
153251
new RegExp('http(s*)://(.*?)/conversation/(.*?)/update-message', 'g'),
154252
new RegExp('http(s*)://(.*?)/conversation/(.*?)/update-tags', 'g')
155253
];
156-
254+
157255
/** @type {RegExp[]} */
158256
const deleteRegexes = [
159257
new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/delete-collection', 'g'),
@@ -199,7 +297,7 @@ export function replaceUrl(url, args) {
199297

200298
/**
201299
* Replace new line as <br>
202-
* @param {string} text
300+
* @param {string} text
203301
* @returns string
204302
*/
205303
export function replaceNewLine(text) {
@@ -208,7 +306,7 @@ export function replaceNewLine(text) {
208306

209307
/**
210308
* Replace unnecessary markdown
211-
* @param {string} text
309+
* @param {string} text
212310
* @returns {string}
213311
*/
214312
export function replaceMarkdown(text) {

src/lib/helpers/store.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const userStore = writable({ id: "", full_name: "", expires: 0, token: nu
4141
export function getUserStore() {
4242
if (browser) {
4343
// Access localStorage only if in the browser context
44-
let json = localStorage.getItem(userKey);
44+
let json = sessionStorage.getItem(userKey);
4545
if (json)
4646
return JSON.parse(json);
4747
else
@@ -54,7 +54,7 @@ export function getUserStore() {
5454

5555
userStore.subscribe(value => {
5656
if (browser && value.token) {
57-
localStorage.setItem(userKey, JSON.stringify(value));
57+
sessionStorage.setItem(userKey, JSON.stringify(value));
5858
}
5959
});
6060

@@ -219,13 +219,31 @@ const createKnowledgeBaseDocumentStore = () => {
219219
export const knowledgeBaseDocumentStore = createKnowledgeBaseDocumentStore();
220220

221221

222-
export function resetLocalStorage(resetUser = false) {
222+
export function resetStorage(resetUser = false) {
223223
conversationUserStateStore.resetAll();
224224
conversationUserMessageStore.reset();
225225
conversationUserAttachmentStore.reset();
226-
localStorage.removeItem('conversation');
226+
clearLocalStorage(['message']);
227227

228228
if (resetUser) {
229-
localStorage.removeItem('user');
229+
sessionStorage.removeItem(userKey);
230+
}
231+
}
232+
233+
/** @param {string[]?} keyPrefixes */
234+
function clearLocalStorage(keyPrefixes = null) {
235+
if (!keyPrefixes) {
236+
localStorage.clear();
237+
return;
238+
}
239+
240+
for (let i = 0; i < localStorage.length; i++) {
241+
const key = localStorage.key(i);
242+
if (!key) continue;
243+
244+
const found = keyPrefixes.find(x => key.startsWith(x));
245+
if (found) {
246+
localStorage.removeItem(key);
247+
}
230248
}
231249
}

src/lib/helpers/types/agentTypes.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,31 @@
135135
* @property {AgentCodeScriptUpdateOptions?} [options]
136136
*/
137137

138+
/**
139+
* @typedef {Object} AgentCodeScriptGenerateModel
140+
* @property {string?} [text]
141+
* @property {CodeProcessOptions?} [options]
142+
*/
143+
144+
/**
145+
* @typedef {Object} CodeProcessOptions
146+
* @property {boolean?} [save_to_db] - Whether to save the generated code to database.
147+
* @property {string?} [script_name] - The code script name.
148+
* @property {string?} [script_type] - The code script type.
149+
* @property {string?} [agent_id] - The agent id.
150+
* @property {string?} [template_name] - The template name.
151+
* @property {any?} [data] - The template data.
152+
* @property {string?} [provider] - The llm provider.
153+
* @property {string?} [model] - The llm model.
154+
*/
155+
156+
/**
157+
* @typedef {Object} CodeGenerationResult
158+
* @property {boolean?} [success]
159+
* @property {string?} [content]
160+
* @property {string?} [language]
161+
* @property {string?} [error_message]
162+
*/
138163

139164
/**
140165
* @typedef {Object} ChannelInstruction
@@ -204,6 +229,9 @@
204229
* @property {string} criteria
205230
* @property {string?} [displayName]
206231
* @property {boolean} disabled
232+
* @property {any?} [output_args]
233+
* @property {string?} [json_args]
234+
* @property {string?} [statement]
207235
*/
208236

209237

src/lib/helpers/types/instructTypes.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* @property {string?} [provider] - The LLM provider.
77
* @property {string?} [model] - The LLM model.
88
* @property {import('$conversationTypes').ConversationStateModel[]} [states]
9+
* @property {CodeInstructOptions?} [codeOptions]
910
*/
1011

1112
/**
@@ -27,6 +28,7 @@
2728
* @property {string[]?} [providers]
2829
* @property {string[]?} [models]
2930
* @property {string[]?} [templateNames]
31+
* @property {string?} [similarTemplateName]
3032
* @property {string?} [startTime]
3133
* @property {string?} [endTime]
3234
* @property {{key: string, value: string}[]?} [states]
@@ -59,4 +61,12 @@
5961
* @property {string?} [endTime]
6062
*/
6163

64+
/**
65+
* @typedef {Object} CodeInstructOptions
66+
* @property {string?} [processor] - The code processor.
67+
* @property {string?} [script_name] - The code script name.
68+
* @property {string?} [script_type] - The code script type: src or test.
69+
* @property {{key: string, value: string}[]?} [arguments] - The arguments.
70+
*/
71+
6272
export default {};

0 commit comments

Comments
 (0)