Address Homarr provider review feedback
This commit is contained in:
1 parent
4ebaea29fd
commit
ea9075bde5
9 files changed
+199
-88
No files matched your search
@@ -1,6 +1,10 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { getAssistantProviderQuotaLevel, isAssistantProviderUnavailable } from "./assistant-provider-quota";
|
||||
import {
|
||||
getAssistantProviderQuotaLevel,
|
||||
getAssistantProviderQuotaRefreshDelay,
|
||||
isAssistantProviderUnavailable,
|
||||
} from "./assistant-provider-quota";
|
||||
|
||||
describe("getAssistantProviderQuotaLevel", () => {
|
||||
test.each([
|
||||
@@ -19,4 +23,10 @@ describe("getAssistantProviderQuotaLevel", () => {
|
||||
expect(isAssistantProviderUnavailable({ provider: "homarr", signedIn: true, remaining: undefined })).toBe(false);
|
||||
expect(isAssistantProviderUnavailable({ provider: "openrouter", signedIn: false, remaining: 0 })).toBe(false);
|
||||
});
|
||||
|
||||
test("refreshes just after the UTC reset, or immediately when it has passed", () => {
|
||||
const reset = "2026-08-12T00:00:00.000Z";
|
||||
expect(getAssistantProviderQuotaRefreshDelay(reset, Date.parse("2026-08-11T23:59:00.000Z"))).toBe(61_000);
|
||||
expect(getAssistantProviderQuotaRefreshDelay(reset, Date.parse("2026-08-12T00:00:01.000Z"))).toBe(1_000);
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,9 @@ import type { AssistantProvider } from "@homarr/definitions";
|
||||
|
||||
export type AssistantProviderQuotaLevel = "ok" | "warning" | "bad" | "dead";
|
||||
|
||||
export const getAssistantProviderQuotaRefreshDelay = (resetsAt: string, now = Date.now()) =>
|
||||
Math.max(0, Date.parse(resetsAt) - now) + 1_000;
|
||||
|
||||
export const getAssistantProviderQuotaLevel = (
|
||||
usage: Pick<WorkshopAssistantUsage, "limit" | "remaining">,
|
||||
): AssistantProviderQuotaLevel => {
|
||||
|
||||
@@ -29,7 +29,7 @@ import { createWorkshopClient } from "~/components/workshop/workshop-client";
|
||||
|
||||
import { AssistantContext, AssistantPreferencesContext, useAssistantPreferences } from "./assistant-context";
|
||||
import { shouldAutomaticallyContinueAssistant } from "./assistant-auto-submit";
|
||||
import { isAssistantProviderUnavailable } from "./assistant-provider-quota";
|
||||
import { getAssistantProviderQuotaRefreshDelay, isAssistantProviderUnavailable } from "./assistant-provider-quota";
|
||||
import { AssistantAutoApprovalProvider } from "./assistant-auto-approval";
|
||||
import { getRunningAssistantPartType } from "./assistant-activity-state";
|
||||
import { prepareAssistantRequestBody } from "./assistant-attachment-payload";
|
||||
@@ -237,6 +237,12 @@ const AssistantPreferencesProvider = ({ children }: PropsWithChildren) => {
|
||||
void refreshQuota();
|
||||
}, [data?.provider, providerUser, refreshQuota]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.provider !== "homarr" || !providerUser || !quota) return;
|
||||
const timeout = window.setTimeout(() => void refreshQuota(), getAssistantProviderQuotaRefreshDelay(quota.resetsAt));
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [data?.provider, providerUser, quota, refreshQuota]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
provider: data?.provider ?? null,
|
||||
|
||||
@@ -3,13 +3,23 @@ import { WORKSHOP_API_URL, WORKSHOP_WEB_URL } from "@homarr/workshop/schema";
|
||||
|
||||
let browserClient: WorkshopBackend | undefined;
|
||||
|
||||
export function getWorkshopApiUrl() {
|
||||
const runtimeUrl =
|
||||
typeof document === "undefined"
|
||||
? undefined
|
||||
: document.querySelector<HTMLMetaElement>('meta[name="homarr-workshop-api-url"]')?.content;
|
||||
return (runtimeUrl || WORKSHOP_API_URL).replace(/\/+$/u, "");
|
||||
}
|
||||
|
||||
export function getWorkshopAssistantProviderUrl() {
|
||||
return `${getWorkshopApiUrl()}/api/ai/v1`;
|
||||
}
|
||||
|
||||
export function createWorkshopClient() {
|
||||
if (typeof document === "undefined") return new WorkshopBackend(WORKSHOP_API_URL);
|
||||
if (typeof document === "undefined") return new WorkshopBackend(getWorkshopApiUrl());
|
||||
if (browserClient) return browserClient;
|
||||
|
||||
const runtimeUrl = document.querySelector<HTMLMetaElement>('meta[name="homarr-workshop-api-url"]')?.content;
|
||||
|
||||
browserClient = new WorkshopBackend(runtimeUrl || WORKSHOP_API_URL);
|
||||
browserClient = new WorkshopBackend(getWorkshopApiUrl());
|
||||
return browserClient;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { getWorkshopWebUrl } from "./workshop-client";
|
||||
import { getWorkshopAssistantProviderUrl, getWorkshopWebUrl } from "./workshop-client";
|
||||
|
||||
describe("Workshop widget install flow", () => {
|
||||
afterEach(() => {
|
||||
@@ -17,6 +17,12 @@ describe("Workshop widget install flow", () => {
|
||||
expect(getWorkshopWebUrl("widget/id")).toBe("https://preview.example/workshop/widget%2Fid");
|
||||
});
|
||||
|
||||
it("derives the Homarr provider endpoint from the runtime Workshop API", () => {
|
||||
document.head.innerHTML = '<meta name="homarr-workshop-api-url" content="https://api.preview.example/">';
|
||||
|
||||
expect(getWorkshopAssistantProviderUrl()).toBe("https://api.preview.example/api/ai/v1");
|
||||
});
|
||||
|
||||
it("uses one registered modal stack for browsing, details, reports, and confirmation", () => {
|
||||
const installButton = readFileSync(
|
||||
`${process.cwd()}/apps/nextjs/src/components/workshop/workshop-install-button.tsx`,
|
||||
|
||||
Reference in new issue
Block a user