fix(webui): move mutations to authenticated websocket requests

This commit is contained in:
chengyongru
2026-08-10 16:23:47 +08:00
committed by chengyongru
parent 71a99b0780
commit 5d733b1c7c
25 changed files with 2458 additions and 2056 deletions
+30 -31
View File
@@ -724,7 +724,7 @@ export function SettingsView({
hostChromeInset = false,
}: SettingsViewProps) {
const { t } = useTranslation();
const { getToken, token } = useClient();
const { client, getToken, token } = useClient();
const pageVisible = usePageVisibility();
const remoteBrowserAccess =
typeof window !== "undefined" && !isLoopbackHost(window.location.hostname);
@@ -872,7 +872,7 @@ export function SettingsView({
const poll = async () => {
try {
const payload = await completeProviderOAuth(
getToken(),
client,
providerOAuthFlow.provider,
providerOAuthFlow.flow_id,
);
@@ -902,7 +902,7 @@ export function SettingsView({
cancelled = true;
if (timer !== null) window.clearTimeout(timer);
};
}, [applyPayload, closeProviderOAuthFlow, getToken, providerOAuthFlow]);
}, [applyPayload, client, closeProviderOAuthFlow, providerOAuthFlow]);
useEffect(() => {
if (!initialSettings || settings !== null) return;
@@ -1301,7 +1301,7 @@ export function SettingsView({
}
setModelConfigurationSaving(true);
try {
const payload = await createModelConfiguration(token, {
const payload = await createModelConfiguration(client, {
label,
provider,
model,
@@ -1319,7 +1319,7 @@ export function SettingsView({
let finalPayload = payload;
if (nextOrder) {
const orderedPayload = await updateModelCallOrder(token, nextOrder);
const orderedPayload = await updateModelCallOrder(client, nextOrder);
applyPayload(orderedPayload);
finalPayload = orderedPayload;
}
@@ -1345,7 +1345,7 @@ export function SettingsView({
const reasoningEffort = form.reasoningEffort || null;
setSaving(true);
try {
const payload = await updateModelConfiguration(token, {
const payload = await updateModelConfiguration(client, {
name: selectedPreset.name,
label:
form.presetLabel.trim() !== selectedPreset.label
@@ -1431,7 +1431,7 @@ export function SettingsView({
setModelCallOrder(nextOrder);
setModelCallOrderSaving(true);
try {
const payload = await updateModelCallOrder(token, nextOrder);
const payload = await updateModelCallOrder(client, nextOrder);
applyPayload(payload, { preserveAgentForm: true });
onModelNameChange(payload.agent.model || null);
setError(null);
@@ -1447,7 +1447,7 @@ export function SettingsView({
if (modelMigrationSaving) return;
setModelMigrationSaving(true);
try {
const payload = await migrateModelConfigurations(token);
const payload = await migrateModelConfigurations(client);
applyPayload(payload);
onModelNameChange(payload.agent.model || null);
setError(null);
@@ -1469,7 +1469,7 @@ export function SettingsView({
}
setSaving(true);
try {
const payload = await deleteModelConfiguration(token, modelPresetPendingDelete.name);
const payload = await deleteModelConfiguration(client, modelPresetPendingDelete.name);
applyPayload(payload);
setModelPresetPendingDelete(null);
setError(null);
@@ -1484,7 +1484,7 @@ export function SettingsView({
if (!settings || !imageGenerationDirty || imageGenerationSaving) return;
setImageGenerationSaving(true);
try {
const payload = await updateImageGenerationSettings(token, imageGenerationForm);
const payload = await updateImageGenerationSettings(client, imageGenerationForm);
applyPayload(payload);
if (payload.requires_restart) {
setPendingRestartSections((prev) => ({ ...prev, image: true }));
@@ -1502,7 +1502,7 @@ export function SettingsView({
if (!settings || !transcriptionDirty || transcriptionSaving) return;
setTranscriptionSaving(true);
try {
const payload = await updateTranscriptionSettings(token, transcriptionForm);
const payload = await updateTranscriptionSettings(client, transcriptionForm);
applyPayload(payload);
if (payload.requires_restart) {
setPendingRestartSections((prev) => ({ ...prev, browser: true }));
@@ -1520,7 +1520,7 @@ export function SettingsView({
if (!settings || !networkSafetyDirty || networkSafetySaving) return;
setNetworkSafetySaving(true);
try {
const payload = await updateNetworkSafetySettings(token, networkSafetyForm);
const payload = await updateNetworkSafetySettings(client, networkSafetyForm);
applyPayload(payload);
if (payload.requires_restart) {
setPendingRestartSections((prev) => ({ ...prev, runtime: true }));
@@ -1544,7 +1544,7 @@ export function SettingsView({
try {
let latest = nanobotFeatures;
for (const name of missing) {
latest = await enableNanobotFeature(token, name);
latest = await enableNanobotFeature(client, name);
if (latest.requires_restart) {
setPendingRestartSections((prev) => ({ ...prev, runtime: true }));
}
@@ -1568,8 +1568,8 @@ export function SettingsView({
setApiServiceError(null);
try {
const payload = action === "start"
? await startApiService(token, values!)
: await stopApiService(token);
? await startApiService(client, values!)
: await stopApiService(client);
setApiService(payload);
const refreshed = await fetchNanobotFeatures(token);
setNanobotFeatures(refreshed);
@@ -1622,7 +1622,7 @@ export function SettingsView({
if (field === "region") update.region = providerForm.region.trim();
if (field === "profile") update.profile = providerForm.profile.trim();
}
const payload = await updateProviderSettings(token, update);
const payload = await updateProviderSettings(client, update);
applyPayload(payload);
if (payload.requires_restart) {
setPendingRestartSections((prev) => ({ ...prev, image: true }));
@@ -1656,7 +1656,7 @@ export function SettingsView({
if (providerSaving) return false;
setProviderSaving(CUSTOM_PROVIDER_CREATION_KEY);
try {
const payload = await createProviderSettings(token, {
const payload = await createProviderSettings(client, {
name: draft.name.trim(),
apiKey: draft.apiKey.trim() || undefined,
apiBase: draft.apiBase.trim(),
@@ -1698,12 +1698,11 @@ export function SettingsView({
const payload =
action === "login"
? await loginProviderOAuth(
token,
client,
providerName,
"",
providerName === "openai_codex" && remoteBrowserAccess,
)
: await logoutProviderOAuth(token, providerName);
: await logoutProviderOAuth(client, providerName);
if (isProviderOAuthAuthorizationRequired(payload)) {
try {
if (popup && !popup.closed) popup.location.href = payload.authorization_url;
@@ -1739,7 +1738,7 @@ export function SettingsView({
setProviderOAuthDialogError(null);
try {
const payload = await completeProviderOAuth(
token,
client,
flow.provider,
flow.flow_id,
authorizationResponse,
@@ -1798,7 +1797,7 @@ export function SettingsView({
update.apiKey = apiKey;
}
if (provider.credential === "base_url") update.baseUrl = baseUrl;
const payload = await updateWebSearchSettings(token, update);
const payload = await updateWebSearchSettings(client, update);
applyPayload(payload);
if (payload.requires_restart || webFetchRestartRequired) {
setPendingRestartSections((prev) => ({ ...prev, browser: true }));
@@ -1903,7 +1902,7 @@ export function SettingsView({
setCliAppsMessage(null);
setCliAppsError(null);
try {
const payload = await runCliAppAction(token, action, name);
const payload = await runCliAppAction(client, action, name);
setCliApps(payload);
if (action !== "test") {
notifyCliAppsChanged(payload);
@@ -1934,8 +1933,8 @@ export function SettingsView({
setNanobotFeaturesError(null);
try {
const payload = action === "enable"
? await enableNanobotFeature(token, name)
: await disableNanobotFeature(token, name);
? await enableNanobotFeature(client, name)
: await disableNanobotFeature(client, name);
setNanobotFeatures(payload);
if (payload.requires_restart) {
setPendingRestartSections((prev) => ({ ...prev, runtime: true }));
@@ -1955,7 +1954,7 @@ export function SettingsView({
setAutomationAction(key);
setAutomationsError(null);
try {
const payload = await runAutomationAction(token, action, job.id);
const payload = await runAutomationAction(client, action, job.id);
setAutomations(payload);
if (action === "delete") setAutomationPendingDelete(null);
if (action === "run") {
@@ -1977,7 +1976,7 @@ export function SettingsView({
setAutomationAction(key);
setAutomationsError(null);
try {
const payload = await updateAutomation(token, job.id, values);
const payload = await updateAutomation(client, job.id, values);
setAutomations(payload);
setAutomationPendingEdit(null);
} catch (err) {
@@ -1997,7 +1996,7 @@ export function SettingsView({
setMcpMessage(null);
setMcpError(null);
try {
const payload = await runMcpPresetAction(token, action, name, values);
const payload = await runMcpPresetAction(client, action, name, values);
setMcpPresets(payload);
setMcpMessage(payload.last_action?.message ?? null);
if (action !== "test") {
@@ -2024,7 +2023,7 @@ export function SettingsView({
setMcpMessage(null);
setMcpError(null);
try {
const payload = await saveCustomMcpServer(token, {
const payload = await saveCustomMcpServer(client, {
name,
transport: customMcpForm.transport,
command: customMcpForm.command,
@@ -2054,7 +2053,7 @@ export function SettingsView({
setMcpMessage(null);
setMcpError(null);
try {
const payload = await importMcpConfig(token, mcpConfigImport);
const payload = await importMcpConfig(client, mcpConfigImport);
setMcpPresets(payload);
setMcpMessage(payload.last_action?.message ?? null);
notifyMcpPresetsChanged(payload);
@@ -2075,7 +2074,7 @@ export function SettingsView({
setMcpMessage(null);
setMcpError(null);
try {
const payload = await updateMcpServerTools(token, name, enabledTools);
const payload = await updateMcpServerTools(client, name, enabledTools);
setMcpPresets(payload);
setMcpMessage(payload.last_action?.message ?? null);
notifyMcpPresetsChanged(payload);
@@ -269,7 +269,7 @@ function SkillDetailSheet({
open: boolean;
onOpenChange: (open: boolean) => void;
}) {
const { getToken } = useClient();
const { client, getToken } = useClient();
const { t } = useTranslation();
const [detail, setDetail] = useState<SkillDetail | null>(null);
const [loading, setLoading] = useState(false);
@@ -321,7 +321,7 @@ function SkillDetailSheet({
setActionBusy(true);
setActionError("");
try {
const payload = await updateSkillEnabled(getToken(), activeSkill.name, !enabled);
const payload = await updateSkillEnabled(client, activeSkill.name, !enabled);
notifySkillsChanged(payload);
const updated = payload.skills.find((item) => item.name === activeSkill.name);
if (updated) {
@@ -345,7 +345,7 @@ function SkillDetailSheet({
setActionBusy(true);
setActionError("");
try {
const payload = await deleteSkill(getToken(), activeSkill.name);
const payload = await deleteSkill(client, activeSkill.name);
notifySkillsChanged(payload);
onOpenChange(false);
} catch (reason) {
@@ -46,7 +46,7 @@ export function SkillsMarketplace({
installing: string;
onInstallingChange: (skillId: string) => void;
}) {
const { getToken } = useClient();
const { client, getToken } = useClient();
const { t } = useTranslation();
const [query, setQuery] = useState("");
const [results, setResults] = useState<MarketplaceSkillSummary[]>([]);
@@ -161,7 +161,7 @@ export function SkillsMarketplace({
setError("");
try {
const payload = await installMarketplaceSkill(
getToken(),
client,
skill.provider,
skill.source,
skill.skill_id,
@@ -38,6 +38,7 @@ import type {
NanobotFeaturesPayload,
} from "@/lib/types";
import { cn } from "@/lib/utils";
import { useClient } from "@/providers/ClientProvider";
export type ChannelInstancesPanelCustomization = {
countLabel?: (runningCount: number) => string;
@@ -50,7 +51,6 @@ export type ChannelInstancesPanelCustomization = {
};
export function ChannelInstancesPanel({
token,
feature,
showBrandLogos,
chatAppsDocsUrl,
@@ -58,7 +58,6 @@ export function ChannelInstancesPanel({
onFeaturesUpdate,
customization = {},
}: {
token: string;
feature: NanobotFeatureInfo;
showBrandLogos: boolean;
chatAppsDocsUrl?: string;
@@ -66,6 +65,7 @@ export function ChannelInstancesPanel({
onFeaturesUpdate: (payload: NanobotFeaturesPayload) => void;
customization?: ChannelInstancesPanelCustomization;
}) {
const { client } = useClient();
const { t, i18n } = useTranslation();
const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback });
const displayName = localizedChannelDisplayName(feature, t);
@@ -111,8 +111,8 @@ export function ChannelInstancesPanel({
setNotice(null);
try {
const payload = checked
? await enableNanobotFeature(token, feature.name, { instanceId: instance.id })
: await disableNanobotFeature(token, feature.name, { instanceId: instance.id });
? await enableNanobotFeature(client, feature.name, { instanceId: instance.id })
: await disableNanobotFeature(client, feature.name, { instanceId: instance.id });
onFeaturesUpdate(payload);
} catch (err) {
setNotice((err as Error).message);
@@ -127,7 +127,7 @@ export function ChannelInstancesPanel({
setNotice(null);
try {
const payload = await configureChannel(
token,
client,
feature.name,
channelValuesForSave(instanceFields, fieldValues),
{ enable: selected.enabled, instanceId: selected.id },
@@ -14,6 +14,7 @@ import type {
ChannelConnectPayload,
NanobotFeaturesPayload,
} from "@/lib/types";
import { useClient } from "@/providers/ClientProvider";
export type ChannelQrConnectLabels = {
qrAlt: string;
@@ -43,7 +44,6 @@ export type ChannelQrConnectPendingContext = {
};
export function ChannelQrConnectFlow({
token,
channelName,
startOptions = {},
idleLabel,
@@ -69,6 +69,7 @@ export function ChannelQrConnectFlow({
resolveMessage?: (payload: ChannelConnectPayload) => string | undefined;
suppressSucceeded?: boolean;
}) {
const { client } = useClient();
const pageVisible = usePageVisibility();
const { t } = useTranslation();
const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback });
@@ -78,8 +79,6 @@ export function ChannelQrConnectFlow({
const [error, setError] = useState<string | null>(null);
const [handledRequestId, setHandledRequestId] = useState(0);
const pollInFlight = useRef(false);
const tokenRef = useRef(token);
tokenRef.current = token;
const startDomain = startOptions.domain;
const startInstanceId = startOptions.instanceId;
const startMode = startOptions.mode;
@@ -129,7 +128,7 @@ export function ChannelQrConnectFlow({
pollInFlight.current = true;
try {
const payload = await pollChannelConnect(
tokenRef.current,
client,
channelName,
sessionId,
);
@@ -163,6 +162,7 @@ export function ChannelQrConnectFlow({
};
}, [
channelName,
client,
connect?.interval_ms,
connect?.session_id,
connect?.status,
@@ -175,7 +175,7 @@ export function ChannelQrConnectFlow({
setBusy(true);
setError(null);
try {
const payload = await startChannelConnect(tokenRef.current, channelName, {
const payload = await startChannelConnect(client, channelName, {
domain: startDomain,
instanceId: startInstanceId,
mode: startMode,
@@ -187,7 +187,7 @@ export function ChannelQrConnectFlow({
} finally {
setBusy(false);
}
}, [channelName, startDomain, startForce, startInstanceId, startMode]);
}, [channelName, client, startDomain, startForce, startInstanceId, startMode]);
useEffect(() => {
if (!connectRequestId || connectRequestId === handledRequestId) return;
@@ -203,7 +203,7 @@ export function ChannelQrConnectFlow({
setBusy(true);
try {
const payload = await cancelChannelConnect(
tokenRef.current,
client,
channelName,
connect.session_id,
);
@@ -223,10 +223,9 @@ export function ChannelQrConnectFlow({
setError(null);
try {
const payload = await pollChannelConnect(
tokenRef.current,
client,
channelName,
connect.session_id,
"",
params,
);
setConnect((current) => ({
@@ -54,6 +54,7 @@ import type {
NanobotFeaturesPayload,
} from "@/lib/types";
import { cn } from "@/lib/utils";
import { useClient } from "@/providers/ClientProvider";
export function ChannelCatalogRow({
feature,
@@ -148,7 +149,6 @@ export function ChannelSetupPanel({
if (feature.instances !== undefined) {
return (
<ChannelInstancesPanel
token={token}
feature={feature}
showBrandLogos={showBrandLogos}
chatAppsDocsUrl={chatAppsDocsUrl}
@@ -269,6 +269,7 @@ function ChannelSetupSurface({
ConnectFlow?: ComponentType<ChannelPluginConnectFlowProps>;
onFeaturesUpdate: (payload: NanobotFeaturesPayload) => void;
}) {
const { client } = useClient();
const { t } = useTranslation();
const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback });
const [notice, setNotice] = useState<string | null>(null);
@@ -345,7 +346,7 @@ function ChannelSetupSurface({
setNotice(null);
const values = channelValuesForSubmit(fields, fieldValues, touchedFields);
try {
const validationPayload = await validateChannel(token, feature.name, values);
const validationPayload = await validateChannel(client, feature.name, values);
setValidation(validationPayload);
if (!validationPayload.can_enable) {
setNotice(
@@ -355,7 +356,7 @@ function ChannelSetupSurface({
return;
}
const payload = await configureChannel(
token,
client,
feature.name,
values,
{ enable: true },
@@ -377,7 +378,7 @@ function ChannelSetupSurface({
setNotice(null);
try {
const payload = await validateChannel(
token,
client,
feature.name,
channelValuesForSubmit(fields, fieldValues, touchedFields),
);