feat(webui): add computer use controls

This commit is contained in:
Xubin Ren
2026-08-09 02:40:17 +09:00
parent a739185740
commit 612e714479
20 changed files with 501 additions and 1 deletions
+29
View File
@@ -1261,6 +1261,11 @@ def settings_payload(
"use_jina_reader": config.tools.web.fetch.use_jina_reader, "use_jina_reader": config.tools.web.fetch.use_jina_reader,
}, },
}, },
"computer_use": {
"browser_enabled": config.tools.browser.enable,
"enabled": config.tools.computer_use.enable,
"backend": config.tools.computer_use.backend,
},
"api": { "api": {
"host": config.api.host, "host": config.api.host,
"port": config.api.port, "port": config.api.port,
@@ -2045,6 +2050,30 @@ def update_network_safety_settings(query: QueryParams) -> dict[str, Any]:
return settings_payload(requires_restart=changed) return settings_payload(requires_restart=changed)
def update_computer_use_settings(query: QueryParams) -> dict[str, Any]:
raw_browser = _query_first_alias(query, "browser_enabled", "browserEnabled")
raw_computer = _query_first_alias(query, "enabled", "computerEnabled")
if raw_browser is None and raw_computer is None:
raise WebUISettingsError("browser_enabled or enabled is required")
config = load_config()
changed = False
if raw_browser is not None:
browser_enabled = _parse_bool(raw_browser, "browser_enabled")
if config.tools.browser.enable != browser_enabled:
config.tools.browser.enable = browser_enabled
changed = True
if raw_computer is not None:
computer_enabled = _parse_bool(raw_computer, "enabled")
if config.tools.computer_use.enable != computer_enabled:
config.tools.computer_use.enable = computer_enabled
changed = True
if changed:
save_config(config)
return settings_payload(requires_restart=changed)
def update_web_search_settings(query: QueryParams) -> dict[str, Any]: def update_web_search_settings(query: QueryParams) -> dict[str, Any]:
provider_name = (_query_first(query, "provider") or "").strip().lower() provider_name = (_query_first(query, "provider") or "").strip().lower()
provider_option = _WEB_SEARCH_PROVIDER_BY_NAME.get(provider_name) provider_option = _WEB_SEARCH_PROVIDER_BY_NAME.get(provider_name)
+12
View File
@@ -64,6 +64,7 @@ from nanobot.webui.settings_api import (
settings_usage_payload, settings_usage_payload,
update_agent_settings, update_agent_settings,
update_api_settings, update_api_settings,
update_computer_use_settings,
update_image_generation_settings, update_image_generation_settings,
update_model_call_order, update_model_call_order,
update_model_configuration, update_model_configuration,
@@ -174,6 +175,8 @@ class WebUISettingsRouter:
return await self._handle_settings_provider_oauth(request, "logout") return await self._handle_settings_provider_oauth(request, "logout")
if path == "/api/settings/web-search/update": if path == "/api/settings/web-search/update":
return self._handle_settings_web_search_update(request) return self._handle_settings_web_search_update(request)
if path == "/api/settings/computer-use/update":
return self._handle_settings_computer_use_update(request)
if path == "/api/settings/api-service": if path == "/api/settings/api-service":
return self._handle_settings_api_service(request) return self._handle_settings_api_service(request)
if path == "/api/settings/api-service/start": if path == "/api/settings/api-service/start":
@@ -506,6 +509,15 @@ class WebUISettingsRouter:
return self._error_response(e.status, e.message) return self._error_response(e.status, e.message)
return self._json_response(self._with_restart_state(payload, section="browser")) return self._json_response(self._with_restart_state(payload, section="browser"))
def _handle_settings_computer_use_update(self, request: WsRequest) -> Response:
if not self._authorized(request):
return self._unauthorized()
try:
payload = update_computer_use_settings(self._query(request))
except WebUISettingsError as e:
return self._error_response(e.status, e.message)
return self._json_response(self._with_restart_state(payload, section="runtime"))
def _handle_settings_api_service(self, request: WsRequest) -> Response: def _handle_settings_api_service(self, request: WsRequest) -> Response:
if not self._authorized(request): if not self._authorized(request):
return self._unauthorized() return self._unauthorized()
+48
View File
@@ -29,6 +29,7 @@ from nanobot.webui.settings_api import (
settings_usage_payload, settings_usage_payload,
update_agent_settings, update_agent_settings,
update_api_settings, update_api_settings,
update_computer_use_settings,
update_model_call_order, update_model_call_order,
update_model_configuration, update_model_configuration,
update_network_safety_settings, update_network_safety_settings,
@@ -1007,6 +1008,27 @@ def test_settings_payload_includes_network_safety_fields(
assert payload["advanced"]["ssrf_whitelist_count"] == 1 assert payload["advanced"]["ssrf_whitelist_count"] == 1
def test_settings_payload_includes_computer_use_tools(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
config_path = tmp_path / "config.json"
config = Config()
config.tools.browser.enable = True
config.tools.computer_use.enable = True
config.tools.computer_use.backend = "browser"
save_config(config, config_path)
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
payload = settings_payload()
assert payload["computer_use"] == {
"browser_enabled": True,
"enabled": True,
"backend": "browser",
}
def test_settings_payload_includes_exec_path_flags( def test_settings_payload_includes_exec_path_flags(
tmp_path, tmp_path,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
@@ -1374,6 +1396,32 @@ def test_update_network_safety_settings_writes_local_service_flag(
assert payload["requires_restart"] is True assert payload["requires_restart"] is True
def test_update_computer_use_settings_writes_only_requested_switches(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
config_path = tmp_path / "config.json"
save_config(Config(), config_path)
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
browser_payload = update_computer_use_settings({"browser_enabled": ["true"]})
saved = load_config(config_path)
assert saved.tools.browser.enable is True
assert saved.tools.computer_use.enable is False
assert browser_payload["requires_restart"] is True
computer_payload = update_computer_use_settings({"computerEnabled": ["true"]})
saved = load_config(config_path)
assert saved.tools.browser.enable is True
assert saved.tools.computer_use.enable is True
assert computer_payload["computer_use"]["enabled"] is True
def test_update_computer_use_settings_requires_a_switch() -> None:
with pytest.raises(WebUISettingsError, match="browser_enabled or enabled"):
update_computer_use_settings({})
def test_update_network_safety_settings_accepts_legacy_restricted_default_access( def test_update_network_safety_settings_accepts_legacy_restricted_default_access(
tmp_path, tmp_path,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
+25
View File
@@ -138,3 +138,28 @@ async def test_model_preset_mutation_routes(
assert response.status_code == 200 assert response.status_code == 200
assert json.loads(response.body)["routed"] == function_name assert json.loads(response.body)["routed"] == function_name
assert captured["query"] == expected_query assert captured["query"] == expected_query
@pytest.mark.asyncio
async def test_computer_use_update_route(monkeypatch) -> None:
captured: dict[str, object] = {}
def update(query):
captured["query"] = query
return {"requires_restart": True}
monkeypatch.setattr("nanobot.webui.settings_routes.update_computer_use_settings", update)
request = SimpleNamespace(
path="/api/settings/computer-use/update?browser_enabled=true",
headers=Headers(),
)
response = await _router().dispatch(
None,
request,
"/api/settings/computer-use/update",
)
assert response is not None
assert response.status_code == 200
assert captured["query"] == {"browser_enabled": ["true"]}
+147 -1
View File
@@ -139,6 +139,7 @@ import {
startApiService, startApiService,
stopApiService, stopApiService,
updateAutomation, updateAutomation,
updateComputerUseSettings,
updateImageGenerationSettings, updateImageGenerationSettings,
updateMcpServerTools, updateMcpServerTools,
updateModelCallOrder, updateModelCallOrder,
@@ -179,6 +180,7 @@ import type {
AutomationUpdatePayload, AutomationUpdatePayload,
CliAppInfo, CliAppInfo,
CliAppsPayload, CliAppsPayload,
ComputerUseSettingsUpdate,
ImageGenerationSettingsUpdate, ImageGenerationSettingsUpdate,
McpPresetInfo, McpPresetInfo,
McpPresetsPayload, McpPresetsPayload,
@@ -762,6 +764,7 @@ export function SettingsView({
const [imageGenerationSaving, setImageGenerationSaving] = useState(false); const [imageGenerationSaving, setImageGenerationSaving] = useState(false);
const [transcriptionSaving, setTranscriptionSaving] = useState(false); const [transcriptionSaving, setTranscriptionSaving] = useState(false);
const [networkSafetySaving, setNetworkSafetySaving] = useState(false); const [networkSafetySaving, setNetworkSafetySaving] = useState(false);
const [computerUseSaving, setComputerUseSaving] = useState<"browser" | "computer" | null>(null);
const [apiService, setApiService] = useState<ApiServicePayload | null>(null); const [apiService, setApiService] = useState<ApiServicePayload | null>(null);
const [apiServiceLoading, setApiServiceLoading] = useState(false); const [apiServiceLoading, setApiServiceLoading] = useState(false);
const [apiServiceAction, setApiServiceAction] = useState<"start" | "stop" | null>(null); const [apiServiceAction, setApiServiceAction] = useState<"start" | "stop" | null>(null);
@@ -1002,7 +1005,7 @@ export function SettingsView({
useEffect(() => { useEffect(() => {
if ( if (
!pageVisible !pageVisible
|| !["channels", "models", "browser", "runtime"].includes(activeSection) || !["channels", "models", "browser", "runtime", "advanced"].includes(activeSection)
) { ) {
return; return;
} }
@@ -1559,6 +1562,34 @@ export function SettingsView({
} }
}; };
const setComputerUseEnabled = async (
target: "browser" | "computer",
enabled: boolean,
) => {
if (!settings || computerUseSaving) return;
setComputerUseSaving(target);
try {
if (enabled && !(await installCapabilities(["computer-use"]))) return;
const update: ComputerUseSettingsUpdate = target === "browser"
? { browserEnabled: enabled }
: { computerEnabled: enabled };
const payload = await updateComputerUseSettings(token, update);
applyPayload(payload);
if (payload.requires_restart) {
setPendingRestartSections((prev) => ({
...prev,
[target === "browser" ? "browser" : "runtime"]: true,
}));
}
await maybeRestartHostEngine(payload);
setError(null);
} catch (err) {
setError((err as Error).message);
} finally {
setComputerUseSaving(null);
}
};
const handleApiServiceAction = async ( const handleApiServiceAction = async (
action: "start" | "stop", action: "start" | "stop",
values?: { host: string; port: number; timeout: number; apiKey?: string }, values?: { host: string; port: number; timeout: number; apiKey?: string },
@@ -2238,6 +2269,11 @@ export function SettingsView({
olostepFeature={featureCatalog.find((feature) => feature.name === "olostep")} olostepFeature={featureCatalog.find((feature) => feature.name === "olostep")}
olostepInstalling={nanobotFeatureAction === "enable:olostep"} olostepInstalling={nanobotFeatureAction === "enable:olostep"}
capabilityError={nanobotFeaturesError} capabilityError={nanobotFeaturesError}
browserAutomationEnabled={settings.computer_use?.browser_enabled ?? false}
computerUseFeature={featureCatalog.find((feature) => feature.name === "computer-use")}
computerUseSaving={computerUseSaving === "browser"}
computerUseInstalling={nanobotFeatureAction === "enable:computer-use"}
onToggleBrowserAutomation={(enabled) => void setComputerUseEnabled("browser", enabled)}
/> />
); );
case "channels": case "channels":
@@ -2364,6 +2400,13 @@ export function SettingsView({
onRestart={restartViaSettingsSurface} onRestart={restartViaSettingsSurface}
isRestarting={isRestarting || hostEngineApplying} isRestarting={isRestarting || hostEngineApplying}
requiresRestartPending={pendingRestartSections.runtime} requiresRestartPending={pendingRestartSections.runtime}
computerControlEnabled={settings.computer_use?.enabled ?? false}
computerUseBackend={settings.computer_use?.backend ?? "desktop"}
computerUseFeature={featureCatalog.find((feature) => feature.name === "computer-use")}
computerUseSaving={computerUseSaving === "computer"}
computerUseInstalling={nanobotFeatureAction === "enable:computer-use"}
capabilityError={nanobotFeaturesError}
onToggleComputerControl={(enabled) => void setComputerUseEnabled("computer", enabled)}
/> />
); );
default: default:
@@ -5255,6 +5298,11 @@ function WebSettings({
olostepFeature, olostepFeature,
olostepInstalling, olostepInstalling,
capabilityError, capabilityError,
browserAutomationEnabled,
computerUseFeature,
computerUseSaving,
computerUseInstalling,
onToggleBrowserAutomation,
}: { }: {
settings: SettingsPayload; settings: SettingsPayload;
form: WebSearchSettingsUpdate; form: WebSearchSettingsUpdate;
@@ -5274,6 +5322,11 @@ function WebSettings({
olostepFeature?: NanobotFeatureInfo; olostepFeature?: NanobotFeatureInfo;
olostepInstalling: boolean; olostepInstalling: boolean;
capabilityError: string | null; capabilityError: string | null;
browserAutomationEnabled: boolean;
computerUseFeature?: NanobotFeatureInfo;
computerUseSaving: boolean;
computerUseInstalling: boolean;
onToggleBrowserAutomation: (enabled: boolean) => void;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback }); const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback });
@@ -5302,9 +5355,46 @@ function WebSettings({
: selectedProvider?.credential === "base_url" : selectedProvider?.credential === "base_url"
? !baseUrl ? !baseUrl
: false; : false;
const computerUseInstalled = computerUseFeature?.installed ?? true;
const browserAutomationDescription = computerUseInstalling
? tx("settings.help.computerUseInstalling", "Installing computer-use support...")
: computerUseInstalled
? tx(
"settings.help.browserAutomation",
"Let nanobot navigate and act on web pages using structured page elements. Requires Playwright Chromium.",
)
: tx(
"settings.help.computerUseInstall",
"Required Python support will be installed when you turn this on.",
);
return ( return (
<div className="space-y-7"> <div className="space-y-7">
<section>
<SettingsSectionTitle>
{tx("settings.sections.browserAutomation", "Browser automation")}
</SettingsSectionTitle>
<SettingsGroup>
<SettingsRow
title={tx("settings.rows.browserAutomation", "Browser automation")}
description={browserAutomationDescription}
>
<ToggleButton
checked={browserAutomationEnabled}
disabled={computerUseSaving || computerUseInstalling}
onChange={onToggleBrowserAutomation}
ariaLabel={tx("settings.rows.browserAutomation", "Browser automation")}
label={browserAutomationEnabled
? tx("settings.values.on", "On")
: tx("settings.values.off", "Off")}
/>
</SettingsRow>
</SettingsGroup>
{capabilityError ? (
<p className="mt-2 px-1 text-[12px] text-destructive">{capabilityError}</p>
) : null}
</section>
<section> <section>
<SettingsSectionTitle>{tx("settings.sections.webSearch", "Web search")}</SettingsSectionTitle> <SettingsSectionTitle>{tx("settings.sections.webSearch", "Web search")}</SettingsSectionTitle>
{form.provider === "olostep" && olostepFeature && !olostepFeature.installed ? ( {form.provider === "olostep" && olostepFeature && !olostepFeature.installed ? (
@@ -8765,6 +8855,13 @@ function AdvancedSettings({
onSave, onSave,
onRestart, onRestart,
isRestarting, isRestarting,
computerControlEnabled,
computerUseBackend,
computerUseFeature,
computerUseSaving,
computerUseInstalling,
capabilityError,
onToggleComputerControl,
}: { }: {
form: NetworkSafetySettingsUpdate; form: NetworkSafetySettingsUpdate;
dirty: boolean; dirty: boolean;
@@ -8775,11 +8872,60 @@ function AdvancedSettings({
onSave: () => void; onSave: () => void;
onRestart?: () => void; onRestart?: () => void;
isRestarting?: boolean; isRestarting?: boolean;
computerControlEnabled: boolean;
computerUseBackend: "desktop" | "browser";
computerUseFeature?: NanobotFeatureInfo;
computerUseSaving: boolean;
computerUseInstalling: boolean;
capabilityError: string | null;
onToggleComputerControl: (enabled: boolean) => void;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback }); const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback });
const computerUseInstalled = computerUseFeature?.installed ?? true;
const computerControlDescription = computerUseInstalling
? tx("settings.help.computerUseInstalling", "Installing computer-use support...")
: !computerUseInstalled
? tx(
"settings.help.computerUseInstall",
"Required Python support will be installed when you turn this on.",
)
: computerUseBackend === "browser"
? tx(
"settings.help.computerControlBrowser",
"Pixel-based control currently targets an isolated browser, as configured in config.json.",
)
: tx(
"settings.help.computerControl",
"Let nanobot see and control the computer running its engine. macOS requires Screen Recording and Accessibility access.",
);
return ( return (
<div className="space-y-7"> <div className="space-y-7">
<section>
<SettingsSectionTitle>
{tx("settings.sections.computerControl", "Computer control")}
</SettingsSectionTitle>
<SettingsGroup>
<SettingsRow
title={tx("settings.rows.computerControl", "Computer control")}
description={computerControlDescription}
>
<ToggleButton
checked={computerControlEnabled}
disabled={computerUseSaving || computerUseInstalling}
onChange={onToggleComputerControl}
ariaLabel={tx("settings.rows.computerControl", "Computer control")}
label={computerControlEnabled
? tx("settings.values.on", "On")
: tx("settings.values.off", "Off")}
/>
</SettingsRow>
</SettingsGroup>
{capabilityError ? (
<p className="mt-2 px-1 text-[12px] text-destructive">{capabilityError}</p>
) : null}
</section>
<section> <section>
<SettingsSectionTitle> <SettingsSectionTitle>
{isNativeHostSurface {isNativeHostSurface
+9
View File
@@ -115,6 +115,8 @@
"imageDefaults": "Defaults", "imageDefaults": "Defaults",
"webSearch": "Web search", "webSearch": "Web search",
"webBehavior": "Behavior", "webBehavior": "Behavior",
"browserAutomation": "Browser automation",
"computerControl": "Computer control",
"cliApps": "CLI apps", "cliApps": "CLI apps",
"mcp": "MCP services", "mcp": "MCP services",
"regional": "Regional", "regional": "Regional",
@@ -199,6 +201,8 @@
"maxResults": "Max results", "maxResults": "Max results",
"timeout": "Timeout", "timeout": "Timeout",
"jinaReader": "Jina reader", "jinaReader": "Jina reader",
"browserAutomation": "Browser automation",
"computerControl": "Computer control",
"imageGeneration": "Image generation", "imageGeneration": "Image generation",
"imageProvider": "Image provider", "imageProvider": "Image provider",
"imageProviderStatus": "Provider status", "imageProviderStatus": "Provider status",
@@ -244,6 +248,11 @@
"maxResults": "Results returned by each web_search call.", "maxResults": "Results returned by each web_search call.",
"timeout": "Seconds before a search provider request times out.", "timeout": "Seconds before a search provider request times out.",
"jinaReader": "Use Jina Reader for web_fetch when available.", "jinaReader": "Use Jina Reader for web_fetch when available.",
"browserAutomation": "Let nanobot navigate and act on web pages using structured page elements. Requires Playwright Chromium.",
"computerControl": "Let nanobot see and control the computer running its engine. macOS requires Screen Recording and Accessibility access.",
"computerControlBrowser": "Pixel-based control currently targets an isolated browser, as configured in config.json.",
"computerUseInstall": "Required Python support will be installed when you turn this on.",
"computerUseInstalling": "Installing computer-use support...",
"imageGeneration": "Expose generate_image in chats when a configured image provider is available.", "imageGeneration": "Expose generate_image in chats when a configured image provider is available.",
"imageProvider": "Choose the registry provider used by generate_image.", "imageProvider": "Choose the registry provider used by generate_image.",
"imageProviderStatus": "Image generation reuses provider credentials from Providers.", "imageProviderStatus": "Image generation reuses provider credentials from Providers.",
+9
View File
@@ -115,6 +115,8 @@
"imageDefaults": "Valores predeterminados", "imageDefaults": "Valores predeterminados",
"webSearch": "Búsqueda web", "webSearch": "Búsqueda web",
"webBehavior": "Comportamiento", "webBehavior": "Comportamiento",
"browserAutomation": "Automatización del navegador",
"computerControl": "Control del ordenador",
"regional": "Configuración regional", "regional": "Configuración regional",
"webuiSafety": "Seguridad de WebUI", "webuiSafety": "Seguridad de WebUI",
"capabilities": "Capacidades", "capabilities": "Capacidades",
@@ -145,6 +147,8 @@
"maxResults": "Resultados máximos", "maxResults": "Resultados máximos",
"timeout": "Tiempo de espera", "timeout": "Tiempo de espera",
"jinaReader": "Lector Jina", "jinaReader": "Lector Jina",
"browserAutomation": "Automatización del navegador",
"computerControl": "Control del ordenador",
"imageGeneration": "Generación de imágenes", "imageGeneration": "Generación de imágenes",
"imageProvider": "Proveedor de imágenes", "imageProvider": "Proveedor de imágenes",
"imageProviderStatus": "Estado del proveedor", "imageProviderStatus": "Estado del proveedor",
@@ -188,6 +192,11 @@
"maxResults": "Resultados devueltos por cada llamada web_search.", "maxResults": "Resultados devueltos por cada llamada web_search.",
"timeout": "Segundos antes de que una solicitud de búsqueda expire.", "timeout": "Segundos antes de que una solicitud de búsqueda expire.",
"jinaReader": "Usa Jina Reader para web_fetch cuando esté disponible.", "jinaReader": "Usa Jina Reader para web_fetch cuando esté disponible.",
"browserAutomation": "Permite que nanobot navegue y actúe en páginas web mediante elementos estructurados. Requiere Chromium de Playwright.",
"computerControl": "Permite que nanobot vea y controle el ordenador que ejecuta su motor. macOS requiere permisos de grabación de pantalla y accesibilidad.",
"computerControlBrowser": "El control por píxeles apunta actualmente a un navegador aislado, según config.json.",
"computerUseInstall": "El soporte de Python necesario se instalará al activarlo.",
"computerUseInstalling": "Instalando componentes de control...",
"imageGeneration": "Expone generate_image en chats cuando hay un proveedor de imagen configurado.", "imageGeneration": "Expone generate_image en chats cuando hay un proveedor de imagen configurado.",
"imageProvider": "Elige el proveedor registrado usado por generate_image.", "imageProvider": "Elige el proveedor registrado usado por generate_image.",
"imageProviderStatus": "La generación de imágenes reutiliza las credenciales de los proveedores.", "imageProviderStatus": "La generación de imágenes reutiliza las credenciales de los proveedores.",
+9
View File
@@ -115,6 +115,8 @@
"imageDefaults": "Valeurs par défaut", "imageDefaults": "Valeurs par défaut",
"webSearch": "Recherche web", "webSearch": "Recherche web",
"webBehavior": "Comportement", "webBehavior": "Comportement",
"browserAutomation": "Automatisation du navigateur",
"computerControl": "Contrôle de lordinateur",
"regional": "Paramètres régionaux", "regional": "Paramètres régionaux",
"webuiSafety": "Sécurité WebUI", "webuiSafety": "Sécurité WebUI",
"capabilities": "Capacités", "capabilities": "Capacités",
@@ -145,6 +147,8 @@
"maxResults": "Résultats max.", "maxResults": "Résultats max.",
"timeout": "Délai dattente", "timeout": "Délai dattente",
"jinaReader": "Lecteur Jina", "jinaReader": "Lecteur Jina",
"browserAutomation": "Automatisation du navigateur",
"computerControl": "Contrôle de lordinateur",
"imageGeneration": "Génération dimages", "imageGeneration": "Génération dimages",
"imageProvider": "Fournisseur dimages", "imageProvider": "Fournisseur dimages",
"imageProviderStatus": "État du fournisseur", "imageProviderStatus": "État du fournisseur",
@@ -188,6 +192,11 @@
"maxResults": "Résultats renvoyés par chaque appel web_search.", "maxResults": "Résultats renvoyés par chaque appel web_search.",
"timeout": "Nombre de secondes avant lexpiration dune requête de recherche.", "timeout": "Nombre de secondes avant lexpiration dune requête de recherche.",
"jinaReader": "Utilise Jina Reader pour web_fetch lorsque disponible.", "jinaReader": "Utilise Jina Reader pour web_fetch lorsque disponible.",
"browserAutomation": "Permet à nanobot de parcourir et manipuler les pages web à partir de leurs éléments structurés. Nécessite Chromium de Playwright.",
"computerControl": "Permet à nanobot de voir et contrôler lordinateur qui exécute son moteur. macOS exige les autorisations Enregistrement de l’écran et Accessibilité.",
"computerControlBrowser": "Le contrôle par pixels cible actuellement un navigateur isolé, conformément à config.json.",
"computerUseInstall": "Les composants Python requis seront installés lors de lactivation.",
"computerUseInstalling": "Installation des composants de contrôle...",
"imageGeneration": "Expose generate_image dans les chats lorsquun fournisseur dimage configuré est disponible.", "imageGeneration": "Expose generate_image dans les chats lorsquun fournisseur dimage configuré est disponible.",
"imageProvider": "Choisissez le fournisseur inscrit utilisé par generate_image.", "imageProvider": "Choisissez le fournisseur inscrit utilisé par generate_image.",
"imageProviderStatus": "La génération dimages réutilise les identifiants des fournisseurs.", "imageProviderStatus": "La génération dimages réutilise les identifiants des fournisseurs.",
+9
View File
@@ -115,6 +115,8 @@
"imageDefaults": "Bawaan", "imageDefaults": "Bawaan",
"webSearch": "Pencarian web", "webSearch": "Pencarian web",
"webBehavior": "Perilaku", "webBehavior": "Perilaku",
"browserAutomation": "Otomatisasi browser",
"computerControl": "Kontrol komputer",
"regional": "Regional", "regional": "Regional",
"webuiSafety": "Keamanan WebUI", "webuiSafety": "Keamanan WebUI",
"capabilities": "Kemampuan", "capabilities": "Kemampuan",
@@ -145,6 +147,8 @@
"maxResults": "Hasil maksimum", "maxResults": "Hasil maksimum",
"timeout": "Batas waktu", "timeout": "Batas waktu",
"jinaReader": "Pembaca Jina", "jinaReader": "Pembaca Jina",
"browserAutomation": "Otomatisasi browser",
"computerControl": "Kontrol komputer",
"imageGeneration": "Pembuatan gambar", "imageGeneration": "Pembuatan gambar",
"imageProvider": "Penyedia gambar", "imageProvider": "Penyedia gambar",
"imageProviderStatus": "Status penyedia", "imageProviderStatus": "Status penyedia",
@@ -188,6 +192,11 @@
"maxResults": "Hasil yang dikembalikan oleh setiap panggilan web_search.", "maxResults": "Hasil yang dikembalikan oleh setiap panggilan web_search.",
"timeout": "Detik sebelum permintaan penyedia pencarian mencapai batas waktu.", "timeout": "Detik sebelum permintaan penyedia pencarian mencapai batas waktu.",
"jinaReader": "Gunakan Jina Reader untuk web_fetch jika tersedia.", "jinaReader": "Gunakan Jina Reader untuk web_fetch jika tersedia.",
"browserAutomation": "Izinkan nanobot menjelajah dan bertindak pada halaman web melalui elemen terstruktur. Memerlukan Chromium dari Playwright.",
"computerControl": "Izinkan nanobot melihat dan mengontrol komputer yang menjalankan mesinnya. macOS memerlukan izin Perekaman Layar dan Aksesibilitas.",
"computerControlBrowser": "Kontrol berbasis piksel saat ini menargetkan browser terisolasi sesuai config.json.",
"computerUseInstall": "Dukungan Python yang diperlukan akan dipasang saat diaktifkan.",
"computerUseInstalling": "Memasang komponen kontrol komputer...",
"imageGeneration": "Tampilkan generate_image di chat saat penyedia gambar yang dikonfigurasi tersedia.", "imageGeneration": "Tampilkan generate_image di chat saat penyedia gambar yang dikonfigurasi tersedia.",
"imageProvider": "Pilih penyedia registry yang digunakan oleh generate_image.", "imageProvider": "Pilih penyedia registry yang digunakan oleh generate_image.",
"imageProviderStatus": "Pembuatan gambar menggunakan kembali kredensial penyedia dari bagian Penyedia.", "imageProviderStatus": "Pembuatan gambar menggunakan kembali kredensial penyedia dari bagian Penyedia.",
+9
View File
@@ -115,6 +115,8 @@
"imageDefaults": "既定値", "imageDefaults": "既定値",
"webSearch": "ウェブ検索", "webSearch": "ウェブ検索",
"webBehavior": "動作", "webBehavior": "動作",
"browserAutomation": "ブラウザ自動操作",
"computerControl": "コンピュータ操作",
"regional": "地域", "regional": "地域",
"webuiSafety": "WebUI の安全性", "webuiSafety": "WebUI の安全性",
"capabilities": "機能", "capabilities": "機能",
@@ -145,6 +147,8 @@
"maxResults": "最大結果数", "maxResults": "最大結果数",
"timeout": "タイムアウト", "timeout": "タイムアウト",
"jinaReader": "Jina リーダー", "jinaReader": "Jina リーダー",
"browserAutomation": "ブラウザ自動操作",
"computerControl": "コンピュータ操作",
"imageGeneration": "画像生成", "imageGeneration": "画像生成",
"imageProvider": "画像プロバイダー", "imageProvider": "画像プロバイダー",
"imageProviderStatus": "プロバイダー状態", "imageProviderStatus": "プロバイダー状態",
@@ -188,6 +192,11 @@
"maxResults": "各 web_search 呼び出しで返す結果数です。", "maxResults": "各 web_search 呼び出しで返す結果数です。",
"timeout": "検索プロバイダーのリクエストがタイムアウトするまでの秒数です。", "timeout": "検索プロバイダーのリクエストがタイムアウトするまでの秒数です。",
"jinaReader": "利用可能な場合、web_fetch に Jina Reader を使います。", "jinaReader": "利用可能な場合、web_fetch に Jina Reader を使います。",
"browserAutomation": "構造化されたページ要素を使って、nanobot がウェブページを閲覧・操作できるようにします。Playwright Chromium が必要です。",
"computerControl": "nanobot がエンジンを実行しているコンピュータを表示・操作できるようにします。macOS では画面収録とアクセシビリティの許可が必要です。",
"computerControlBrowser": "ピクセル操作は現在、config.json の設定に従って分離ブラウザを対象としています。",
"computerUseInstall": "有効にすると必要な Python コンポーネントがインストールされます。",
"computerUseInstalling": "コンピュータ操作コンポーネントをインストール中...",
"imageGeneration": "画像プロバイダーが設定済みのとき、チャットで generate_image を有効にします。", "imageGeneration": "画像プロバイダーが設定済みのとき、チャットで generate_image を有効にします。",
"imageProvider": "generate_image で使用する登録済みプロバイダーを選択します。", "imageProvider": "generate_image で使用する登録済みプロバイダーを選択します。",
"imageProviderStatus": "画像生成はプロバイダー設定の認証情報を再利用します。", "imageProviderStatus": "画像生成はプロバイダー設定の認証情報を再利用します。",
+9
View File
@@ -115,6 +115,8 @@
"imageDefaults": "기본값", "imageDefaults": "기본값",
"webSearch": "웹 검색", "webSearch": "웹 검색",
"webBehavior": "동작", "webBehavior": "동작",
"browserAutomation": "브라우저 자동화",
"computerControl": "컴퓨터 제어",
"regional": "지역", "regional": "지역",
"webuiSafety": "WebUI 보안", "webuiSafety": "WebUI 보안",
"capabilities": "기능", "capabilities": "기능",
@@ -145,6 +147,8 @@
"maxResults": "최대 결과 수", "maxResults": "최대 결과 수",
"timeout": "타임아웃", "timeout": "타임아웃",
"jinaReader": "Jina 리더", "jinaReader": "Jina 리더",
"browserAutomation": "브라우저 자동화",
"computerControl": "컴퓨터 제어",
"imageGeneration": "이미지 생성", "imageGeneration": "이미지 생성",
"imageProvider": "이미지 제공자", "imageProvider": "이미지 제공자",
"imageProviderStatus": "제공자 상태", "imageProviderStatus": "제공자 상태",
@@ -188,6 +192,11 @@
"maxResults": "각 web_search 호출에서 반환되는 결과 수입니다.", "maxResults": "각 web_search 호출에서 반환되는 결과 수입니다.",
"timeout": "검색 제공자 요청이 타임아웃되기 전의 초입니다.", "timeout": "검색 제공자 요청이 타임아웃되기 전의 초입니다.",
"jinaReader": "가능할 때 web_fetch에 Jina Reader를 사용합니다.", "jinaReader": "가능할 때 web_fetch에 Jina Reader를 사용합니다.",
"browserAutomation": "nanobot이 구조화된 페이지 요소를 사용해 웹페이지를 탐색하고 조작할 수 있게 합니다. Playwright Chromium이 필요합니다.",
"computerControl": "nanobot이 엔진을 실행하는 컴퓨터를 보고 제어할 수 있게 합니다. macOS에서는 화면 기록 및 손쉬운 사용 권한이 필요합니다.",
"computerControlBrowser": "픽셀 기반 제어는 현재 config.json 설정에 따라 격리된 브라우저를 대상으로 합니다.",
"computerUseInstall": "켜면 필요한 Python 구성 요소가 설치됩니다.",
"computerUseInstalling": "컴퓨터 제어 구성 요소 설치 중...",
"imageGeneration": "구성된 이미지 제공자가 있을 때 채팅에서 generate_image를 노출합니다.", "imageGeneration": "구성된 이미지 제공자가 있을 때 채팅에서 generate_image를 노출합니다.",
"imageProvider": "generate_image에 사용할 등록 제공자를 선택합니다.", "imageProvider": "generate_image에 사용할 등록 제공자를 선택합니다.",
"imageProviderStatus": "이미지 생성은 제공자 자격 증명을 재사용합니다.", "imageProviderStatus": "이미지 생성은 제공자 자격 증명을 재사용합니다.",
+9
View File
@@ -115,6 +115,8 @@
"imageDefaults": "Padrões", "imageDefaults": "Padrões",
"webSearch": "Busca na web", "webSearch": "Busca na web",
"webBehavior": "Comportamento", "webBehavior": "Comportamento",
"browserAutomation": "Automação do navegador",
"computerControl": "Controle do computador",
"cliApps": "Aplicativos CLI", "cliApps": "Aplicativos CLI",
"mcp": "Servidores MCP", "mcp": "Servidores MCP",
"regional": "Regional", "regional": "Regional",
@@ -199,6 +201,8 @@
"maxResults": "Máx. de resultados", "maxResults": "Máx. de resultados",
"timeout": "Tempo limite", "timeout": "Tempo limite",
"jinaReader": "Leitor Jina", "jinaReader": "Leitor Jina",
"browserAutomation": "Automação do navegador",
"computerControl": "Controle do computador",
"imageGeneration": "Geração de imagens", "imageGeneration": "Geração de imagens",
"imageProvider": "Provedor de imagem", "imageProvider": "Provedor de imagem",
"imageProviderStatus": "Status do provedor", "imageProviderStatus": "Status do provedor",
@@ -244,6 +248,11 @@
"maxResults": "Resultados retornados por cada chamada de web_search.", "maxResults": "Resultados retornados por cada chamada de web_search.",
"timeout": "Segundos antes de uma requisição de busca expirar.", "timeout": "Segundos antes de uma requisição de busca expirar.",
"jinaReader": "Usa o Jina Reader para web_fetch quando disponível.", "jinaReader": "Usa o Jina Reader para web_fetch quando disponível.",
"browserAutomation": "Permite que o nanobot navegue e interaja com páginas usando elementos estruturados. Requer o Chromium do Playwright.",
"computerControl": "Permite que o nanobot veja e controle o computador que executa o mecanismo. No macOS, requer acesso à Gravação de Tela e Acessibilidade.",
"computerControlBrowser": "O controle por pixels está direcionado a um navegador isolado, conforme o config.json.",
"computerUseInstall": "O suporte Python necessário será instalado ao ativar.",
"computerUseInstalling": "Instalando componentes de controle...",
"imageGeneration": "Expõe generate_image nas conversas quando há um provedor de imagem configurado.", "imageGeneration": "Expõe generate_image nas conversas quando há um provedor de imagem configurado.",
"imageProvider": "Escolha o provedor do registro usado por generate_image.", "imageProvider": "Escolha o provedor do registro usado por generate_image.",
"imageProviderStatus": "A geração de imagens reaproveita as credenciais dos provedores.", "imageProviderStatus": "A geração de imagens reaproveita as credenciais dos provedores.",
+9
View File
@@ -115,6 +115,8 @@
"imageDefaults": "Mặc định", "imageDefaults": "Mặc định",
"webSearch": "Tìm kiếm web", "webSearch": "Tìm kiếm web",
"webBehavior": "Hành vi", "webBehavior": "Hành vi",
"browserAutomation": "Tự động hóa trình duyệt",
"computerControl": "Điều khiển máy tính",
"regional": "Khu vực", "regional": "Khu vực",
"webuiSafety": "An toàn WebUI", "webuiSafety": "An toàn WebUI",
"capabilities": "Khả năng", "capabilities": "Khả năng",
@@ -145,6 +147,8 @@
"maxResults": "Kết quả tối đa", "maxResults": "Kết quả tối đa",
"timeout": "Thời gian chờ", "timeout": "Thời gian chờ",
"jinaReader": "Trình đọc Jina", "jinaReader": "Trình đọc Jina",
"browserAutomation": "Tự động hóa trình duyệt",
"computerControl": "Điều khiển máy tính",
"imageGeneration": "Tạo hình ảnh", "imageGeneration": "Tạo hình ảnh",
"imageProvider": "Nhà cung cấp hình ảnh", "imageProvider": "Nhà cung cấp hình ảnh",
"imageProviderStatus": "Trạng thái nhà cung cấp", "imageProviderStatus": "Trạng thái nhà cung cấp",
@@ -188,6 +192,11 @@
"maxResults": "Số kết quả được trả về sau mỗi lần gọi web_search.", "maxResults": "Số kết quả được trả về sau mỗi lần gọi web_search.",
"timeout": "Số giây trước khi yêu cầu của nhà cung cấp tìm kiếm hết thời gian.", "timeout": "Số giây trước khi yêu cầu của nhà cung cấp tìm kiếm hết thời gian.",
"jinaReader": "Dùng Jina Reader cho web_fetch khi có thể.", "jinaReader": "Dùng Jina Reader cho web_fetch khi có thể.",
"browserAutomation": "Cho phép nanobot duyệt và thao tác trên trang web bằng các phần tử có cấu trúc. Cần Playwright Chromium.",
"computerControl": "Cho phép nanobot xem và điều khiển máy tính đang chạy engine. macOS yêu cầu quyền Ghi màn hình và Trợ năng.",
"computerControlBrowser": "Điều khiển theo điểm ảnh hiện nhắm tới trình duyệt cách ly theo config.json.",
"computerUseInstall": "Hỗ trợ Python cần thiết sẽ được cài đặt khi bật.",
"computerUseInstalling": "Đang cài đặt thành phần điều khiển...",
"imageGeneration": "Hiển thị generate_image trong chat khi đã cấu hình nhà cung cấp hình ảnh.", "imageGeneration": "Hiển thị generate_image trong chat khi đã cấu hình nhà cung cấp hình ảnh.",
"imageProvider": "Chọn nhà cung cấp registry được generate_image sử dụng.", "imageProvider": "Chọn nhà cung cấp registry được generate_image sử dụng.",
"imageProviderStatus": "Tạo ảnh dùng lại thông tin xác thực từ mục Nhà cung cấp.", "imageProviderStatus": "Tạo ảnh dùng lại thông tin xác thực từ mục Nhà cung cấp.",
+9
View File
@@ -115,6 +115,8 @@
"imageDefaults": "默认值", "imageDefaults": "默认值",
"webSearch": "网络搜索", "webSearch": "网络搜索",
"webBehavior": "网络行为", "webBehavior": "网络行为",
"browserAutomation": "浏览器自动化",
"computerControl": "电脑控制",
"cliApps": "CLI 应用", "cliApps": "CLI 应用",
"mcp": "MCP 服务", "mcp": "MCP 服务",
"regional": "区域", "regional": "区域",
@@ -199,6 +201,8 @@
"maxResults": "最大结果数", "maxResults": "最大结果数",
"timeout": "超时", "timeout": "超时",
"jinaReader": "Jina 阅读器", "jinaReader": "Jina 阅读器",
"browserAutomation": "浏览器自动化",
"computerControl": "电脑控制",
"imageGeneration": "图片生成", "imageGeneration": "图片生成",
"imageProvider": "图片提供商", "imageProvider": "图片提供商",
"imageProviderStatus": "提供商状态", "imageProviderStatus": "提供商状态",
@@ -244,6 +248,11 @@
"maxResults": "每次 web_search 调用返回的结果数。", "maxResults": "每次 web_search 调用返回的结果数。",
"timeout": "搜索提供商请求超时前等待的秒数。", "timeout": "搜索提供商请求超时前等待的秒数。",
"jinaReader": "可用时为 web_fetch 使用 Jina Reader。", "jinaReader": "可用时为 web_fetch 使用 Jina Reader。",
"browserAutomation": "允许 nanobot 通过结构化页面元素浏览网页并执行操作,需要 Playwright Chromium。",
"computerControl": "允许 nanobot 查看并控制运行引擎的电脑。macOS 需要授予“屏幕录制”和“辅助功能”权限。",
"computerControlBrowser": "像素控制当前按 config.json 配置作用于隔离浏览器。",
"computerUseInstall": "开启时会自动安装所需 Python 组件。",
"computerUseInstalling": "正在安装电脑操作组件...",
"imageGeneration": "配置图片提供商后,即可在聊天中使用 generate_image。", "imageGeneration": "配置图片提供商后,即可在聊天中使用 generate_image。",
"imageProvider": "选择 generate_image 使用的注册提供商。", "imageProvider": "选择 generate_image 使用的注册提供商。",
"imageProviderStatus": "图片生成会复用「提供商」里的凭据。", "imageProviderStatus": "图片生成会复用「提供商」里的凭据。",
+9
View File
@@ -115,6 +115,8 @@
"imageDefaults": "預設值", "imageDefaults": "預設值",
"webSearch": "網路搜尋", "webSearch": "網路搜尋",
"webBehavior": "網路行為", "webBehavior": "網路行為",
"browserAutomation": "瀏覽器自動化",
"computerControl": "電腦控制",
"regional": "區域", "regional": "區域",
"webuiSafety": "WebUI 安全", "webuiSafety": "WebUI 安全",
"capabilities": "能力", "capabilities": "能力",
@@ -145,6 +147,8 @@
"maxResults": "最大結果數", "maxResults": "最大結果數",
"timeout": "逾時", "timeout": "逾時",
"jinaReader": "Jina 閱讀器", "jinaReader": "Jina 閱讀器",
"browserAutomation": "瀏覽器自動化",
"computerControl": "電腦控制",
"imageGeneration": "圖片生成", "imageGeneration": "圖片生成",
"imageProvider": "圖片供應商", "imageProvider": "圖片供應商",
"imageProviderStatus": "供應商狀態", "imageProviderStatus": "供應商狀態",
@@ -188,6 +192,11 @@
"maxResults": "每次呼叫 web_search 所回傳的結果數。", "maxResults": "每次呼叫 web_search 所回傳的結果數。",
"timeout": "搜尋供應商請求逾時前的秒數。", "timeout": "搜尋供應商請求逾時前的秒數。",
"jinaReader": "若可用,則讓 web_fetch 使用 Jina Reader。", "jinaReader": "若可用,則讓 web_fetch 使用 Jina Reader。",
"browserAutomation": "允許 nanobot 透過結構化頁面元素瀏覽網頁並執行操作,需要 Playwright Chromium。",
"computerControl": "允許 nanobot 查看並控制執行引擎的電腦。macOS 需要授予螢幕錄製與輔助使用權限。",
"computerControlBrowser": "像素控制目前依 config.json 設定作用於隔離瀏覽器。",
"computerUseInstall": "開啟時會自動安裝所需 Python 元件。",
"computerUseInstalling": "正在安裝電腦操作元件...",
"imageGeneration": "設定圖片供應商後,即可在聊天中使用 generate_image。", "imageGeneration": "設定圖片供應商後,即可在聊天中使用 generate_image。",
"imageProvider": "選擇 generate_image 使用的註冊供應商。", "imageProvider": "選擇 generate_image 使用的註冊供應商。",
"imageProviderStatus": "圖片生成功能會沿用 [供應商] 中的憑證。", "imageProviderStatus": "圖片生成功能會沿用 [供應商] 中的憑證。",
+19
View File
@@ -7,6 +7,7 @@ import type {
ChannelValidationPayload, ChannelValidationPayload,
ChatSummary, ChatSummary,
CliAppsPayload, CliAppsPayload,
ComputerUseSettingsUpdate,
FilePreviewPayload, FilePreviewPayload,
ImageGenerationSettingsUpdate, ImageGenerationSettingsUpdate,
McpPresetsPayload, McpPresetsPayload,
@@ -1077,6 +1078,24 @@ export async function updateNetworkSafetySettings(
); );
} }
export async function updateComputerUseSettings(
token: string,
update: ComputerUseSettingsUpdate,
base: string = "",
): Promise<SettingsPayload> {
const query = new URLSearchParams();
if (update.browserEnabled !== undefined) {
query.set("browser_enabled", String(update.browserEnabled));
}
if (update.computerEnabled !== undefined) {
query.set("enabled", String(update.computerEnabled));
}
return request<SettingsPayload>(
`${base}/api/settings/computer-use/update?${query}`,
token,
);
}
export async function updateImageGenerationSettings( export async function updateImageGenerationSettings(
token: string, token: string,
update: ImageGenerationSettingsUpdate, update: ImageGenerationSettingsUpdate,
+10
View File
@@ -579,6 +579,11 @@ export interface SettingsPayload {
use_jina_reader: boolean; use_jina_reader: boolean;
}; };
}; };
computer_use?: {
browser_enabled: boolean;
enabled: boolean;
backend: "desktop" | "browser";
};
api?: { api?: {
host: string; host: string;
port: number; port: number;
@@ -1102,6 +1107,11 @@ export interface NetworkSafetySettingsUpdate {
webuiDefaultAccessMode: WebuiDefaultAccessMode; webuiDefaultAccessMode: WebuiDefaultAccessMode;
} }
export interface ComputerUseSettingsUpdate {
browserEnabled?: boolean;
computerEnabled?: boolean;
}
export interface ImageGenerationSettingsUpdate { export interface ImageGenerationSettingsUpdate {
enabled: boolean; enabled: boolean;
provider: string; provider: string;
+15
View File
@@ -46,6 +46,7 @@ import {
pollChannelConnect, pollChannelConnect,
startChannelConnect, startChannelConnect,
updateAutomation, updateAutomation,
updateComputerUseSettings,
updateSidebarState, updateSidebarState,
updateImageGenerationSettings, updateImageGenerationSettings,
updateModelCallOrder, updateModelCallOrder,
@@ -836,6 +837,20 @@ describe("webui API helpers", () => {
); );
}); });
it("updates computer-use capability switches", async () => {
await updateComputerUseSettings("tok", { browserEnabled: true });
expect(fetch).toHaveBeenCalledWith(
"/api/settings/computer-use/update?browser_enabled=true",
expect.objectContaining({ headers: { Authorization: "Bearer tok" } }),
);
await updateComputerUseSettings("tok", { computerEnabled: false });
expect(fetch).toHaveBeenCalledWith(
"/api/settings/computer-use/update?enabled=false",
expect.objectContaining({ headers: { Authorization: "Bearer tok" } }),
);
});
it("manages the API service capability", async () => { it("manages the API service capability", async () => {
await fetchApiService("tok"); await fetchApiService("tok");
expect(fetch).toHaveBeenCalledWith( expect(fetch).toHaveBeenCalledWith(
+9
View File
@@ -69,6 +69,8 @@ const LOCALIZED_SETTINGS_COPY_KEYS = [
"settings.sections.localPreferences", "settings.sections.localPreferences",
"settings.sections.webSearch", "settings.sections.webSearch",
"settings.sections.webBehavior", "settings.sections.webBehavior",
"settings.sections.browserAutomation",
"settings.sections.computerControl",
"settings.sections.webuiSafety", "settings.sections.webuiSafety",
"settings.sections.capabilities", "settings.sections.capabilities",
"settings.sections.apps", "settings.sections.apps",
@@ -149,6 +151,8 @@ const LOCALIZED_SETTINGS_COPY_KEYS = [
"settings.rows.fileEditDisplay", "settings.rows.fileEditDisplay",
"settings.rows.codeWrap", "settings.rows.codeWrap",
"settings.rows.brandLogos", "settings.rows.brandLogos",
"settings.rows.browserAutomation",
"settings.rows.computerControl",
"settings.rows.currentModel", "settings.rows.currentModel",
"settings.rows.localServiceAccess", "settings.rows.localServiceAccess",
"settings.rows.webuiDefaultAccess", "settings.rows.webuiDefaultAccess",
@@ -160,6 +164,11 @@ const LOCALIZED_SETTINGS_COPY_KEYS = [
"settings.help.fileEditDisplay", "settings.help.fileEditDisplay",
"settings.help.codeWrap", "settings.help.codeWrap",
"settings.help.brandLogos", "settings.help.brandLogos",
"settings.help.browserAutomation",
"settings.help.computerControl",
"settings.help.computerControlBrowser",
"settings.help.computerUseInstall",
"settings.help.computerUseInstalling",
"settings.help.currentModel", "settings.help.currentModel",
"settings.help.localServiceAccess", "settings.help.localServiceAccess",
"settings.help.webuiDefaultAccess", "settings.help.webuiDefaultAccess",
+97
View File
@@ -64,6 +64,11 @@ function settingsPayload(): SettingsPayload {
search: { max_results: 5, timeout: 30 }, search: { max_results: 5, timeout: 30 },
fetch: { use_jina_reader: true }, fetch: { use_jina_reader: true },
}, },
computer_use: {
browser_enabled: false,
enabled: false,
backend: "desktop",
},
api: { api: {
host: "127.0.0.1", host: "127.0.0.1",
port: 8900, port: 8900,
@@ -4202,6 +4207,98 @@ describe("SettingsView Apps catalog", () => {
}); });
}); });
it("enables browser automation from Web settings", async () => {
const payload = settingsPayload();
const updatedPayload: SettingsPayload = {
...payload,
computer_use: { ...payload.computer_use!, browser_enabled: true },
requires_restart: true,
restart_required_sections: ["runtime"],
};
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url === "/api/settings") return jsonResponse(payload);
if (url === "/api/settings/nanobot-features") {
return jsonResponse({
features: [{
name: "computer-use",
display_name: "Computer Use",
type: "feature",
enabled: true,
installed: true,
ready: true,
status: "enabled",
install_supported: true,
requires_restart: true,
}],
enabled_count: 1,
});
}
if (url === "/api/settings/computer-use/update?browser_enabled=true") {
return jsonResponse(updatedPayload);
}
return { ok: false, status: 404, json: async () => ({}) } as Response;
});
vi.stubGlobal("fetch", fetchMock);
renderSettingsView({ initialSection: "browser" });
fireEvent.click(await screen.findByRole("switch", { name: "Browser automation" }));
await waitFor(() =>
expect(fetchMock).toHaveBeenCalledWith(
"/api/settings/computer-use/update?browser_enabled=true",
expect.objectContaining({ headers: { Authorization: "Bearer tok" } }),
),
);
});
it("enables computer control from Security settings", async () => {
const payload = settingsPayload();
const updatedPayload: SettingsPayload = {
...payload,
computer_use: { ...payload.computer_use!, enabled: true },
requires_restart: true,
restart_required_sections: ["runtime"],
};
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url === "/api/settings") return jsonResponse(payload);
if (url === "/api/settings/nanobot-features") {
return jsonResponse({
features: [{
name: "computer-use",
display_name: "Computer Use",
type: "feature",
enabled: true,
installed: true,
ready: true,
status: "enabled",
install_supported: true,
requires_restart: true,
}],
enabled_count: 1,
});
}
if (url === "/api/settings/computer-use/update?enabled=true") {
return jsonResponse(updatedPayload);
}
return { ok: false, status: 404, json: async () => ({}) } as Response;
});
vi.stubGlobal("fetch", fetchMock);
renderSettingsView({ initialSection: "advanced" });
fireEvent.click(await screen.findByRole("switch", { name: "Computer control" }));
await waitFor(() =>
expect(fetchMock).toHaveBeenCalledWith(
"/api/settings/computer-use/update?enabled=true",
expect.objectContaining({ headers: { Authorization: "Bearer tok" } }),
),
);
});
it("saves network safety without exposing technical SSRF copy", async () => { it("saves network safety without exposing technical SSRF copy", async () => {
const payload = settingsPayload(); const payload = settingsPayload();
const fetchMock = vi.fn(async (input: RequestInfo | URL) => { const fetchMock = vi.fn(async (input: RequestInfo | URL) => {