mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
UI: consolidate stale request handling in skills and usage
This commit is contained in:
@@ -87,6 +87,31 @@ function getErrorMessage(err: unknown) {
|
||||
return String(err);
|
||||
}
|
||||
|
||||
async function runStaleAwareRequest<T>(
|
||||
isCurrent: () => boolean,
|
||||
request: () => Promise<T>,
|
||||
onSuccess: (value: T) => void,
|
||||
onError: (err: unknown) => void,
|
||||
onFinally: () => void,
|
||||
) {
|
||||
try {
|
||||
const result = await request();
|
||||
if (!isCurrent()) {
|
||||
return;
|
||||
}
|
||||
onSuccess(result);
|
||||
} catch (err) {
|
||||
if (!isCurrent()) {
|
||||
return;
|
||||
}
|
||||
onError(err);
|
||||
} finally {
|
||||
if (isCurrent()) {
|
||||
onFinally();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function setClawHubSearchQuery(state: SkillsState, query: string) {
|
||||
state.clawhubSearchQuery = query;
|
||||
state.clawhubInstallMessage = null;
|
||||
@@ -202,56 +227,53 @@ export async function searchClawHub(state: SkillsState, query: string) {
|
||||
state.clawhubSearchLoading = false;
|
||||
return;
|
||||
}
|
||||
const client = state.client;
|
||||
// Clear stale entries as soon as a new search begins so the UI cannot act on
|
||||
// results that no longer match the current query while the next request is in flight.
|
||||
state.clawhubSearchResults = null;
|
||||
state.clawhubSearchLoading = true;
|
||||
state.clawhubSearchError = null;
|
||||
try {
|
||||
const res = await state.client.request<{ results: ClawHubSearchResult[] }>("skills.search", {
|
||||
query,
|
||||
limit: 20,
|
||||
});
|
||||
if (query !== state.clawhubSearchQuery) {
|
||||
return;
|
||||
}
|
||||
state.clawhubSearchResults = res?.results ?? [];
|
||||
} catch (err) {
|
||||
if (query !== state.clawhubSearchQuery) {
|
||||
return;
|
||||
}
|
||||
state.clawhubSearchError = getErrorMessage(err);
|
||||
} finally {
|
||||
if (query === state.clawhubSearchQuery) {
|
||||
await runStaleAwareRequest(
|
||||
() => query === state.clawhubSearchQuery,
|
||||
() =>
|
||||
client.request<{ results: ClawHubSearchResult[] }>("skills.search", {
|
||||
query,
|
||||
limit: 20,
|
||||
}),
|
||||
(res) => {
|
||||
state.clawhubSearchResults = res?.results ?? [];
|
||||
},
|
||||
(err) => {
|
||||
state.clawhubSearchError = getErrorMessage(err);
|
||||
},
|
||||
() => {
|
||||
state.clawhubSearchLoading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function loadClawHubDetail(state: SkillsState, slug: string) {
|
||||
if (!state.client || !state.connected) {
|
||||
return;
|
||||
}
|
||||
const client = state.client;
|
||||
state.clawhubDetailSlug = slug;
|
||||
state.clawhubDetailLoading = true;
|
||||
state.clawhubDetailError = null;
|
||||
state.clawhubDetail = null;
|
||||
try {
|
||||
const res = await state.client.request<ClawHubSkillDetail>("skills.detail", { slug });
|
||||
if (slug !== state.clawhubDetailSlug) {
|
||||
return;
|
||||
}
|
||||
state.clawhubDetail = res ?? null;
|
||||
} catch (err) {
|
||||
if (slug !== state.clawhubDetailSlug) {
|
||||
return;
|
||||
}
|
||||
state.clawhubDetailError = getErrorMessage(err);
|
||||
} finally {
|
||||
if (slug === state.clawhubDetailSlug) {
|
||||
await runStaleAwareRequest(
|
||||
() => slug === state.clawhubDetailSlug,
|
||||
() => client.request<ClawHubSkillDetail>("skills.detail", { slug }),
|
||||
(res) => {
|
||||
state.clawhubDetail = res ?? null;
|
||||
},
|
||||
(err) => {
|
||||
state.clawhubDetailError = getErrorMessage(err);
|
||||
},
|
||||
() => {
|
||||
state.clawhubDetailLoading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function closeClawHubDetail(state: SkillsState) {
|
||||
|
||||
@@ -29,10 +29,8 @@ export type UsageState = {
|
||||
settings?: { gatewayUrl?: string };
|
||||
};
|
||||
|
||||
type DateInterpretationMode = "utc" | "gateway" | "specific";
|
||||
|
||||
type UsageDateInterpretationParams = {
|
||||
mode: DateInterpretationMode;
|
||||
mode: "utc" | "specific";
|
||||
utcOffset?: string;
|
||||
};
|
||||
|
||||
@@ -105,17 +103,15 @@ function normalizeGatewayCompatibilityKey(gatewayUrl?: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveGatewayCompatibilityKey(state: UsageState): string {
|
||||
return normalizeGatewayCompatibilityKey(state.settings?.gatewayUrl);
|
||||
}
|
||||
|
||||
function shouldSendLegacyDateInterpretation(state: UsageState): boolean {
|
||||
return !getLegacyUsageDateParamsCache().has(resolveGatewayCompatibilityKey(state));
|
||||
return !getLegacyUsageDateParamsCache().has(
|
||||
normalizeGatewayCompatibilityKey(state.settings?.gatewayUrl),
|
||||
);
|
||||
}
|
||||
|
||||
function rememberLegacyDateInterpretation(state: UsageState) {
|
||||
const cache = getLegacyUsageDateParamsCache();
|
||||
cache.add(resolveGatewayCompatibilityKey(state));
|
||||
cache.add(normalizeGatewayCompatibilityKey(state.settings?.gatewayUrl));
|
||||
persistLegacyUsageDateParamsCache(cache);
|
||||
}
|
||||
|
||||
@@ -143,11 +139,7 @@ const formatUtcOffset = (timezoneOffsetMinutes: number): string => {
|
||||
|
||||
const buildDateInterpretationParams = (
|
||||
timeZone: "local" | "utc",
|
||||
includeDateInterpretation: boolean,
|
||||
): UsageDateInterpretationParams | undefined => {
|
||||
if (!includeDateInterpretation) {
|
||||
return undefined;
|
||||
}
|
||||
): UsageDateInterpretationParams => {
|
||||
if (timeZone === "utc") {
|
||||
return { mode: "utc" };
|
||||
}
|
||||
@@ -174,6 +166,15 @@ function toErrorMessage(err: unknown): string {
|
||||
return "request failed";
|
||||
}
|
||||
|
||||
function applyUsageResults(state: UsageState, sessionsRes: unknown, costRes: unknown) {
|
||||
if (sessionsRes) {
|
||||
state.usageResult = sessionsRes as SessionsUsageResult;
|
||||
}
|
||||
if (costRes) {
|
||||
state.usageCostSummary = costRes as CostUsageSummary;
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadUsage(
|
||||
state: UsageState,
|
||||
overrides?: {
|
||||
@@ -192,10 +193,9 @@ export async function loadUsage(
|
||||
const startDate = overrides?.startDate ?? state.usageStartDate;
|
||||
const endDate = overrides?.endDate ?? state.usageEndDate;
|
||||
const runUsageRequests = (includeDateInterpretation: boolean) => {
|
||||
const dateInterpretation = buildDateInterpretationParams(
|
||||
state.usageTimeZone,
|
||||
includeDateInterpretation,
|
||||
);
|
||||
const dateInterpretation = includeDateInterpretation
|
||||
? buildDateInterpretationParams(state.usageTimeZone)
|
||||
: undefined;
|
||||
return Promise.all([
|
||||
client.request("sessions.usage", {
|
||||
startDate,
|
||||
@@ -212,26 +212,17 @@ export async function loadUsage(
|
||||
]);
|
||||
};
|
||||
|
||||
const applyUsageResults = (sessionsRes: unknown, costRes: unknown) => {
|
||||
if (sessionsRes) {
|
||||
state.usageResult = sessionsRes as SessionsUsageResult;
|
||||
}
|
||||
if (costRes) {
|
||||
state.usageCostSummary = costRes as CostUsageSummary;
|
||||
}
|
||||
};
|
||||
|
||||
const includeDateInterpretation = shouldSendLegacyDateInterpretation(state);
|
||||
try {
|
||||
const [sessionsRes, costRes] = await runUsageRequests(includeDateInterpretation);
|
||||
applyUsageResults(sessionsRes, costRes);
|
||||
applyUsageResults(state, sessionsRes, costRes);
|
||||
} catch (err) {
|
||||
if (includeDateInterpretation && isLegacyDateInterpretationUnsupportedError(err)) {
|
||||
// Older gateways reject `mode`/`utcOffset` in `sessions.usage`.
|
||||
// Remember this per gateway and retry once without those fields.
|
||||
rememberLegacyDateInterpretation(state);
|
||||
const [sessionsRes, costRes] = await runUsageRequests(false);
|
||||
applyUsageResults(sessionsRes, costRes);
|
||||
applyUsageResults(state, sessionsRes, costRes);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user