Enhance upload handling: add db-backed upload rate limiting and lease management, enforce stricter validation for deployment configurations, optimize test suite and retired functionality, and improve input handling and error recovery.
This commit is contained in:
+10
-1
@@ -9,7 +9,16 @@ export class ApiError extends Error {
|
||||
toJSON() { return {...this.details, error: this.message, status: this.status, code: this.code, requestId: this.requestId}; }
|
||||
}
|
||||
function requestScope({signal, timeoutMs = 30000} = {}) { const controller = new AbortController(); const abort = () => controller.abort(new ApiError("Request cancelled.", {code: "ABORTED"})); if (signal?.aborted) abort(); else signal?.addEventListener("abort", abort, {once: true}); const timer = timeoutMs > 0 ? setTimeout(() => controller.abort(new ApiError("Request timed out. Please try again.", {code: "TIMEOUT"})), timeoutMs) : null; return {signal: controller.signal, close() { clearTimeout(timer); signal?.removeEventListener("abort", abort); }}; }
|
||||
function abortable(operation, signal) { if (signal.aborted) return Promise.reject(signal.reason); return new Promise((resolve, reject) => { const abort = () => { signal.removeEventListener("abort", abort); reject(signal.reason); }; signal.addEventListener("abort", abort, {once: true}); Promise.resolve().then(operation).then(resolve, reject).finally(() => signal.removeEventListener("abort", abort)); }); }
|
||||
function abortable(operation, signal) {
|
||||
if (signal.aborted) return Promise.reject(signal.reason);
|
||||
return new Promise((resolve, reject) => {
|
||||
const abort = () => { signal.removeEventListener("abort", abort); reject(signal.reason); };
|
||||
signal.addEventListener("abort", abort, {once: true});
|
||||
let result;
|
||||
try { result = operation(); } catch (error) { reject(error); return; }
|
||||
Promise.resolve(result).then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
||||
});
|
||||
}
|
||||
function responseError(response, payload = {}, fallback = "", code = "") { const details = payload && typeof payload === "object" ? payload : {}; const error = details.error; return new ApiError(typeof error === "string" ? error : error?.message || fallback || `HTTP ${response.status} ${response.statusText}`.trim(), {...details, status: response.ok && Number.isInteger(details.status) && details.status >= 400 ? details.status : response.status, code: details.code || error?.code || code || `HTTP_${response.status}`, requestId: details.requestId || response.headers.get("X-Request-ID") || null}); }
|
||||
async function readResponseText(response, signal) {
|
||||
let textPromise = responseTextCache.get(response);
|
||||
|
||||
@@ -179,7 +179,10 @@ export function createComposerController({
|
||||
else state.chatDrafts.delete(chatId);
|
||||
const currentAttachments = attachmentComposer.attachments;
|
||||
if (currentAttachments.length === attachments.length && currentAttachments.every((item, index) => item.id === attachments[index].id)) attachmentComposer.clear();
|
||||
} else clearSentDraft();
|
||||
} else {
|
||||
state.chatDrafts.delete(chatId);
|
||||
clearSentDraft();
|
||||
}
|
||||
if (!followOutput && visible()) requestAnimationFrame(() => {
|
||||
if (visible()) messagesDiv.scrollTop = previousScrollTop;
|
||||
});
|
||||
@@ -194,8 +197,18 @@ export function createComposerController({
|
||||
const messageText = signal.aborted ? "Generation stopped." : error?.message || "Unable to send message.";
|
||||
if (visible() && completedReply === null) announceResponseStatus(messageText);
|
||||
if (completedReply !== null) {
|
||||
if (visible() && !msgInput.value.trim()) {
|
||||
msgInput.value = message;
|
||||
state.chatDrafts.set(chatId ?? "new", message);
|
||||
resizeComposerInput();
|
||||
}
|
||||
setNotice(`Reply sent, but the conversation could not be refreshed: ${messageText}`, "error");
|
||||
} else {
|
||||
if (signal.aborted && chatId == null && !msgInput.value.trim()) {
|
||||
msgInput.value = message;
|
||||
state.chatDrafts.set("new", message);
|
||||
resizeComposerInput();
|
||||
}
|
||||
if (visible()) {
|
||||
if (!msgInput.value.trim()) {
|
||||
msgInput.value = message;
|
||||
|
||||
@@ -56,14 +56,14 @@ export function createConversationController({
|
||||
}
|
||||
try {
|
||||
const historyUrl = `/chats/${id}/messages${matchMessageId ? `?around=${matchMessageId}` : ""}`;
|
||||
let res = await get(historyUrl);
|
||||
let res = await get(historyUrl, {retries: 0});
|
||||
if (openRequest !== state.chatOpenRequest) return;
|
||||
if (res.error) return failed(res);
|
||||
if (matchMessageId && !res.messages?.some(message => message.id === matchMessageId)) {
|
||||
const activated = await post(`/chats/${id}/branches/${matchMessageId}/activate`, {includeDescendants: true});
|
||||
if (openRequest !== state.chatOpenRequest) return;
|
||||
if (activated.error) return failed(activated);
|
||||
res = await get(historyUrl);
|
||||
res = await get(historyUrl, {retries: 0});
|
||||
if (openRequest !== state.chatOpenRequest) return;
|
||||
if (res.error) return failed(res);
|
||||
}
|
||||
@@ -123,7 +123,7 @@ export function createConversationController({
|
||||
state.historyLoading = true;
|
||||
updateHistoryControls();
|
||||
try {
|
||||
const result = await get(`/chats/${chatId}/messages?before=${page.before}&leaf=${page.leafId}`);
|
||||
const result = await get(`/chats/${chatId}/messages?before=${page.before}&leaf=${page.leafId}`, {retries: 0});
|
||||
if (chatId !== state.activeChatId || requestId !== state.chatOpenRequest) return;
|
||||
if (result.error) { setNotice(result.error, "error"); return result; }
|
||||
const oldHeight = messagesDiv.scrollHeight, oldTop = messagesDiv.scrollTop;
|
||||
|
||||
Reference in New Issue
Block a user