Skip to content

Commit e4a3b7d

Browse files
author
Jicheng Lu
committed
refine request queue
1 parent 506359e commit e4a3b7d

File tree

6 files changed

+116
-99
lines changed

6 files changed

+116
-99
lines changed

src/lib/helpers/http.js

Lines changed: 89 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,92 @@ import { renewToken } from '$lib/services/auth-service';
44
import { delay } from './utils/common';
55

66

7-
/** @type {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void}[]} */
8-
const retryQueue = [];
9-
10-
// Refresh handling state
11-
let isRefreshingToken = false;
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+
},
1227

13-
/**
14-
* Wrap renewToken into a Promise that resolves with the new access token string
15-
* @param {string} token
16-
* @returns {Promise<string>}
17-
*/
18-
function refreshAccessToken(token) {
19-
return new Promise((resolve, reject) => {
20-
renewToken(token, (newToken) => resolve(newToken), () => reject(new Error('Failed to refresh token')));
21-
});
22-
}
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+
},
2358

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+
};
2493

2594
// Add a request interceptor to attach authentication tokens or headers
2695
axios.interceptors.request.use(
@@ -54,10 +123,13 @@ axios.interceptors.response.use(
54123
const user = getUserStore();
55124

56125
// If token expired or 401 returned, attempt a single token refresh and retry requests in queue.
57-
if ((error?.response?.status === 401 || isTokenExired(user.expires)) && originalRequest && !originalRequest._retried) {
126+
if ((error?.response?.status === 401 || isTokenExired(user.expires))
127+
&& originalRequest
128+
&& !originalRequest._retried
129+
&& !originalRequest.url.includes('renew-token')) {
58130
originalRequest._retried = true;
59131
return new Promise((resolve, reject) => {
60-
enqueue({ config: originalRequest, resolve, reject });
132+
retryQueue.enqueue({ config: originalRequest, resolve, reject });
61133
});
62134
} else if (!skipGlobalError(originalRequest)) {
63135
globalErrorStore.set(true);
@@ -71,62 +143,6 @@ axios.interceptors.response.use(
71143
}
72144
);
73145

74-
/**
75-
* @param {{config: import('axios').InternalAxiosRequestConfig, resolve: (value: any) => void, reject: (reason?: any) => void}} retryItem
76-
*/
77-
function enqueue(retryItem) {
78-
// Push the current request into the queue
79-
retryQueue.push({ ...retryItem });
80-
81-
// Start refresh token
82-
if (!isRefreshingToken) {
83-
const user = getUserStore();
84-
if (!isTokenExired(user.expires)) {
85-
dequeue(user.token);
86-
} else {
87-
isRefreshingToken = true;
88-
refreshAccessToken(user?.token || '')
89-
.then((newToken) => {
90-
isRefreshingToken = false;
91-
const promise = dequeue(newToken);
92-
return promise;
93-
})
94-
.catch((err) => {
95-
isRefreshingToken = false;
96-
// Reject all queued requests
97-
while (retryQueue.length > 0) {
98-
const item = retryQueue.shift();
99-
if (item) {
100-
item.reject(err);
101-
}
102-
}
103-
redirectToLogin();
104-
});
105-
}
106-
}
107-
}
108-
109-
/**
110-
* Retry queued requests sequentially
111-
* @param {string} newToken
112-
* @returns {Promise<void>}
113-
*/
114-
function dequeue(newToken) {
115-
let chain = Promise.resolve();
116-
while (retryQueue.length > 0) {
117-
const item = retryQueue.shift();
118-
if (!item) continue;
119-
const { config, resolve, reject } = item;
120-
// @ts-ignore
121-
config.headers = config.headers || {};
122-
// @ts-ignore
123-
config.headers.Authorization = `Bearer ${newToken}`;
124-
chain = chain.then(() => delay(20))
125-
.then(() => { axios(config).then(resolve).catch(reject); });
126-
}
127-
return chain;
128-
}
129-
130146
/**
131147
* @param {number} expires
132148
*/

src/lib/helpers/types/instructTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* @property {string[]?} [providers]
2929
* @property {string[]?} [models]
3030
* @property {string[]?} [templateNames]
31+
* @property {string?} [similarTemplateName]
3132
* @property {string?} [startTime]
3233
* @property {string?} [endTime]
3334
* @property {{key: string, value: string}[]?} [states]

src/lib/services/auth-service.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,10 @@ export async function getToken(email, password, onSucceed, onError) {
4646
* @param {(() => void) | null} [onError]
4747
*/
4848
export async function renewToken(token, onSucceed = null, onError = null) {
49-
const headers = {
50-
Authorization: `Bearer ${token}`,
51-
};
52-
5349
await fetch(endpoints.renewTokenUrl, {
5450
method: 'POST',
55-
headers: headers,
51+
headers: { "Content-Type": "application/json" },
52+
body: JSON.stringify({ refresh_token: token, access_token: token }),
5653
}).then(response => {
5754
if (response.ok) {
5855
return response.json();

src/lib/services/instruct-service.js

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { endpoints } from '$lib/services/api-endpoints.js';
22
import { replaceUrl } from '$lib/helpers/http';
33
import axios from 'axios';
4-
import qs from 'qs';
54

65
/**
76
* Execute agent instruction by template or user provided prompt.
@@ -27,22 +26,6 @@ export async function sendChatCompletion(request) {
2726
}
2827

2928

30-
/**
31-
* Get instruction logs
32-
* @param {import('$instructTypes').InstructLogFilter} filter
33-
* @returns {Promise<import('$commonTypes').PagedItems<import('$instructTypes').InstructionLogModel>>}
34-
*/
35-
export async function getInstructionLogs(filter) {
36-
const url = endpoints.instructLogUrl;
37-
const response = await axios.get(url, {
38-
params: {
39-
...filter
40-
},
41-
paramsSerializer: (params) => qs.stringify(params, { encode: false, allowDots: true, arrayFormat: "indices" })
42-
});
43-
return response.data;
44-
}
45-
4629
/**
4730
* get instruction log search keys
4831
* @param {import('$instructTypes').StateSearchQuery} request

src/lib/services/logging-service.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { replaceUrl } from '$lib/helpers/http.js';
22
import { endpoints } from './api-endpoints.js';
33
import axios from 'axios';
4+
import qs from 'qs';
45

56
/**
67
* Show backend full log
@@ -51,4 +52,21 @@ export async function getStateLogs(conversationId, filter) {
5152
}
5253
});
5354
return response.data;
55+
}
56+
57+
58+
/**
59+
* Get instruction logs
60+
* @param {import('$instructTypes').InstructLogFilter} filter
61+
* @returns {Promise<import('$commonTypes').PagedItems<import('$instructTypes').InstructionLogModel>>}
62+
*/
63+
export async function getInstructionLogs(filter) {
64+
const url = endpoints.instructLogUrl;
65+
const response = await axios.get(url, {
66+
params: {
67+
...filter
68+
},
69+
paramsSerializer: (params) => qs.stringify(params, { encode: false, allowDots: true, arrayFormat: "indices" })
70+
});
71+
return response.data;
5472
}

src/routes/page/instruction/log/+page.svelte

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
import TablePagination from '$lib/common/TablePagination.svelte';
1010
import { getAgentOptions } from '$lib/services/agent-service';
1111
import { getLlmConfigs } from '$lib/services/llm-provider-service';
12-
import { getInstructionLogs, getInstructionLogSearchKeys } from '$lib/services/instruct-service';
12+
import { getInstructionLogSearchKeys } from '$lib/services/instruct-service';
13+
import { getInstructionLogs } from '$lib/services/logging-service';
1314
import LoadingToComplete from '$lib/common/LoadingToComplete.svelte';
14-
import LogItem from './log-item.svelte';
1515
import { convertTimeRange, removeDuplicates } from '$lib/helpers/utils/common';
1616
import StateSearch from '$lib/common/StateSearch.svelte';
1717
import Select from '$lib/common/Select.svelte';
@@ -22,6 +22,8 @@
2222
} from '$lib/helpers/utils/common';
2323
import { TimeRange } from '$lib/helpers/enums';
2424
import { TIME_RANGE_OPTIONS } from '$lib/helpers/constants';
25+
import LogItem from './log-item.svelte';
26+
2527
2628
const firstPage = 1;
2729
const pageSize = 15;
@@ -60,7 +62,7 @@
6062
agentIds: [],
6163
providers: [],
6264
models: [],
63-
template: '',
65+
similarTemplate: '',
6466
timeRange: TimeRange.Today,
6567
states: []
6668
};
@@ -244,7 +246,7 @@
244246
agentIds: agentIds,
245247
providers: providers,
246248
models: models,
247-
templateNames: template ? [template] : [],
249+
similarTemplateName: template,
248250
startTime: innerTimeRange.startTime,
249251
endTime: innerTimeRange.endTime,
250252
states: states

0 commit comments

Comments
 (0)