refactor settings frontend for new data structure

Updated the settings UI to work with the new backend format that separates settings values from additional metadata. - Settings components now read from the new structure (settings + additional) - Removed legacy field-based rendering in favor of direct value binding - Root passwd masking - Added knowledge subdirectory selector support - Enhanced backend `convert_out()`: now ensures current values are always present in dropdown options, preventing UI glitches - Cleaned up old conversion functions marked for removal

3clyp50 committed Dec 26, 2025 at 04:59 UTC 78e6f74a21ce69b8ab2bf88373d057943425926f
32 files changed +1279 -3752
python/helpers/settings.py
+41 -1159
@@ -195,6 +195,7 @@ class SettingsOutputAdditional(TypedDict):
195 embedding_providers: list[ModelProvider]
196 shell_interfaces: list[FieldOption]
197 agent_subdirs: list[FieldOption]
198 + knowledge_subdirs: list[FieldOption]
199 stt_models: list[FieldOption]
200 is_dockerized: bool
201
@@ -209,6 +210,22 @@ API_KEY_PLACEHOLDER = "************"
210 SETTINGS_FILE = files.get_abs_path("tmp/settings.json")
211 _settings: Settings | None = None
212
213 +OptionT = TypeVar("OptionT", bound=FieldOption)
214 +
215 +def _ensure_option_present(options: list[OptionT] | None, current_value: str | None) -> list[OptionT]:
216 + """
217 + Ensure the currently selected value exists in a dropdown options list.
218 + If missing, inserts it at the front as {value: current_value, label: current_value}.
219 + """
220 + opts = list(options or [])
221 + if not current_value:
222 + return opts
223 + for o in opts:
224 + if o.get("value") == current_value:
225 + return opts
226 + opts.insert(0, cast(OptionT, {"value": current_value, "label": current_value}))
227 + return opts
228 +
229 def convert_out(settings: Settings) -> SettingsOutput:
230 out = SettingsOutput(
231 settings = settings.copy(),
@@ -220,6 +237,8 @@ def convert_out(settings: Settings) -> SettingsOutput:
237 agent_subdirs=[{"value": subdir, "label": subdir}
238 for subdir in files.get_subdirectories("agents")
239 if subdir != "_example"],
240 + knowledge_subdirs=[{"value": subdir, "label": subdir}
241 + for subdir in files.get_subdirectories("knowledge", exclude="default")],
242 stt_models=[
243 {"value": "tiny", "label": "Tiny (39M, English)"},
244 {"value": "base", "label": "Base (74M, English)"},
@@ -232,6 +251,19 @@ def convert_out(settings: Settings) -> SettingsOutput:
251 )
252 )
253
254 + # ensure dropdown options include currently selected values
255 + additional = out["additional"]
256 + current = out["settings"]
257 +
258 + additional["chat_providers"] = _ensure_option_present(additional.get("chat_providers"), current.get("chat_model_provider"))
259 + additional["chat_providers"] = _ensure_option_present(additional.get("chat_providers"), current.get("util_model_provider"))
260 + additional["chat_providers"] = _ensure_option_present(additional.get("chat_providers"), current.get("browser_model_provider"))
261 + additional["embedding_providers"] = _ensure_option_present(additional.get("embedding_providers"), current.get("embed_model_provider"))
262 + additional["shell_interfaces"] = _ensure_option_present(additional.get("shell_interfaces"), current.get("shell_interface"))
263 + additional["agent_subdirs"] = _ensure_option_present(additional.get("agent_subdirs"), current.get("agent_profile"))
264 + additional["knowledge_subdirs"] = _ensure_option_present(additional.get("knowledge_subdirs"), current.get("agent_knowledge_subdir"))
265 + additional["stt_models"] = _ensure_option_present(additional.get("stt_models"), current.get("stt_model_size"))
266 +
267 # load auth from dotenv
268 out["settings"]["auth_login"] = dotenv.get_dotenv_value(dotenv.KEY_AUTH_LOGIN) or ""
269 out["settings"]["auth_password"] = (
@@ -240,6 +272,9 @@ def convert_out(settings: Settings) -> SettingsOutput:
272 out["settings"]["rfc_password"] = (
273 PASSWORD_PLACEHOLDER if dotenv.get_dotenv_value(dotenv.KEY_RFC_PASSWORD) else ""
274 )
275 + out["settings"]["root_password"] = (
276 + PASSWORD_PLACEHOLDER if dotenv.get_dotenv_value(dotenv.KEY_ROOT_PASSWORD) else ""
277 + )
278
279 #secrets
280 secrets_manager = get_default_secrets_manager()
@@ -248,6 +283,12 @@ def convert_out(settings: Settings) -> SettingsOutput:
283 except Exception:
284 out["settings"]["secrets"] = ""
285
286 + # mask API keys before sending to frontend
287 + if isinstance(out["settings"].get("api_keys"), dict):
288 + for provider, value in list(out["settings"]["api_keys"].items()):
289 + if value:
290 + out["settings"]["api_keys"][provider] = API_KEY_PLACEHOLDER
291 +
292 # normalize certain fields
293 for key, value in list(out["settings"].items()):
294 # convert kwargs dicts to .env format
@@ -256,1143 +297,6 @@ def convert_out(settings: Settings) -> SettingsOutput:
297 return out
298
299
259 -#TODO just for reference, remove
260 -def _convert_out_old(settings: Settings) -> SettingsOutput:
261 - default_settings = get_default_settings()
262 -
263 - # main model section
264 - chat_model_fields: list[SettingsField] = []
265 - chat_model_fields.append(
266 - {
267 - "id": "chat_model_provider",
268 - "title": "Chat model provider",
269 - "description": "Select provider for main chat model used by Agent Zero",
270 - "type": "select",
271 - "value": settings["chat_model_provider"],
272 - "options": cast(list[FieldOption], get_providers("chat")),
273 - }
274 - )
275 - chat_model_fields.append(
276 - {
277 - "id": "chat_model_name",
278 - "title": "Chat model name",
279 - "description": "Exact name of model from selected provider",
280 - "type": "text",
281 - "value": settings["chat_model_name"],
282 - }
283 - )
284 -
285 - chat_model_fields.append(
286 - {
287 - "id": "chat_model_api_base",
288 - "title": "Chat model API base URL",
289 - "description": "API base URL for main chat model. Leave empty for default. Only relevant for Azure, local and custom (other) providers.",
290 - "type": "text",
291 - "value": settings["chat_model_api_base"],
292 - }
293 - )
294 -
295 - chat_model_fields.append(
296 - {
297 - "id": "chat_model_ctx_length",
298 - "title": "Chat model context length",
299 - "description": "Maximum number of tokens in the context window for LLM. System prompt, chat history, RAG and response all count towards this limit.",
300 - "type": "number",
301 - "value": settings["chat_model_ctx_length"],
302 - }
303 - )
304 -
305 - chat_model_fields.append(
306 - {
307 - "id": "chat_model_ctx_history",
308 - "title": "Context window space for chat history",
309 - "description": "Portion of context window dedicated to chat history visible to the agent. Chat history will automatically be optimized to fit. Smaller size will result in shorter and more summarized history. The remaining space will be used for system prompt, RAG and response.",
310 - "type": "range",
311 - "min": 0.01,
312 - "max": 1,
313 - "step": 0.01,
314 - "value": settings["chat_model_ctx_history"],
315 - }
316 - )
317 -
318 - chat_model_fields.append(
319 - {
320 - "id": "chat_model_vision",
321 - "title": "Supports Vision",
322 - "description": "Models capable of Vision can for example natively see the content of image attachments.",
323 - "type": "switch",
324 - "value": settings["chat_model_vision"],
325 - }
326 - )
327 -
328 - chat_model_fields.append(
329 - {
330 - "id": "chat_model_rl_requests",
331 - "title": "Requests per minute limit",
332 - "description": "Limits the number of requests per minute to the chat model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.",
333 - "type": "number",
334 - "value": settings["chat_model_rl_requests"],
335 - }
336 - )
337 -
338 - chat_model_fields.append(
339 - {
340 - "id": "chat_model_rl_input",
341 - "title": "Input tokens per minute limit",
342 - "description": "Limits the number of input tokens per minute to the chat model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.",
343 - "type": "number",
344 - "value": settings["chat_model_rl_input"],
345 - }
346 - )
347 -
348 - chat_model_fields.append(
349 - {
350 - "id": "chat_model_rl_output",
351 - "title": "Output tokens per minute limit",
352 - "description": "Limits the number of output tokens per minute to the chat model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.",
353 - "type": "number",
354 - "value": settings["chat_model_rl_output"],
355 - }
356 - )
357 -
358 - chat_model_fields.append(
359 - {
360 - "id": "chat_model_kwargs",
361 - "title": "Chat model additional parameters",
362 - "description": "Any other parameters supported by <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a>. Format is KEY=VALUE on individual lines, like .env file. Value can also contain JSON objects - when unquoted, it is treated as object, number etc., when quoted, it is treated as string.",
363 - "type": "textarea",
364 - "value": _dict_to_env(settings["chat_model_kwargs"]),
365 - }
366 - )
367 -
368 - chat_model_section: SettingsSection = {
369 - "id": "chat_model",
370 - "title": "Chat Model",
371 - "description": "Selection and settings for main chat model used by Agent Zero",
372 - "fields": chat_model_fields,
373 - "tab": "agent",
374 - }
375 -
376 - # main model section
377 - util_model_fields: list[SettingsField] = []
378 - util_model_fields.append(
379 - {
380 - "id": "util_model_provider",
381 - "title": "Utility model provider",
382 - "description": "Select provider for utility model used by the framework",
383 - "type": "select",
384 - "value": settings["util_model_provider"],
385 - "options": cast(list[FieldOption], get_providers("chat")),
386 - }
387 - )
388 - util_model_fields.append(
389 - {
390 - "id": "util_model_name",
391 - "title": "Utility model name",
392 - "description": "Exact name of model from selected provider",
393 - "type": "text",
394 - "value": settings["util_model_name"],
395 - }
396 - )
397 -
398 - util_model_fields.append(
399 - {
400 - "id": "util_model_api_base",
401 - "title": "Utility model API base URL",
402 - "description": "API base URL for utility model. Leave empty for default. Only relevant for Azure, local and custom (other) providers.",
403 - "type": "text",
404 - "value": settings["util_model_api_base"],
405 - }
406 - )
407 -
408 - util_model_fields.append(
409 - {
410 - "id": "util_model_rl_requests",
411 - "title": "Requests per minute limit",
412 - "description": "Limits the number of requests per minute to the utility model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.",
413 - "type": "number",
414 - "value": settings["util_model_rl_requests"],
415 - }
416 - )
417 -
418 - util_model_fields.append(
419 - {
420 - "id": "util_model_rl_input",
421 - "title": "Input tokens per minute limit",
422 - "description": "Limits the number of input tokens per minute to the utility model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.",
423 - "type": "number",
424 - "value": settings["util_model_rl_input"],
425 - }
426 - )
427 -
428 - util_model_fields.append(
429 - {
430 - "id": "util_model_rl_output",
431 - "title": "Output tokens per minute limit",
432 - "description": "Limits the number of output tokens per minute to the utility model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.",
433 - "type": "number",
434 - "value": settings["util_model_rl_output"],
435 - }
436 - )
437 -
438 - util_model_fields.append(
439 - {
440 - "id": "util_model_kwargs",
441 - "title": "Utility model additional parameters",
442 - "description": "Any other parameters supported by <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a>. Format is KEY=VALUE on individual lines, like .env file. Value can also contain JSON objects - when unquoted, it is treated as object, number etc., when quoted, it is treated as string.",
443 - "type": "textarea",
444 - "value": _dict_to_env(settings["util_model_kwargs"]),
445 - }
446 - )
447 -
448 - util_model_section: SettingsSection = {
449 - "id": "util_model",
450 - "title": "Utility model",
451 - "description": "Smaller, cheaper, faster model for handling utility tasks like organizing memory, preparing prompts, summarizing.",
452 - "fields": util_model_fields,
453 - "tab": "agent",
454 - }
455 -
456 - # embedding model section
457 - embed_model_fields: list[SettingsField] = []
458 - embed_model_fields.append(
459 - {
460 - "id": "embed_model_provider",
461 - "title": "Embedding model provider",
462 - "description": "Select provider for embedding model used by the framework",
463 - "type": "select",
464 - "value": settings["embed_model_provider"],
465 - "options": cast(list[FieldOption], get_providers("embedding")),
466 - }
467 - )
468 - embed_model_fields.append(
469 - {
470 - "id": "embed_model_name",
471 - "title": "Embedding model name",
472 - "description": "Exact name of model from selected provider",
473 - "type": "text",
474 - "value": settings["embed_model_name"],
475 - }
476 - )
477 -
478 - embed_model_fields.append(
479 - {
480 - "id": "embed_model_api_base",
481 - "title": "Embedding model API base URL",
482 - "description": "API base URL for embedding model. Leave empty for default. Only relevant for Azure, local and custom (other) providers.",
483 - "type": "text",
484 - "value": settings["embed_model_api_base"],
485 - }
486 - )
487 -
488 - embed_model_fields.append(
489 - {
490 - "id": "embed_model_rl_requests",
491 - "title": "Requests per minute limit",
492 - "description": "Limits the number of requests per minute to the embedding model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.",
493 - "type": "number",
494 - "value": settings["embed_model_rl_requests"],
495 - }
496 - )
497 -
498 - embed_model_fields.append(
499 - {
500 - "id": "embed_model_rl_input",
501 - "title": "Input tokens per minute limit",
502 - "description": "Limits the number of input tokens per minute to the embedding model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.",
503 - "type": "number",
504 - "value": settings["embed_model_rl_input"],
505 - }
506 - )
507 -
508 - embed_model_fields.append(
509 - {
510 - "id": "embed_model_kwargs",
511 - "title": "Embedding model additional parameters",
512 - "description": "Any other parameters supported by <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a>. Format is KEY=VALUE on individual lines, like .env file. Value can also contain JSON objects - when unquoted, it is treated as object, number etc., when quoted, it is treated as string.",
513 - "type": "textarea",
514 - "value": _dict_to_env(settings["embed_model_kwargs"]),
515 - }
516 - )
517 -
518 - embed_model_section: SettingsSection = {
519 - "id": "embed_model",
520 - "title": "Embedding Model",
521 - "description": f"Settings for the embedding model used by Agent Zero.<br><h4>⚠️ No need to change</h4>The default HuggingFace model {default_settings['embed_model_name']} is preloaded and runs locally within the docker container and there's no need to change it unless you have a specific requirements for embedding.",
522 - "fields": embed_model_fields,
523 - "tab": "agent",
524 - }
525 -
526 - # embedding model section
527 - browser_model_fields: list[SettingsField] = []
528 - browser_model_fields.append(
529 - {
530 - "id": "browser_model_provider",
531 - "title": "Web Browser model provider",
532 - "description": "Select provider for web browser model used by <a href='https://github.com/browser-use/browser-use' target='_blank'>browser-use</a> framework",
533 - "type": "select",
534 - "value": settings["browser_model_provider"],
535 - "options": cast(list[FieldOption], get_providers("chat")),
536 - }
537 - )
538 - browser_model_fields.append(
539 - {
540 - "id": "browser_model_name",
541 - "title": "Web Browser model name",
542 - "description": "Exact name of model from selected provider",
543 - "type": "text",
544 - "value": settings["browser_model_name"],
545 - }
546 - )
547 -
548 - browser_model_fields.append(
549 - {
550 - "id": "browser_model_api_base",
551 - "title": "Web Browser model API base URL",
552 - "description": "API base URL for web browser model. Leave empty for default. Only relevant for Azure, local and custom (other) providers.",
553 - "type": "text",
554 - "value": settings["browser_model_api_base"],
555 - }
556 - )
557 -
558 - browser_model_fields.append(
559 - {
560 - "id": "browser_model_vision",
561 - "title": "Use Vision",
562 - "description": "Models capable of Vision can use it to analyze web pages from screenshots. Increases quality but also token usage.",
563 - "type": "switch",
564 - "value": settings["browser_model_vision"],
565 - }
566 - )
567 -
568 - browser_model_fields.append(
569 - {
570 - "id": "browser_model_rl_requests",
571 - "title": "Web Browser model rate limit requests",
572 - "description": "Rate limit requests for web browser model.",
573 - "type": "number",
574 - "value": settings["browser_model_rl_requests"],
575 - }
576 - )
577 -
578 - browser_model_fields.append(
579 - {
580 - "id": "browser_model_rl_input",
581 - "title": "Web Browser model rate limit input",
582 - "description": "Rate limit input for web browser model.",
583 - "type": "number",
584 - "value": settings["browser_model_rl_input"],
585 - }
586 - )
587 -
588 - browser_model_fields.append(
589 - {
590 - "id": "browser_model_rl_output",
591 - "title": "Web Browser model rate limit output",
592 - "description": "Rate limit output for web browser model.",
593 - "type": "number",
594 - "value": settings["browser_model_rl_output"],
595 - }
596 - )
597 -
598 - browser_model_fields.append(
599 - {
600 - "id": "browser_model_kwargs",
601 - "title": "Web Browser model additional parameters",
602 - "description": "Any other parameters supported by <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a>. Format is KEY=VALUE on individual lines, like .env file. Value can also contain JSON objects - when unquoted, it is treated as object, number etc., when quoted, it is treated as string.",
603 - "type": "textarea",
604 - "value": _dict_to_env(settings["browser_model_kwargs"]),
605 - }
606 - )
607 -
608 - browser_model_fields.append(
609 - {
610 - "id": "browser_http_headers",
611 - "title": "HTTP Headers",
612 - "description": "HTTP headers to include with all browser requests. Format is KEY=VALUE on individual lines, like .env file. Value can also contain JSON objects - when unquoted, it is treated as object, number etc., when quoted, it is treated as string. Example: Authorization=Bearer token123",
613 - "type": "textarea",
614 - "value": _dict_to_env(settings.get("browser_http_headers", {})),
615 - }
616 - )
617 -
618 - browser_model_section: SettingsSection = {
619 - "id": "browser_model",
620 - "title": "Web Browser Model",
621 - "description": "Settings for the web browser model. Agent Zero uses <a href='https://github.com/browser-use/browser-use' target='_blank'>browser-use</a> agentic framework to handle web interactions.",
622 - "fields": browser_model_fields,
623 - "tab": "agent",
624 - }
625 -
626 - # basic auth section
627 - auth_fields: list[SettingsField] = []
628 -
629 - auth_fields.append(
630 - {
631 - "id": "auth_login",
632 - "title": "UI Login",
633 - "description": "Set user name for web UI",
634 - "type": "text",
635 - "value": dotenv.get_dotenv_value(dotenv.KEY_AUTH_LOGIN) or "",
636 - }
637 - )
638 -
639 - auth_fields.append(
640 - {
641 - "id": "auth_password",
642 - "title": "UI Password",
643 - "description": "Set user password for web UI",
644 - "type": "password",
645 - "value": (
646 - PASSWORD_PLACEHOLDER
647 - if dotenv.get_dotenv_value(dotenv.KEY_AUTH_PASSWORD)
648 - else ""
649 - ),
650 - }
651 - )
652 -
653 - if runtime.is_dockerized():
654 - auth_fields.append(
655 - {
656 - "id": "root_password",
657 - "title": "root Password",
658 - "description": "Change linux root password in docker container. This password can be used for SSH access. Original password was randomly generated during setup.",
659 - "type": "password",
660 - "value": "",
661 - }
662 - )
663 -
664 - auth_section: SettingsSection = {
665 - "id": "auth",
666 - "title": "Authentication",
667 - "description": "Settings for authentication to use Agent Zero Web UI.",
668 - "fields": auth_fields,
669 - "tab": "external",
670 - }
671 -
672 - # api keys model section
673 - api_keys_fields: list[SettingsField] = []
674 -
675 - # Collect unique providers from both chat and embedding sections
676 - providers_seen: set[str] = set()
677 - for p_type in ("chat", "embedding"):
678 - for provider in get_providers(p_type):
679 - pid_lower = provider["value"].lower()
680 - if pid_lower in providers_seen:
681 - continue
682 - providers_seen.add(pid_lower)
683 - api_keys_fields.append(
684 - _get_api_key_field(settings, pid_lower, provider["label"])
685 - )
686 -
687 - api_keys_section: SettingsSection = {
688 - "id": "api_keys",
689 - "title": "API Keys",
690 - "description": "API keys for model providers and services used by Agent Zero. You can set multiple API keys separated by a comma (,). They will be used in round-robin fashion.<br>For more information abou Agent Zero Venice provider, see <a href='http://agent-zero.ai/?community/api-dashboard/about' target='_blank'>Agent Zero Venice</a>.",
691 - "fields": api_keys_fields,
692 - "tab": "external",
693 - }
694 -
695 - # LiteLLM global config section
696 - litellm_fields: list[SettingsField] = []
697 -
698 - litellm_fields.append(
699 - {
700 - "id": "litellm_global_kwargs",
701 - "title": "LiteLLM global parameters",
702 - "description": "Global LiteLLM params (e.g. timeout, stream_timeout) in .env format: one KEY=VALUE per line. Example: <code>stream_timeout=30</code>. Applied to all LiteLLM calls unless overridden. See <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a> and <a href='https://docs.litellm.ai/docs/proxy/timeout' target='_blank'>timeouts</a>.",
703 - "type": "textarea",
704 - "value": _dict_to_env(settings["litellm_global_kwargs"]),
705 - "style": "height: 12em",
706 - }
707 - )
708 -
709 - litellm_section: SettingsSection = {
710 - "id": "litellm",
711 - "title": "LiteLLM Global Settings",
712 - "description": "Configure global parameters passed to LiteLLM for all providers.",
713 - "fields": litellm_fields,
714 - "tab": "external",
715 - }
716 -
717 - # Agent config section
718 - agent_fields: list[SettingsField] = []
719 -
720 - agent_fields.append(
721 - {
722 - "id": "agent_profile",
723 - "title": "Default agent profile",
724 - "description": "Subdirectory of /agents folder to be used by default agent no. 0. Subordinate agents can be spawned with other profiles, that is on their superior agent to decide. This setting affects the behaviour of the top level agent you communicate with.",
725 - "type": "select",
726 - "value": settings["agent_profile"],
727 - "options": [
728 - {"value": subdir, "label": subdir}
729 - for subdir in files.get_subdirectories("agents")
730 - if subdir != "_example"
731 - ],
732 - }
733 - )
734 -
735 - agent_fields.append(
736 - {
737 - "id": "agent_knowledge_subdir",
738 - "title": "Knowledge subdirectory",
739 - "description": "Subdirectory of /knowledge folder to use for agent knowledge import. 'default' subfolder is always imported and contains framework knowledge.",
740 - "type": "select",
741 - "value": settings["agent_knowledge_subdir"],
742 - "options": [
743 - {"value": subdir, "label": subdir}
744 - for subdir in files.get_subdirectories("knowledge", exclude="default")
745 - ],
746 - }
747 - )
748 -
749 - agent_section: SettingsSection = {
750 - "id": "agent",
751 - "title": "Agent Config",
752 - "description": "Agent parameters.",
753 - "fields": agent_fields,
754 - "tab": "agent",
755 - }
756 -
757 - memory_fields: list[SettingsField] = []
758 -
759 - memory_fields.append(
760 - {
761 - "id": "agent_memory_subdir",
762 - "title": "Memory Subdirectory",
763 - "description": "Subdirectory of /memory folder to use for agent memory storage. Used to separate memory storage between different instances.",
764 - "type": "text",
765 - "value": settings["agent_memory_subdir"],
766 - # "options": [
767 - # {"value": subdir, "label": subdir}
768 - # for subdir in files.get_subdirectories("memory", exclude="embeddings")
769 - # ],
770 - }
771 - )
772 -
773 - memory_fields.append(
774 - {
775 - "id": "memory_dashboard",
776 - "title": "Memory Dashboard",
777 - "description": "View and explore all stored memories in a table format with filtering and search capabilities.",
778 - "type": "button",
779 - "value": "Open Dashboard",
780 - }
781 - )
782 -
783 - memory_fields.append(
784 - {
785 - "id": "memory_recall_enabled",
786 - "title": "Memory auto-recall enabled",
787 - "description": "Agent Zero will automatically recall memories based on convesation context.",
788 - "type": "switch",
789 - "value": settings["memory_recall_enabled"],
790 - }
791 - )
792 -
793 - memory_fields.append(
794 - {
795 - "id": "memory_recall_delayed",
796 - "title": "Memory auto-recall delayed",
797 - "description": "The agent will not wait for auto memory recall. Memories will be delivered one message later. This speeds up agent's response time but may result in less relevant first step.",
798 - "type": "switch",
799 - "value": settings["memory_recall_delayed"],
800 - }
801 - )
802 -
803 - memory_fields.append(
804 - {
805 - "id": "memory_recall_query_prep",
806 - "title": "Auto-recall AI query preparation",
807 - "description": "Enables vector DB query preparation from conversation context by utility LLM for auto-recall. Improves search quality, adds 1 utility LLM call per auto-recall.",
808 - "type": "switch",
809 - "value": settings["memory_recall_query_prep"],
810 - }
811 - )
812 -
813 - memory_fields.append(
814 - {
815 - "id": "memory_recall_post_filter",
816 - "title": "Auto-recall AI post-filtering",
817 - "description": "Enables memory relevance filtering by utility LLM for auto-recall. Improves search quality, adds 1 utility LLM call per auto-recall.",
818 - "type": "switch",
819 - "value": settings["memory_recall_post_filter"],
820 - }
821 - )
822 -
823 - memory_fields.append(
824 - {
825 - "id": "memory_recall_interval",
826 - "title": "Memory auto-recall interval",
827 - "description": "Memories are recalled after every user or superior agent message. During agent's monologue, memories are recalled every X turns based on this parameter.",
828 - "type": "range",
829 - "min": 1,
830 - "max": 10,
831 - "step": 1,
832 - "value": settings["memory_recall_interval"],
833 - }
834 - )
835 -
836 - memory_fields.append(
837 - {
838 - "id": "memory_recall_history_len",
839 - "title": "Memory auto-recall history length",
840 - "description": "The length of conversation history passed to memory recall LLM for context (in characters).",
841 - "type": "number",
842 - "value": settings["memory_recall_history_len"],
843 - }
844 - )
845 -
846 - memory_fields.append(
847 - {
848 - "id": "memory_recall_similarity_threshold",
849 - "title": "Memory auto-recall similarity threshold",
850 - "description": "The threshold for similarity search in memory recall (0 = no similarity, 1 = exact match).",
851 - "type": "range",
852 - "min": 0,
853 - "max": 1,
854 - "step": 0.01,
855 - "value": settings["memory_recall_similarity_threshold"],
856 - }
857 - )
858 -
859 - memory_fields.append(
860 - {
861 - "id": "memory_recall_memories_max_search",
862 - "title": "Memory auto-recall max memories to search",
863 - "description": "The maximum number of memories returned by vector DB for further processing.",
864 - "type": "number",
865 - "value": settings["memory_recall_memories_max_search"],
866 - }
867 - )
868 -
869 - memory_fields.append(
870 - {
871 - "id": "memory_recall_memories_max_result",
872 - "title": "Memory auto-recall max memories to use",
873 - "description": "The maximum number of memories to inject into A0's context window.",
874 - "type": "number",
875 - "value": settings["memory_recall_memories_max_result"],
876 - }
877 - )
878 -
879 - memory_fields.append(
880 - {
881 - "id": "memory_recall_solutions_max_search",
882 - "title": "Memory auto-recall max solutions to search",
883 - "description": "The maximum number of solutions returned by vector DB for further processing.",
884 - "type": "number",
885 - "value": settings["memory_recall_solutions_max_search"],
886 - }
887 - )
888 -
889 - memory_fields.append(
890 - {
891 - "id": "memory_recall_solutions_max_result",
892 - "title": "Memory auto-recall max solutions to use",
893 - "description": "The maximum number of solutions to inject into A0's context window.",
894 - "type": "number",
895 - "value": settings["memory_recall_solutions_max_result"],
896 - }
897 - )
898 -
899 - memory_fields.append(
900 - {
901 - "id": "memory_memorize_enabled",
902 - "title": "Auto-memorize enabled",
903 - "description": "A0 will automatically memorize facts and solutions from conversation history.",
904 - "type": "switch",
905 - "value": settings["memory_memorize_enabled"],
906 - }
907 - )
908 -
909 - memory_fields.append(
910 - {
911 - "id": "memory_memorize_consolidation",
912 - "title": "Auto-memorize AI consolidation",
913 - "description": "A0 will automatically consolidate similar memories using utility LLM. Improves memory quality over time, adds 2 utility LLM calls per memory.",
914 - "type": "switch",
915 - "value": settings["memory_memorize_consolidation"],
916 - }
917 - )
918 -
919 - memory_fields.append(
920 - {
921 - "id": "memory_memorize_replace_threshold",
922 - "title": "Auto-memorize replacement threshold",
923 - "description": "Only applies when AI consolidation is disabled. Replaces previous similar memories with new ones based on this threshold. 0 = replace even if not similar at all, 1 = replace only if exact match.",
924 - "type": "range",
925 - "min": 0,
926 - "max": 1,
927 - "step": 0.01,
928 - "value": settings["memory_memorize_replace_threshold"],
929 - }
930 - )
931 -
932 - memory_section: SettingsSection = {
933 - "id": "memory",
934 - "title": "Memory",
935 - "description": "Configuration of A0's memory system. A0 memorizes and recalls memories automatically to help it's context awareness.",
936 - "fields": memory_fields,
937 - "tab": "agent",
938 - }
939 -
940 - dev_fields: list[SettingsField] = []
941 -
942 - dev_fields.append(
943 - {
944 - "id": "shell_interface",
945 - "title": "Shell Interface",
946 - "description": "Terminal interface used for Code Execution Tool. Local Python TTY works locally in both dockerized and development environments. SSH always connects to dockerized environment (automatically at localhost or RFC host address).",
947 - "type": "select",
948 - "value": settings["shell_interface"],
949 - "options": [{"value": "local", "label": "Local Python TTY"}, {"value": "ssh", "label": "SSH"}],
950 - }
951 - )
952 -
953 - if runtime.is_development():
954 - # dev_fields.append(
955 - # {
956 - # "id": "rfc_auto_docker",
957 - # "title": "RFC Auto Docker Management",
958 - # "description": "Automatically create dockerized instance of A0 for RFCs using this instance's code base and, settings and .env.",
959 - # "type": "text",
960 - # "value": settings["rfc_auto_docker"],
961 - # }
962 - # )
963 -
964 - dev_fields.append(
965 - {
966 - "id": "rfc_url",
967 - "title": "RFC Destination URL",
968 - "description": "URL of dockerized A0 instance for remote function calls. Do not specify port here.",
969 - "type": "text",
970 - "value": settings["rfc_url"],
971 - }
972 - )
973 -
974 - dev_fields.append(
975 - {
976 - "id": "rfc_password",
977 - "title": "RFC Password",
978 - "description": "Password for remote function calls. Passwords must match on both instances. RFCs can not be used with empty password.",
979 - "type": "password",
980 - "value": (
981 - PASSWORD_PLACEHOLDER
982 - if dotenv.get_dotenv_value(dotenv.KEY_RFC_PASSWORD)
983 - else ""
984 - ),
985 - }
986 - )
987 -
988 - if runtime.is_development():
989 - dev_fields.append(
990 - {
991 - "id": "rfc_port_http",
992 - "title": "RFC HTTP port",
993 - "description": "HTTP port for dockerized instance of A0.",
994 - "type": "text",
995 - "value": settings["rfc_port_http"],
996 - }
997 - )
998 -
999 - dev_fields.append(
1000 - {
1001 - "id": "rfc_port_ssh",
1002 - "title": "RFC SSH port",
1003 - "description": "SSH port for dockerized instance of A0.",
1004 - "type": "text",
1005 - "value": settings["rfc_port_ssh"],
1006 - }
1007 - )
1008 -
1009 - dev_section: SettingsSection = {
1010 - "id": "dev",
1011 - "title": "Development",
1012 - "description": "Parameters for A0 framework development. RFCs (remote function calls) are used to call functions on another A0 instance. You can develop and debug A0 natively on your local system while redirecting some functions to A0 instance in docker. This is crucial for development as A0 needs to run in standardized environment to support all features.",
1013 - "fields": dev_fields,
1014 - "tab": "developer",
1015 - }
1016 -
1017 - # code_exec_fields: list[SettingsField] = []
1018 -
1019 - # code_exec_fields.append(
1020 - # {
1021 - # "id": "code_exec_ssh_enabled",
1022 - # "title": "Use SSH for code execution",
1023 - # "description": "Code execution will use SSH to connect to the terminal. When disabled, a local python terminal interface is used instead. SSH should only be used in development environment or when encountering issues with the local python terminal interface.",
1024 - # "type": "switch",
1025 - # "value": settings["code_exec_ssh_enabled"],
1026 - # }
1027 - # )
1028 -
1029 - # code_exec_fields.append(
1030 - # {
1031 - # "id": "code_exec_ssh_addr",
1032 - # "title": "Code execution SSH address",
1033 - # "description": "Address of the SSH server for code execution. Only applies when SSH is enabled.",
1034 - # "type": "text",
1035 - # "value": settings["code_exec_ssh_addr"],
1036 - # }
1037 - # )
1038 -
1039 - # code_exec_fields.append(
1040 - # {
1041 - # "id": "code_exec_ssh_port",
1042 - # "title": "Code execution SSH port",
1043 - # "description": "Port of the SSH server for code execution. Only applies when SSH is enabled.",
1044 - # "type": "text",
1045 - # "value": settings["code_exec_ssh_port"],
1046 - # }
1047 - # )
1048 -
1049 - # code_exec_section: SettingsSection = {
1050 - # "id": "code_exec",
1051 - # "title": "Code execution",
1052 - # "description": "Configuration of code execution by the agent.",
1053 - # "fields": code_exec_fields,
1054 - # "tab": "developer",
1055 - # }
1056 -
1057 - # Speech to text section
1058 - stt_fields: list[SettingsField] = []
1059 -
1060 - stt_fields.append(
1061 - {
1062 - "id": "stt_microphone_section",
1063 - "title": "Microphone device",
1064 - "description": "Select the microphone device to use for speech-to-text.",
1065 - "value": "<x-component path='/settings/speech/microphone.html' />",
1066 - "type": "html",
1067 - }
1068 - )
1069 -
1070 - stt_fields.append(
1071 - {
1072 - "id": "stt_model_size",
1073 - "title": "Speech-to-text model size",
1074 - "description": "Select the speech-to-text model size",
1075 - "type": "select",
1076 - "value": settings["stt_model_size"],
1077 - "options": [
1078 - {"value": "tiny", "label": "Tiny (39M, English)"},
1079 - {"value": "base", "label": "Base (74M, English)"},
1080 - {"value": "small", "label": "Small (244M, English)"},
1081 - {"value": "medium", "label": "Medium (769M, English)"},
1082 - {"value": "large", "label": "Large (1.5B, Multilingual)"},
1083 - {"value": "turbo", "label": "Turbo (Multilingual)"},
1084 - ],
1085 - }
1086 - )
1087 -
1088 - stt_fields.append(
1089 - {
1090 - "id": "stt_language",
1091 - "title": "Speech-to-text language code",
1092 - "description": "Language code (e.g. en, fr, it)",
1093 - "type": "text",
1094 - "value": settings["stt_language"],
1095 - }
1096 - )
1097 -
1098 - stt_fields.append(
1099 - {
1100 - "id": "stt_silence_threshold",
1101 - "title": "Microphone silence threshold",
1102 - "description": "Silence detection threshold. Lower values are more sensitive to noise.",
1103 - "type": "range",
1104 - "min": 0,
1105 - "max": 1,
1106 - "step": 0.01,
1107 - "value": settings["stt_silence_threshold"],
1108 - }
1109 - )
1110 -
1111 - stt_fields.append(
1112 - {
1113 - "id": "stt_silence_duration",
1114 - "title": "Microphone silence duration (ms)",
1115 - "description": "Duration of silence before the system considers speaking to have ended.",
1116 - "type": "text",
1117 - "value": settings["stt_silence_duration"],
1118 - }
1119 - )
1120 -
1121 - stt_fields.append(
1122 - {
1123 - "id": "stt_waiting_timeout",
1124 - "title": "Microphone waiting timeout (ms)",
1125 - "description": "Duration of silence before the system closes the microphone.",
1126 - "type": "text",
1127 - "value": settings["stt_waiting_timeout"],
1128 - }
1129 - )
1130 -
1131 - # TTS fields
1132 - tts_fields: list[SettingsField] = []
1133 -
1134 - tts_fields.append(
1135 - {
1136 - "id": "tts_kokoro",
1137 - "title": "Enable Kokoro TTS",
1138 - "description": "Enable higher quality server-side AI (Kokoro) instead of browser-based text-to-speech.",
1139 - "type": "switch",
1140 - "value": settings["tts_kokoro"],
1141 - }
1142 - )
1143 -
1144 - speech_section: SettingsSection = {
1145 - "id": "speech",
1146 - "title": "Speech",
1147 - "description": "Voice transcription and speech synthesis settings.",
1148 - "fields": stt_fields + tts_fields,
1149 - "tab": "agent",
1150 - }
1151 -
1152 - # MCP section
1153 - mcp_client_fields: list[SettingsField] = []
1154 -
1155 - mcp_client_fields.append(
1156 - {
1157 - "id": "mcp_servers_config",
1158 - "title": "MCP Servers Configuration",
1159 - "description": "External MCP servers can be configured here.",
1160 - "type": "button",
1161 - "value": "Open",
1162 - }
1163 - )
1164 -
1165 - mcp_client_fields.append(
1166 - {
1167 - "id": "mcp_servers",
1168 - "title": "MCP Servers",
1169 - "description": "(JSON list of) >> RemoteServer <<: [name, url, headers, timeout (opt), sse_read_timeout (opt), disabled (opt)] / >> Local Server <<: [name, command, args, env, encoding (opt), encoding_error_handler (opt), disabled (opt)]",
1170 - "type": "textarea",
1171 - "value": settings["mcp_servers"],
1172 - "hidden": True,
1173 - }
1174 - )
1175 -
1176 - mcp_client_fields.append(
1177 - {
1178 - "id": "mcp_client_init_timeout",
1179 - "title": "MCP Client Init Timeout",
1180 - "description": "Timeout for MCP client initialization (in seconds). Higher values might be required for complex MCPs, but might also slowdown system startup.",
1181 - "type": "number",
1182 - "value": settings["mcp_client_init_timeout"],
1183 - }
1184 - )
1185 -
1186 - mcp_client_fields.append(
1187 - {
1188 - "id": "mcp_client_tool_timeout",
1189 - "title": "MCP Client Tool Timeout",
1190 - "description": "Timeout for MCP client tool execution. Higher values might be required for complex tools, but might also result in long responses with failing tools.",
1191 - "type": "number",
1192 - "value": settings["mcp_client_tool_timeout"],
1193 - }
1194 - )
1195 -
1196 - mcp_client_section: SettingsSection = {
1197 - "id": "mcp_client",
1198 - "title": "External MCP Servers",
1199 - "description": "Agent Zero can use external MCP servers, local or remote as tools.",
1200 - "fields": mcp_client_fields,
1201 - "tab": "mcp",
1202 - }
1203 -
1204 - # Secrets section
1205 - secrets_fields: list[SettingsField] = []
1206 -
1207 - secrets_manager = get_default_secrets_manager()
1208 - try:
1209 - secrets = secrets_manager.get_masked_secrets()
1210 - except Exception:
1211 - secrets = ""
1212 -
1213 - secrets_fields.append({
1214 - "id": "variables",
1215 - "title": "Variables Store",
1216 - "description": "Store non-sensitive variables in .env format e.g. EMAIL_IMAP_SERVER=\"imap.gmail.com\", one item per line. You can use comments starting with # to add descriptions for the agent. See <a href=\"javascript:openModal('settings/secrets/example-vars.html')\">example</a>.<br>These variables are visible to LLMs and in chat history, they are not being masked.",
1217 - "type": "textarea",
1218 - "value": settings["variables"].strip(),
1219 - "style": "height: 20em",
1220 - })
1221 -
1222 - secrets_fields.append({
1223 - "id": "secrets",
1224 - "title": "Secrets Store",
1225 - "description": "Store secrets and credentials in .env format e.g. EMAIL_PASSWORD=\"s3cret-p4$$w0rd\", one item per line. You can use comments starting with # to add descriptions for the agent. See <a href=\"javascript:openModal('settings/secrets/example-secrets.html')\">example</a>.<br>These variables are not visile to LLMs and in chat history, they are being masked. ⚠️ only values with length >= 4 are being masked to prevent false positives. ",
1226 - "type": "textarea",
1227 - "value": secrets,
1228 - "style": "height: 20em",
1229 - })
1230 -
1231 - secrets_section: SettingsSection = {
1232 - "id": "secrets",
1233 - "title": "Secrets Management",
1234 - "description": "Manage secrets and credentials that agents can use without exposing values to LLMs, chat history or logs. Placeholders are automatically replaced with values just before tool calls. If bare passwords occur in tool results, they are masked back to placeholders.",
1235 - "fields": secrets_fields,
1236 - "tab": "external",
1237 - }
1238 -
1239 - mcp_server_fields: list[SettingsField] = []
1240 -
1241 - mcp_server_fields.append(
1242 - {
1243 - "id": "mcp_server_enabled",
1244 - "title": "Enable A0 MCP Server",
1245 - "description": "Expose Agent Zero as an SSE/HTTP MCP server. This will make this A0 instance available to MCP clients.",
1246 - "type": "switch",
1247 - "value": settings["mcp_server_enabled"],
1248 - }
1249 - )
1250 -
1251 - mcp_server_fields.append(
1252 - {
1253 - "id": "mcp_server_token",
1254 - "title": "MCP Server Token",
1255 - "description": "Token for MCP server authentication.",
1256 - "type": "text",
1257 - "hidden": True,
1258 - "value": settings["mcp_server_token"],
1259 - }
1260 - )
1261 -
1262 - mcp_server_section: SettingsSection = {
1263 - "id": "mcp_server",
1264 - "title": "A0 MCP Server",
1265 - "description": "Agent Zero can be exposed as an SSE MCP server. See <a href=\"javascript:openModal('settings/mcp/server/example.html')\">connection example</a>.",
1266 - "fields": mcp_server_fields,
1267 - "tab": "mcp",
1268 - }
1269 -
1270 - # -------- A2A Section --------
1271 - a2a_fields: list[SettingsField] = []
1272 -
1273 - a2a_fields.append(
1274 - {
1275 - "id": "a2a_server_enabled",
1276 - "title": "Enable A2A server",
1277 - "description": "Expose Agent Zero as A2A server. This allows other agents to connect to A0 via A2A protocol.",
1278 - "type": "switch",
1279 - "value": settings["a2a_server_enabled"],
1280 - }
1281 - )
1282 -
1283 - a2a_section: SettingsSection = {
1284 - "id": "a2a_server",
1285 - "title": "A0 A2A Server",
1286 - "description": "Agent Zero can be exposed as an A2A server. See <a href=\"javascript:openModal('settings/a2a/a2a-connection.html')\">connection example</a>.",
1287 - "fields": a2a_fields,
1288 - "tab": "mcp",
1289 - }
1290 -
1291 -
1292 - # External API section
1293 - external_api_fields: list[SettingsField] = []
1294 -
1295 - external_api_fields.append(
1296 - {
1297 - "id": "external_api_examples",
1298 - "title": "API Examples",
1299 - "description": "View examples for using Agent Zero's external API endpoints with API key authentication.",
1300 - "type": "button",
1301 - "value": "Show API Examples",
1302 - }
1303 - )
1304 -
1305 - external_api_section: SettingsSection = {
1306 - "id": "external_api",
1307 - "title": "External API",
1308 - "description": "Agent Zero provides external API endpoints for integration with other applications. "
1309 - "These endpoints use API key authentication and support text messages and file attachments.",
1310 - "fields": external_api_fields,
1311 - "tab": "external",
1312 - }
1313 -
1314 - # update checker section
1315 - update_checker_fields: list[SettingsField] = []
1316 -
1317 - update_checker_fields.append(
1318 - {
1319 - "id": "update_check_enabled",
1320 - "title": "Enable Update Checker",
1321 - "description": "Enable update checker to notify about newer versions of Agent Zero.",
1322 - "type": "switch",
1323 - "value": settings["update_check_enabled"],
1324 - }
1325 - )
1326 -
1327 - update_checker_section: SettingsSection = {
1328 - "id": "update_checker",
1329 - "title": "Update Checker",
1330 - "description": "Update checker periodically checks for new releases of Agent Zero and will notify when an update is recommended.<br>No personal data is sent to the update server, only randomized+anonymized unique ID and current version number, which help us evaluate the importance of the update in case of critical bug fixes etc.",
1331 - "fields": update_checker_fields,
1332 - "tab": "external",
1333 - }
1334 -
1335 - # Backup & Restore section
1336 - backup_fields: list[SettingsField] = []
1337 -
1338 - backup_fields.append(
1339 - {
1340 - "id": "backup_create",
1341 - "title": "Create Backup",
1342 - "description": "Create a backup archive of selected files and configurations "
1343 - "using customizable patterns.",
1344 - "type": "button",
1345 - "value": "Create Backup",
1346 - }
1347 - )
1348 -
1349 - backup_fields.append(
1350 - {
1351 - "id": "backup_restore",
1352 - "title": "Restore from Backup",
1353 - "description": "Restore files and configurations from a backup archive "
1354 - "with pattern-based selection.",
1355 - "type": "button",
1356 - "value": "Restore Backup",
1357 - }
1358 - )
1359 -
1360 - backup_section: SettingsSection = {
1361 - "id": "backup_restore",
1362 - "title": "Backup & Restore",
1363 - "description": "Backup and restore Agent Zero data and configurations "
1364 - "using glob pattern-based file selection.",
1365 - "fields": backup_fields,
1366 - "tab": "backup",
1367 - }
1368 -
1369 - # Add the section to the result
1370 - result: SettingsOutput = {
1371 - "sections": [
1372 - agent_section,
1373 - chat_model_section,
1374 - util_model_section,
1375 - browser_model_section,
1376 - embed_model_section,
1377 - memory_section,
1378 - speech_section,
1379 - api_keys_section,
1380 - litellm_section,
1381 - secrets_section,
1382 - auth_section,
1383 - mcp_client_section,
1384 - mcp_server_section,
1385 - a2a_section,
1386 - external_api_section,
1387 - update_checker_section,
1388 - backup_section,
1389 - dev_section,
1390 - # code_exec_section,
1391 - ]
1392 - }
1393 - return result
1394 -
1395 -
300 def _get_api_key_field(settings: Settings, provider: str, title: str) -> SettingsField:
301 key = settings["api_keys"].get(provider, models.get_api_key(provider))
302 # For API keys, use simple asterisk placeholder for existing keys
@@ -1430,28 +334,6 @@ def convert_in(settings: Settings) -> Settings:
334 current[key] = value
335 return current
336
1433 -#TODO just for reference, remove
1434 -def _convert_in_old(settings: dict) -> Settings:
1435 - current = get_settings()
1436 - for section in settings["sections"]:
1437 - if "fields" in section:
1438 - for field in section["fields"]:
1439 - # Skip saving if value is a placeholder
1440 - should_skip = (
1441 - field["value"] == PASSWORD_PLACEHOLDER or
1442 - field["value"] == API_KEY_PLACEHOLDER
1443 - )
1444 -
1445 - if not should_skip:
1446 - # Special handling for browser_http_headers
1447 - if field["id"] == "browser_http_headers" or field["id"].endswith("_kwargs"):
1448 - current[field["id"]] = _env_to_dict(field["value"])
1449 - elif field["id"].startswith("api_key_"):
1450 - current["api_keys"][field["id"]] = field["value"]
1451 - else:
1452 - current[field["id"]] = field["value"]
1453 - return current
1454 -
337
338 def get_settings() -> Settings:
339 global _settings
webui/components/chat/speech/speech-store.js
+12 -10
@@ -112,16 +112,18 @@ const model = {
112 try {
113 const response = await fetchApi("/settings_get", { method: "POST" });
114 const data = await response.json();
115 - const speechSection = data.settings.sections.find(
116 - (s) => s.title === "Speech"
117 - );
118 -
119 - if (speechSection) {
120 - speechSection.fields.forEach((field) => {
121 - if (this.hasOwnProperty(field.id)) {
122 - this[field.id] = field.value;
123 - }
124 - });
115 + const settings = data?.settings || {};
116 +
117 + if (settings) {
118 + this.stt_model_size = settings.stt_model_size ?? this.stt_model_size;
119 + this.stt_language = settings.stt_language ?? this.stt_language;
120 + this.stt_silence_threshold =
121 + settings.stt_silence_threshold ?? this.stt_silence_threshold;
122 + this.stt_silence_duration =
123 + settings.stt_silence_duration ?? this.stt_silence_duration;
124 + this.stt_waiting_timeout =
125 + settings.stt_waiting_timeout ?? this.stt_waiting_timeout;
126 + this.tts_kokoro = settings.tts_kokoro ?? this.tts_kokoro;
127 }
128 } catch (error) {
129 window.toastFetchError("Failed to load speech settings", error);
webui/components/settings/a2a/a2a-connection.html
+2 -7
@@ -44,12 +44,7 @@
44 let token = "";
45 try {
46 const response = await API.callJsonApi("settings_get", null);
47 - if (response && response.settings && response.settings.sections) {
48 - const allFields = response.settings.sections.flatMap(s => s.fields || []);
49 - const tokenField = allFields.find(f => f.id === "a2a_token") ||
50 - allFields.find(f => f.id === "mcp_server_token");
51 - if (tokenField) token = tokenField.value || "";
52 - }
47 + token = response?.settings?.mcp_server_token || "";
48 } catch (e) {
49 console.error("Failed to fetch token:", e);
50 }
@@ -109,4 +104,4 @@
104
105 </body>
106
112 -</html>
\ No newline at end of file
107 +</html>
webui/components/settings/a2a/a2a-server.html
+19 -132
@@ -4,143 +4,30 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('a2a_server');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data="{ get settings() { return $store.settingsStore.settings } }">
8 + <template x-if="settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
20 -
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
33 - </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
134 -
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
10 + <div class="section-title">A0 A2A Server</div>
11 + <div class="section-description">
12 + Agent Zero can be exposed as an A2A server. See <a href="javascript:openModal('settings/a2a/a2a-connection.html')">connection example</a>.
13 + </div>
14 +
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">Enable A2A server</div>
18 + <div class="field-description">
19 + Expose Agent Zero as A2A server. This allows other agents to connect to A0 via A2A protocol.
20 </div>
21 </div>
140 - </template>
22 + <div class="field-control">
23 + <label class="toggle">
24 + <input type="checkbox" x-model="settings.a2a_server_enabled" />
25 + <span class="toggler"></span>
26 + </label>
27 + </div>
28 + </div>
29 </div>
30 </template>
31 </div>
32 </body>
33 </html>
146 -
webui/components/settings/agent/agent-settings.html
+69 -43
@@ -9,54 +9,80 @@
9 <div>
10 <nav>
11 <ul>
12 - <template
13 - x-for="(section, index) in $store.settingsStore.filteredSections"
14 - :key="section.id"
15 - >
16 - <li>
17 - <a :href="'#section' + (index + 1)">
18 - <img
19 - :src="'/public/' + section.id + '.svg'"
20 - :alt="section.title"
21 - />
22 - <span x-text="section.title"></span>
23 - </a>
24 - </li>
25 - </template>
12 + <li>
13 + <a href="#section-agent-config">
14 + <img src="/public/agent.svg" alt="Agent Config" />
15 + <span>Agent Config</span>
16 + </a>
17 + </li>
18 + <li>
19 + <a href="#section-chat-model">
20 + <img src="/public/chat_model.svg" alt="Chat Model" />
21 + <span>Chat Model</span>
22 + </a>
23 + </li>
24 + <li>
25 + <a href="#section-util-model">
26 + <img src="/public/util_model.svg" alt="Utility Model" />
27 + <span>Utility Model</span>
28 + </a>
29 + </li>
30 + <li>
31 + <a href="#section-browser-model">
32 + <img src="/public/browser_model.svg" alt="Browser Model" />
33 + <span>Browser Model</span>
34 + </a>
35 + </li>
36 + <li>
37 + <a href="#section-embed-model">
38 + <img src="/public/embed_model.svg" alt="Embedding Model" />
39 + <span>Embedding Model</span>
40 + </a>
41 + </li>
42 + <li>
43 + <a href="#section-memory">
44 + <img src="/public/memory.svg" alt="Memory" />
45 + <span>Memory</span>
46 + </a>
47 + </li>
48 + <li>
49 + <a href="#section-speech">
50 + <img src="/public/speech.svg" alt="Speech" />
51 + <span>Speech</span>
52 + </a>
53 + </li>
54 </ul>
55 </nav>
56
29 - <template
30 - x-for="(section, index) in $store.settingsStore.filteredSections"
31 - :key="section.id"
32 - >
33 - <div :id="'section' + (index + 1)" class="section">
34 - <template x-if="section.id === 'agent'">
35 - <x-component path="settings/agent/agent.html"></x-component>
36 - </template>
37 - <template x-if="section.id === 'chat_model'">
38 - <x-component path="settings/agent/chat_model.html"></x-component>
39 - </template>
40 - <template x-if="section.id === 'util_model'">
41 - <x-component path="settings/agent/util_model.html"></x-component>
42 - </template>
43 - <template x-if="section.id === 'browser_model'">
44 - <x-component path="settings/agent/browser_model.html"></x-component>
45 - </template>
46 - <template x-if="section.id === 'embed_model'">
47 - <x-component path="settings/agent/embed_model.html"></x-component>
48 - </template>
49 - <template x-if="section.id === 'memory'">
50 - <x-component path="settings/agent/memory.html"></x-component>
51 - </template>
52 - <template x-if="section.id === 'speech'">
53 - <x-component path="settings/agent/speech.html"></x-component>
54 - </template>
55 - </div>
56 - </template>
57 + <div id="section-agent-config" class="section">
58 + <x-component path="settings/agent/agent.html"></x-component>
59 + </div>
60 +
61 + <div id="section-chat-model" class="section">
62 + <x-component path="settings/agent/chat_model.html"></x-component>
63 + </div>
64 +
65 + <div id="section-util-model" class="section">
66 + <x-component path="settings/agent/util_model.html"></x-component>
67 + </div>
68 +
69 + <div id="section-browser-model" class="section">
70 + <x-component path="settings/agent/browser_model.html"></x-component>
71 + </div>
72 +
73 + <div id="section-embed-model" class="section">
74 + <x-component path="settings/agent/embed_model.html"></x-component>
75 + </div>
76 +
77 + <div id="section-memory" class="section">
78 + <x-component path="settings/agent/memory.html"></x-component>
79 + </div>
80 +
81 + <div id="section-speech" class="section">
82 + <x-component path="settings/agent/speech.html"></x-component>
83 + </div>
84 </div>
85 </template>
86 </div>
87 </body>
88 </html>
62 -
webui/components/settings/agent/agent.html
+33 -129
@@ -4,143 +4,47 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('agent');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data>
8 + <template x-if="$store.settingsStore.settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
20 -
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
10 + <div class="section-title">Agent Config</div>
11 + <div class="section-description">
12 + Agent parameters.
13 + </div>
14 +
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">Default agent profile</div>
18 + <div class="field-description">
19 + Subdirectory of /agents folder to be used by default agent no. 0. Subordinate agents can be spawned with other profiles, that is on their superior agent to decide. This setting affects the behaviour of the top level agent you communicate with.
20 </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
21 + </div>
22 + <div class="field-control">
23 + <select x-model="$store.settingsStore.settings.agent_profile">
24 + <template x-for="option in $store.settingsStore.additional?.agent_subdirs" :key="option.value">
25 + <option :value="option.value" :selected="option.value === $store.settingsStore.settings.agent_profile" x-text="option.label"></option>
26 </template>
27 + </select>
28 + </div>
29 + </div>
30
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
31 + <div class="field">
32 + <div class="field-label">
33 + <div class="field-title">Knowledge subdirectory</div>
34 + <div class="field-description">
35 + Subdirectory of /knowledge folder to use for agent knowledge import. 'default' subfolder is always imported and contains framework knowledge.
36 </div>
37 </div>
140 - </template>
38 + <div class="field-control">
39 + <select x-model="$store.settingsStore.settings.agent_knowledge_subdir">
40 + <template x-for="option in $store.settingsStore.additional?.knowledge_subdirs" :key="option.value">
41 + <option :value="option.value" :selected="option.value === $store.settingsStore.settings.agent_knowledge_subdir" x-text="option.label"></option>
42 + </template>
43 + </select>
44 + </div>
45 + </div>
46 </div>
47 </template>
48 </div>
49 </body>
50 </html>
146 -
webui/components/settings/agent/browser_model.html
+105 -122
@@ -4,143 +4,126 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('browser_model');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data>
8 + <template x-if="$store.settingsStore.settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
10 + <div class="section-title">Web Browser Model</div>
11 + <div class="section-description">
12 + Settings for the web browser model. Agent Zero uses <a href='https://github.com/browser-use/browser-use' target='_blank'>browser-use</a> agentic framework to handle web interactions.
13 + </div>
14
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
33 - </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">Web browser model provider</div>
18 + <div class="field-description">Select provider for web browser model used by Agent Zero</div>
19 + </div>
20 + <div class="field-control">
21 + <select x-model="$store.settingsStore.settings.browser_model_provider">
22 + <template x-for="option in $store.settingsStore.additional?.chat_providers" :key="option.value">
23 + <option :value="option.value" :selected="option.value === $store.settingsStore.settings.browser_model_provider" x-text="option.label"></option>
24 </template>
25 + </select>
26 + </div>
27 + </div>
28
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
29 + <div class="field">
30 + <div class="field-label">
31 + <div class="field-title">Web browser model name</div>
32 + <div class="field-description">Exact name of model from selected provider</div>
33 + </div>
34 + <div class="field-control">
35 + <input type="text" x-model="$store.settingsStore.settings.browser_model_name" />
36 + </div>
37 + </div>
38
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
39 + <div class="field">
40 + <div class="field-label">
41 + <div class="field-title">Web browser model API base URL</div>
42 + <div class="field-description">
43 + API base URL for web browser model. Leave empty for default. Only relevant for Azure, local and custom (other) providers.
44 + </div>
45 + </div>
46 + <div class="field-control">
47 + <input type="text" x-model="$store.settingsStore.settings.browser_model_api_base" />
48 + </div>
49 + </div>
50
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
51 + <div class="field">
52 + <div class="field-label">
53 + <div class="field-title">Supports Vision</div>
54 + <div class="field-description">
55 + Models capable of Vision can for example natively see the content of image attachments.
56 + </div>
57 + </div>
58 + <div class="field-control">
59 + <label class="toggle">
60 + <input type="checkbox" x-model="$store.settingsStore.settings.browser_model_vision" />
61 + <span class="toggler"></span>
62 + </label>
63 + </div>
64 + </div>
65
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
66 + <div class="field">
67 + <div class="field-label">
68 + <div class="field-title">Requests per minute limit</div>
69 + <div class="field-description">
70 + Limits the number of requests per minute to the web browser model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
71 + </div>
72 + </div>
73 + <div class="field-control">
74 + <input type="number" x-model.number="$store.settingsStore.settings.browser_model_rl_requests" />
75 + </div>
76 + </div>
77
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
78 + <div class="field">
79 + <div class="field-label">
80 + <div class="field-title">Input tokens per minute limit</div>
81 + <div class="field-description">
82 + Limits the number of input tokens per minute to the web browser model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
83 + </div>
84 + </div>
85 + <div class="field-control">
86 + <input type="number" x-model.number="$store.settingsStore.settings.browser_model_rl_input" />
87 + </div>
88 + </div>
89
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
90 + <div class="field">
91 + <div class="field-label">
92 + <div class="field-title">Output tokens per minute limit</div>
93 + <div class="field-description">
94 + Limits the number of output tokens per minute to the web browser model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
95 + </div>
96 + </div>
97 + <div class="field-control">
98 + <input type="number" x-model.number="$store.settingsStore.settings.browser_model_rl_output" />
99 + </div>
100 + </div>
101
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
102 + <div class="field field-full">
103 + <div class="field-label">
104 + <div class="field-title">Web browser model additional parameters</div>
105 + <div class="field-description">
106 + Any other parameters supported by <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a>. Format is KEY=VALUE on individual lines, like .env file. Value can also contain JSON objects - when unquoted, it is treated as object, number etc., when quoted, it is treated as string.
107 + </div>
108 + </div>
109 + <div class="field-control">
110 + <textarea x-model="$store.settingsStore.settings.browser_model_kwargs"></textarea>
111 + </div>
112 + </div>
113
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
114 + <div class="field field-full">
115 + <div class="field-label">
116 + <div class="field-title">Browser HTTP headers</div>
117 + <div class="field-description">
118 + Default HTTP headers for web browsing calls, in KEY=VALUE (.env) format.
119 </div>
120 </div>
140 - </template>
121 + <div class="field-control">
122 + <textarea x-model="$store.settingsStore.settings.browser_http_headers"></textarea>
123 + </div>
124 + </div>
125 </div>
126 </template>
127 </div>
128 </body>
129 </html>
146 -
webui/components/settings/agent/chat_model.html
+124 -122
@@ -4,143 +4,145 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('chat_model');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data>
8 + <template x-if="$store.settingsStore.settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
10 + <div class="section-title">Chat Model</div>
11 + <div class="section-description">
12 + Selection and settings for main chat model used by Agent Zero
13 + </div>
14
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
33 - </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">Chat model provider</div>
18 + <div class="field-description">Select provider for main chat model used by Agent Zero</div>
19 + </div>
20 + <div class="field-control">
21 + <select x-model="$store.settingsStore.settings.chat_model_provider">
22 + <template x-for="option in $store.settingsStore.additional?.chat_providers" :key="option.value">
23 + <option :value="option.value" :selected="option.value === $store.settingsStore.settings.chat_model_provider" x-text="option.label"></option>
24 </template>
25 + </select>
26 + </div>
27 + </div>
28
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
29 + <div class="field">
30 + <div class="field-label">
31 + <div class="field-title">Chat model name</div>
32 + <div class="field-description">Exact name of model from selected provider</div>
33 + </div>
34 + <div class="field-control">
35 + <input type="text" x-model="$store.settingsStore.settings.chat_model_name" />
36 + </div>
37 + </div>
38
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
39 + <div class="field">
40 + <div class="field-label">
41 + <div class="field-title">Chat model API base URL</div>
42 + <div class="field-description">
43 + API base URL for main chat model. Leave empty for default. Only relevant for Azure, local and custom (other) providers.
44 + </div>
45 + </div>
46 + <div class="field-control">
47 + <input type="text" x-model="$store.settingsStore.settings.chat_model_api_base" />
48 + </div>
49 + </div>
50
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
51 + <div class="field">
52 + <div class="field-label">
53 + <div class="field-title">Chat model context length</div>
54 + <div class="field-description">
55 + Maximum number of tokens in the context window for LLM. System prompt, chat history, RAG and response all count towards this limit.
56 + </div>
57 + </div>
58 + <div class="field-control">
59 + <input type="number" x-model.number="$store.settingsStore.settings.chat_model_ctx_length" />
60 + </div>
61 + </div>
62
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
63 + <div class="field">
64 + <div class="field-label">
65 + <div class="field-title">Context window space for chat history</div>
66 + <div class="field-description">
67 + Portion of context window dedicated to chat history visible to the agent. Chat history will automatically be optimized to fit. Smaller size will result in shorter and more summarized history. The remaining space will be used for system prompt, RAG and response.
68 + </div>
69 + </div>
70 + <div class="field-control">
71 + <input
72 + type="range"
73 + min="0.01"
74 + max="1"
75 + step="0.01"
76 + x-model.number="$store.settingsStore.settings.chat_model_ctx_history"
77 + />
78 + <span class="range-value" x-text="$store.settingsStore.settings.chat_model_ctx_history"></span>
79 + </div>
80 + </div>
81
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
82 + <div class="field">
83 + <div class="field-label">
84 + <div class="field-title">Supports Vision</div>
85 + <div class="field-description">
86 + Models capable of Vision can for example natively see the content of image attachments.
87 + </div>
88 + </div>
89 + <div class="field-control">
90 + <label class="toggle">
91 + <input type="checkbox" x-model="$store.settingsStore.settings.chat_model_vision" />
92 + <span class="toggler"></span>
93 + </label>
94 + </div>
95 + </div>
96
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
97 + <div class="field">
98 + <div class="field-label">
99 + <div class="field-title">Requests per minute limit</div>
100 + <div class="field-description">
101 + Limits the number of requests per minute to the chat model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
102 + </div>
103 + </div>
104 + <div class="field-control">
105 + <input type="number" x-model.number="$store.settingsStore.settings.chat_model_rl_requests" />
106 + </div>
107 + </div>
108
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
109 + <div class="field">
110 + <div class="field-label">
111 + <div class="field-title">Input tokens per minute limit</div>
112 + <div class="field-description">
113 + Limits the number of input tokens per minute to the chat model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
114 + </div>
115 + </div>
116 + <div class="field-control">
117 + <input type="number" x-model.number="$store.settingsStore.settings.chat_model_rl_input" />
118 + </div>
119 + </div>
120
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
121 + <div class="field">
122 + <div class="field-label">
123 + <div class="field-title">Output tokens per minute limit</div>
124 + <div class="field-description">
125 + Limits the number of output tokens per minute to the chat model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
126 </div>
127 </div>
140 - </template>
128 + <div class="field-control">
129 + <input type="number" x-model.number="$store.settingsStore.settings.chat_model_rl_output" />
130 + </div>
131 + </div>
132 +
133 + <div class="field field-full">
134 + <div class="field-label">
135 + <div class="field-title">Chat model additional parameters</div>
136 + <div class="field-description">
137 + Any other parameters supported by <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a>. Format is KEY=VALUE on individual lines, like .env file. Value can also contain JSON objects - when unquoted, it is treated as object, number etc., when quoted, it is treated as string.
138 + </div>
139 + </div>
140 + <div class="field-control">
141 + <textarea x-model="$store.settingsStore.settings.chat_model_kwargs"></textarea>
142 + </div>
143 + </div>
144 </div>
145 </template>
146 </div>
147 </body>
148 </html>
146 -
webui/components/settings/agent/embed_model.html
+69 -125
@@ -4,143 +4,87 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('embed_model');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data>
8 + <template x-if="$store.settingsStore.settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
10 + <div class="section-title">Embedding Model</div>
11 + <div class="section-description">
12 + Settings for the embedding model used by the framework
13 + </div>
14
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
33 - </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">Embedding model provider</div>
18 + <div class="field-description">Select provider for embedding model used by the framework</div>
19 + </div>
20 + <div class="field-control">
21 + <select x-model="$store.settingsStore.settings.embed_model_provider">
22 + <template x-for="option in $store.settingsStore.additional?.embedding_providers" :key="option.value">
23 + <option :value="option.value" :selected="option.value === $store.settingsStore.settings.embed_model_provider" x-text="option.label"></option>
24 </template>
25 + </select>
26 + </div>
27 + </div>
28
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
29 + <div class="field">
30 + <div class="field-label">
31 + <div class="field-title">Embedding model name</div>
32 + <div class="field-description">Exact name of model from selected provider</div>
33 + </div>
34 + <div class="field-control">
35 + <input type="text" x-model="$store.settingsStore.settings.embed_model_name" />
36 + </div>
37 + </div>
38
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
39 + <div class="field">
40 + <div class="field-label">
41 + <div class="field-title">Embedding model API base URL</div>
42 + <div class="field-description">
43 + API base URL for embedding model. Leave empty for default. Only relevant for Azure, local and custom (other) providers.
44 + </div>
45 + </div>
46 + <div class="field-control">
47 + <input type="text" x-model="$store.settingsStore.settings.embed_model_api_base" />
48 + </div>
49 + </div>
50
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
51 + <div class="field">
52 + <div class="field-label">
53 + <div class="field-title">Requests per minute limit</div>
54 + <div class="field-description">
55 + Limits the number of requests per minute to the embedding model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
56 + </div>
57 + </div>
58 + <div class="field-control">
59 + <input type="number" x-model.number="$store.settingsStore.settings.embed_model_rl_requests" />
60 + </div>
61 + </div>
62
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
63 + <div class="field">
64 + <div class="field-label">
65 + <div class="field-title">Input tokens per minute limit</div>
66 + <div class="field-description">
67 + Limits the number of input tokens per minute to the embedding model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
68 + </div>
69 + </div>
70 + <div class="field-control">
71 + <input type="number" x-model.number="$store.settingsStore.settings.embed_model_rl_input" />
72 + </div>
73 + </div>
74
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
75 + <div class="field field-full">
76 + <div class="field-label">
77 + <div class="field-title">Embedding model additional parameters</div>
78 + <div class="field-description">
79 + Any other parameters supported by <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a>. Format is KEY=VALUE on individual lines, like .env file. Value can also contain JSON objects - when unquoted, it is treated as object, number etc., when quoted, it is treated as string.
80 </div>
81 </div>
140 - </template>
82 + <div class="field-control">
83 + <textarea x-model="$store.settingsStore.settings.embed_model_kwargs"></textarea>
84 + </div>
85 + </div>
86 </div>
87 </template>
88 </div>
89 </body>
90 </html>
146 -
webui/components/settings/agent/memory.html
+218 -130
@@ -4,143 +4,231 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('memory');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data>
8 + <template x-if="$store.settingsStore.settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
20 -
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
10 + <div class="section-title">Memory</div>
11 + <div class="section-description">
12 + Configuration of A0's memory system. A0 memorizes and recalls memories automatically to help its context awareness.
13 + </div>
14 +
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">Memory Subdirectory</div>
18 + <div class="field-description">
19 + Subdirectory of /memory folder to use for agent memory storage. Used to separate memory storage between different instances.
20 + </div>
21 + </div>
22 + <div class="field-control">
23 + <input type="text" x-model="$store.settingsStore.settings.agent_memory_subdir" />
24 + </div>
25 + </div>
26 +
27 + <div class="field">
28 + <div class="field-label">
29 + <div class="field-title">Memory Dashboard</div>
30 + <div class="field-description">
31 + View and explore all stored memories in a table format with filtering and search capabilities.
32 + </div>
33 + </div>
34 + <div class="field-control">
35 + <button
36 + class="btn btn-field"
37 + @click="$store.settingsStore.handleFieldButton({ id: 'memory_dashboard' })"
38 + >
39 + Open Dashboard
40 + </button>
41 + </div>
42 + </div>
43 +
44 + <div class="field">
45 + <div class="field-label">
46 + <div class="field-title">Memory auto-recall enabled</div>
47 + <div class="field-description">Agent Zero will automatically recall memories based on convesation context.</div>
48 + </div>
49 + <div class="field-control">
50 + <label class="toggle">
51 + <input type="checkbox" x-model="$store.settingsStore.settings.memory_recall_enabled" />
52 + <span class="toggler"></span>
53 + </label>
54 + </div>
55 + </div>
56 +
57 + <div class="field">
58 + <div class="field-label">
59 + <div class="field-title">Memory auto-recall delayed</div>
60 + <div class="field-description">
61 + The agent will not wait for auto memory recall. Memories will be delivered one message later. This speeds up agent's response time but may result in less relevant first step.
62 + </div>
63 + </div>
64 + <div class="field-control">
65 + <label class="toggle">
66 + <input type="checkbox" x-model="$store.settingsStore.settings.memory_recall_delayed" />
67 + <span class="toggler"></span>
68 + </label>
69 + </div>
70 + </div>
71 +
72 + <div class="field">
73 + <div class="field-label">
74 + <div class="field-title">Auto-recall AI query preparation</div>
75 + <div class="field-description">
76 + Enables vector DB query preparation from conversation context by utility LLM for auto-recall. Improves search quality, adds 1 utility LLM call per auto-recall.
77 + </div>
78 + </div>
79 + <div class="field-control">
80 + <label class="toggle">
81 + <input type="checkbox" x-model="$store.settingsStore.settings.memory_recall_query_prep" />
82 + <span class="toggler"></span>
83 + </label>
84 + </div>
85 + </div>
86 +
87 + <div class="field">
88 + <div class="field-label">
89 + <div class="field-title">Auto-recall AI post-filtering</div>
90 + <div class="field-description">
91 + Enables memory relevance filtering by utility LLM for auto-recall. Improves search quality, adds 1 utility LLM call per auto-recall.
92 + </div>
93 + </div>
94 + <div class="field-control">
95 + <label class="toggle">
96 + <input type="checkbox" x-model="$store.settingsStore.settings.memory_recall_post_filter" />
97 + <span class="toggler"></span>
98 + </label>
99 + </div>
100 + </div>
101 +
102 + <div class="field">
103 + <div class="field-label">
104 + <div class="field-title">Memory auto-recall interval</div>
105 + <div class="field-description">
106 + Memories are recalled after every user or superior agent message. During agent's monologue, memories are recalled every X turns based on this parameter.
107 + </div>
108 + </div>
109 + <div class="field-control">
110 + <input type="range" min="1" max="10" step="1" x-model.number="$store.settingsStore.settings.memory_recall_interval" />
111 + <span class="range-value" x-text="$store.settingsStore.settings.memory_recall_interval"></span>
112 + </div>
113 + </div>
114 +
115 + <div class="field">
116 + <div class="field-label">
117 + <div class="field-title">Memory auto-recall history length</div>
118 + <div class="field-description">
119 + The length of conversation history passed to memory recall LLM for context (in characters).
120 + </div>
121 + </div>
122 + <div class="field-control">
123 + <input type="number" x-model.number="$store.settingsStore.settings.memory_recall_history_len" />
124 + </div>
125 + </div>
126 +
127 + <div class="field">
128 + <div class="field-label">
129 + <div class="field-title">Memory auto-recall similarity threshold</div>
130 + <div class="field-description">
131 + The threshold for similarity search in memory recall (0 = no similarity, 1 = exact match).
132 + </div>
133 + </div>
134 + <div class="field-control">
135 + <input type="range" min="0" max="1" step="0.01" x-model.number="$store.settingsStore.settings.memory_recall_similarity_threshold" />
136 + <span class="range-value" x-text="$store.settingsStore.settings.memory_recall_similarity_threshold"></span>
137 + </div>
138 + </div>
139 +
140 + <div class="field">
141 + <div class="field-label">
142 + <div class="field-title">Memory auto-recall max memories to search</div>
143 + <div class="field-description">
144 + The maximum number of memories returned by vector DB for further processing.
145 + </div>
146 + </div>
147 + <div class="field-control">
148 + <input type="number" x-model.number="$store.settingsStore.settings.memory_recall_memories_max_search" />
149 + </div>
150 + </div>
151 +
152 + <div class="field">
153 + <div class="field-label">
154 + <div class="field-title">Memory auto-recall max memories to use</div>
155 + <div class="field-description">
156 + The maximum number of memories to inject into A0's context window.
157 + </div>
158 + </div>
159 + <div class="field-control">
160 + <input type="number" x-model.number="$store.settingsStore.settings.memory_recall_memories_max_result" />
161 + </div>
162 + </div>
163 +
164 + <div class="field">
165 + <div class="field-label">
166 + <div class="field-title">Memory auto-recall max solutions to search</div>
167 + <div class="field-description">
168 + The maximum number of solutions returned by vector DB for further processing.
169 + </div>
170 + </div>
171 + <div class="field-control">
172 + <input type="number" x-model.number="$store.settingsStore.settings.memory_recall_solutions_max_search" />
173 + </div>
174 + </div>
175 +
176 + <div class="field">
177 + <div class="field-label">
178 + <div class="field-title">Memory auto-recall max solutions to use</div>
179 + <div class="field-description">
180 + The maximum number of solutions to inject into A0's context window.
181 </div>
182 + </div>
183 + <div class="field-control">
184 + <input type="number" x-model.number="$store.settingsStore.settings.memory_recall_solutions_max_result" />
185 + </div>
186 + </div>
187
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
134 -
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
188 + <div class="field">
189 + <div class="field-label">
190 + <div class="field-title">Auto-memorize enabled</div>
191 + <div class="field-description">
192 + A0 will automatically memorize facts and solutions from conversation history.
193 </div>
194 </div>
140 - </template>
195 + <div class="field-control">
196 + <label class="toggle">
197 + <input type="checkbox" x-model="$store.settingsStore.settings.memory_memorize_enabled" />
198 + <span class="toggler"></span>
199 + </label>
200 + </div>
201 + </div>
202 +
203 + <div class="field">
204 + <div class="field-label">
205 + <div class="field-title">Auto-memorize AI consolidation</div>
206 + <div class="field-description">
207 + A0 will automatically consolidate similar memories using utility LLM. Improves memory quality over time, adds 2 utility LLM calls per memory.
208 + </div>
209 + </div>
210 + <div class="field-control">
211 + <label class="toggle">
212 + <input type="checkbox" x-model="$store.settingsStore.settings.memory_memorize_consolidation" />
213 + <span class="toggler"></span>
214 + </label>
215 + </div>
216 + </div>
217 +
218 + <div class="field">
219 + <div class="field-label">
220 + <div class="field-title">Auto-memorize replacement threshold</div>
221 + <div class="field-description">
222 + Only applies when AI consolidation is disabled. Replaces previous similar memories with new ones based on this threshold. 0 = replace even if not similar at all, 1 = replace only if exact match.
223 + </div>
224 + </div>
225 + <div class="field-control">
226 + <input type="range" min="0" max="1" step="0.01" x-model.number="$store.settingsStore.settings.memory_memorize_replace_threshold" />
227 + <span class="range-value" x-text="$store.settingsStore.settings.memory_memorize_replace_threshold"></span>
228 + </div>
229 + </div>
230 </div>
231 </template>
232 </div>
233 </body>
234 </html>
146 -
webui/components/settings/agent/speech.html
+76 -124
@@ -4,143 +4,95 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('speech');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data>
8 + <template x-if="$store.settingsStore.settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
10 + <div class="section-title">Speech</div>
11 + <div class="section-description">
12 + Voice transcription and speech synthesis settings.
13 + </div>
14
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
33 - </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
15 + <div class="field field-full">
16 + <div class="field-label">
17 + <div class="field-title">Microphone device</div>
18 + <div class="field-description">Select the microphone device to use for speech-to-text.</div>
19 + </div>
20 + <div class="field-control">
21 + <x-component path="/settings/speech/microphone.html"></x-component>
22 + </div>
23 + </div>
24
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
25 + <div class="field">
26 + <div class="field-label">
27 + <div class="field-title">Speech-to-text model size</div>
28 + <div class="field-description">Select the speech-to-text model size</div>
29 + </div>
30 + <div class="field-control">
31 + <select x-model="$store.settingsStore.settings.stt_model_size">
32 + <template x-for="option in $store.settingsStore.additional?.stt_models" :key="option.value">
33 + <option :value="option.value" :selected="option.value === $store.settingsStore.settings.stt_model_size" x-text="option.label"></option>
34 </template>
35 + </select>
36 + </div>
37 + </div>
38
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
39 + <div class="field">
40 + <div class="field-label">
41 + <div class="field-title">Speech-to-text language code</div>
42 + <div class="field-description">Language code (e.g. en, fr, it)</div>
43 + </div>
44 + <div class="field-control">
45 + <input type="text" x-model="$store.settingsStore.settings.stt_language" />
46 + </div>
47 + </div>
48
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
49 + <div class="field">
50 + <div class="field-label">
51 + <div class="field-title">Microphone silence threshold</div>
52 + <div class="field-description">Silence detection threshold. Lower values are more sensitive to noise.</div>
53 + </div>
54 + <div class="field-control">
55 + <input type="range" min="0" max="1" step="0.01" x-model.number="$store.settingsStore.settings.stt_silence_threshold" />
56 + <span class="range-value" x-text="$store.settingsStore.settings.stt_silence_threshold"></span>
57 + </div>
58 + </div>
59
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
60 + <div class="field">
61 + <div class="field-label">
62 + <div class="field-title">Microphone silence duration (ms)</div>
63 + <div class="field-description">Duration of silence before the system considers speaking to have ended.</div>
64 + </div>
65 + <div class="field-control">
66 + <input type="number" x-model.number="$store.settingsStore.settings.stt_silence_duration" />
67 + </div>
68 + </div>
69
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
70 + <div class="field">
71 + <div class="field-label">
72 + <div class="field-title">Microphone waiting timeout (ms)</div>
73 + <div class="field-description">Duration of silence before the system closes the microphone.</div>
74 + </div>
75 + <div class="field-control">
76 + <input type="number" x-model.number="$store.settingsStore.settings.stt_waiting_timeout" />
77 + </div>
78 + </div>
79
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
80 + <div class="field">
81 + <div class="field-label">
82 + <div class="field-title">Enable Kokoro TTS</div>
83 + <div class="field-description">
84 + Enable higher quality server-side AI (Kokoro) instead of browser-based text-to-speech.
85 </div>
86 </div>
140 - </template>
87 + <div class="field-control">
88 + <label class="toggle">
89 + <input type="checkbox" x-model="$store.settingsStore.settings.tts_kokoro" />
90 + <span class="toggler"></span>
91 + </label>
92 + </div>
93 + </div>
94 </div>
95 </template>
96 </div>
97 </body>
98 </html>
146 -
webui/components/settings/agent/util_model.html
+80 -124
@@ -4,143 +4,99 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('util_model');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data>
8 + <template x-if="$store.settingsStore.settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
10 + <div class="section-title">Utility model</div>
11 + <div class="section-description">
12 + Smaller, cheaper, faster model for handling utility tasks like organizing memory, preparing prompts, summarizing.
13 + </div>
14
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
33 - </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">Utility model provider</div>
18 + <div class="field-description">Select provider for utility model used by the framework</div>
19 + </div>
20 + <div class="field-control">
21 + <select x-model="$store.settingsStore.settings.util_model_provider">
22 + <template x-for="option in $store.settingsStore.additional?.chat_providers" :key="option.value">
23 + <option :value="option.value" :selected="option.value === $store.settingsStore.settings.util_model_provider" x-text="option.label"></option>
24 </template>
25 + </select>
26 + </div>
27 + </div>
28
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
29 + <div class="field">
30 + <div class="field-label">
31 + <div class="field-title">Utility model name</div>
32 + <div class="field-description">Exact name of model from selected provider</div>
33 + </div>
34 + <div class="field-control">
35 + <input type="text" x-model="$store.settingsStore.settings.util_model_name" />
36 + </div>
37 + </div>
38
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
39 + <div class="field">
40 + <div class="field-label">
41 + <div class="field-title">Utility model API base URL</div>
42 + <div class="field-description">
43 + API base URL for utility model. Leave empty for default. Only relevant for Azure, local and custom (other) providers.
44 + </div>
45 + </div>
46 + <div class="field-control">
47 + <input type="text" x-model="$store.settingsStore.settings.util_model_api_base" />
48 + </div>
49 + </div>
50
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
51 + <div class="field">
52 + <div class="field-label">
53 + <div class="field-title">Requests per minute limit</div>
54 + <div class="field-description">
55 + Limits the number of requests per minute to the utility model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
56 + </div>
57 + </div>
58 + <div class="field-control">
59 + <input type="number" x-model.number="$store.settingsStore.settings.util_model_rl_requests" />
60 + </div>
61 + </div>
62
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
63 + <div class="field">
64 + <div class="field-label">
65 + <div class="field-title">Input tokens per minute limit</div>
66 + <div class="field-description">
67 + Limits the number of input tokens per minute to the utility model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
68 + </div>
69 + </div>
70 + <div class="field-control">
71 + <input type="number" x-model.number="$store.settingsStore.settings.util_model_rl_input" />
72 + </div>
73 + </div>
74
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
75 + <div class="field">
76 + <div class="field-label">
77 + <div class="field-title">Output tokens per minute limit</div>
78 + <div class="field-description">
79 + Limits the number of output tokens per minute to the utility model. Waits if the limit is exceeded. Set to 0 to disable rate limiting.
80 + </div>
81 + </div>
82 + <div class="field-control">
83 + <input type="number" x-model.number="$store.settingsStore.settings.util_model_rl_output" />
84 + </div>
85 + </div>
86
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
87 + <div class="field field-full">
88 + <div class="field-label">
89 + <div class="field-title">Utility model additional parameters</div>
90 + <div class="field-description">
91 + Any other parameters supported by <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a>. Format is KEY=VALUE on individual lines, like .env file. Value can also contain JSON objects - when unquoted, it is treated as object, number etc., when quoted, it is treated as string.
92 </div>
93 </div>
140 - </template>
94 + <div class="field-control">
95 + <textarea x-model="$store.settingsStore.settings.util_model_kwargs"></textarea>
96 + </div>
97 + </div>
98 </div>
99 </template>
100 </div>
101 </body>
102 </html>
146 -
webui/components/settings/backup/backup-settings.html
+9 -25
@@ -9,36 +9,20 @@
9 <div>
10 <nav>
11 <ul>
12 - <template
13 - x-for="(section, index) in $store.settingsStore.filteredSections"
14 - :key="section.id"
15 - >
16 - <li>
17 - <a :href="'#section' + (index + 1)">
18 - <img
19 - :src="'/public/' + section.id + '.svg'"
20 - :alt="section.title"
21 - />
22 - <span x-text="section.title"></span>
23 - </a>
24 - </li>
25 - </template>
12 + <li>
13 + <a href="#section-backup-restore">
14 + <img src="/public/backup_restore.svg" alt="Backup & Restore" />
15 + <span>Backup & Restore</span>
16 + </a>
17 + </li>
18 </ul>
19 </nav>
20
29 - <template
30 - x-for="(section, index) in $store.settingsStore.filteredSections"
31 - :key="section.id"
32 - >
33 - <div :id="'section' + (index + 1)" class="section">
34 - <template x-if="section.id === 'backup_restore'">
35 - <x-component path="settings/backup/backup_restore.html"></x-component>
36 - </template>
37 - </div>
38 - </template>
21 + <div id="section-backup-restore" class="section">
22 + <x-component path="settings/backup/backup_restore.html"></x-component>
23 + </div>
24 </div>
25 </template>
26 </div>
27 </body>
28 </html>
44 -
webui/components/settings/backup/backup_restore.html
+37 -133
@@ -4,143 +4,47 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('backup_restore');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
17 - <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
20 -
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
33 - </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
7 + <div x-data>
8 + <div>
9 + <div class="section-title">Backup &amp; Restore</div>
10 + <div class="section-description">
11 + Backup and restore Agent Zero data and configurations using glob pattern-based file selection.
12 + </div>
13
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
14 + <div class="field">
15 + <div class="field-label">
16 + <div class="field-title">Create Backup</div>
17 + <div class="field-description">
18 + Create a backup archive of selected files and configurations using customizable patterns.
19 + </div>
20 + </div>
21 + <div class="field-control">
22 + <button
23 + class="btn btn-field"
24 + @click="$store.settingsStore.handleFieldButton({ id: 'backup_create' })"
25 + >
26 + Create Backup
27 + </button>
28 + </div>
29 + </div>
30
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
138 - </div>
31 + <div class="field">
32 + <div class="field-label">
33 + <div class="field-title">Restore from Backup</div>
34 + <div class="field-description">
35 + Restore files and configurations from a backup archive with pattern-based selection.
36 </div>
140 - </template>
37 + </div>
38 + <div class="field-control">
39 + <button
40 + class="btn btn-field"
41 + @click="$store.settingsStore.handleFieldButton({ id: 'backup_restore' })"
42 + >
43 + Restore Backup
44 + </button>
45 + </div>
46 </div>
142 - </template>
47 + </div>
48 </div>
49 </body>
50 </html>
146 -
webui/components/settings/developer/dev.html
+63 -123
@@ -4,137 +4,78 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('dev');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data>
8 + <template x-if="$store.settingsStore.settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
10 + <div class="section-title">Development</div>
11 + <div class="section-description">
12 + Parameters for A0 framework development. RFCs (remote function calls) are used to call functions on another A0 instance. You can develop and debug A0 natively on your local system while redirecting some functions to A0 instance in docker. This is crucial for development as A0 needs to run in standardized environment to support all features.
13 + </div>
14
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">Shell Interface</div>
18 + <div class="field-description">
19 + Terminal interface used for Code Execution Tool. Local Python TTY works locally in both dockerized and development environments. SSH always connects to dockerized environment (automatically at localhost or RFC host address).
20 </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
21 + </div>
22 + <div class="field-control">
23 + <select x-model="$store.settingsStore.settings.shell_interface">
24 + <template x-for="option in $store.settingsStore.additional?.shell_interfaces" :key="option.value">
25 + <option :value="option.value" :selected="option.value === $store.settingsStore.settings.shell_interface" x-text="option.label"></option>
26 </template>
27 + </select>
28 + </div>
29 + </div>
30
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
31 + <template x-if="!$store.settingsStore.additional?.is_dockerized">
32 + <div>
33 + <div class="field">
34 + <div class="field-label">
35 + <div class="field-title">RFC Destination URL</div>
36 + <div class="field-description">
37 + URL of dockerized A0 instance for remote function calls. Do not specify port here.
38 </div>
107 - </template>
39 + </div>
40 + <div class="field-control">
41 + <input type="text" x-model="$store.settingsStore.settings.rfc_url" />
42 + </div>
43 + </div>
44 + </div>
45 + </template>
46
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
47 + <div class="field">
48 + <div class="field-label">
49 + <div class="field-title">RFC Password</div>
50 + <div class="field-description">
51 + Password for remote function calls. Passwords must match on both instances. RFCs can not be used with empty password.
52 + </div>
53 + </div>
54 + <div class="field-control">
55 + <input type="password" autocomplete="off" x-model="$store.settingsStore.settings.rfc_password" />
56 + </div>
57 + </div>
58
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
59 + <template x-if="!$store.settingsStore.additional?.is_dockerized">
60 + <div>
61 + <div class="field">
62 + <div class="field-label">
63 + <div class="field-title">RFC HTTP port</div>
64 + <div class="field-description">HTTP port for dockerized instance of A0.</div>
65 + </div>
66 + <div class="field-control">
67 + <input type="number" x-model.number="$store.settingsStore.settings.rfc_port_http" />
68 + </div>
69 + </div>
70
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
71 + <div class="field">
72 + <div class="field-label">
73 + <div class="field-title">RFC SSH port</div>
74 + <div class="field-description">SSH port for dockerized instance of A0.</div>
75 + </div>
76 + <div class="field-control">
77 + <input type="number" x-model.number="$store.settingsStore.settings.rfc_port_ssh" />
78 + </div>
79 </div>
80 </div>
81 </template>
@@ -143,4 +84,3 @@
84 </div>
85 </body>
86 </html>
146 -
webui/components/settings/developer/developer-settings.html
+9 -25
@@ -9,36 +9,20 @@
9 <div>
10 <nav>
11 <ul>
12 - <template
13 - x-for="(section, index) in $store.settingsStore.filteredSections"
14 - :key="section.id"
15 - >
16 - <li>
17 - <a :href="'#section' + (index + 1)">
18 - <img
19 - :src="'/public/' + section.id + '.svg'"
20 - :alt="section.title"
21 - />
22 - <span x-text="section.title"></span>
23 - </a>
24 - </li>
25 - </template>
12 + <li>
13 + <a href="#section-dev">
14 + <img src="/public/dev.svg" alt="Development" />
15 + <span>Development</span>
16 + </a>
17 + </li>
18 </ul>
19 </nav>
20
29 - <template
30 - x-for="(section, index) in $store.settingsStore.filteredSections"
31 - :key="section.id"
32 - >
33 - <div :id="'section' + (index + 1)" class="section">
34 - <template x-if="section.id === 'dev'">
35 - <x-component path="settings/developer/dev.html"></x-component>
36 - </template>
37 - </div>
38 - </template>
21 + <div id="section-dev" class="section">
22 + <x-component path="settings/developer/dev.html"></x-component>
23 + </div>
24 </div>
25 </template>
26 </div>
27 </body>
28 </html>
44 -
webui/components/settings/external/api-examples.html
+1 -7
@@ -200,13 +200,7 @@
200 let token = "";
201 try {
202 const response = await API.callJsonApi("settings_get", null);
203 - if (response && response.settings && response.settings.sections) {
204 - const mcpSection = response.settings.sections.find(s => s.id === "mcp_server");
205 - if (mcpSection) {
206 - const tokenField = (mcpSection.fields || []).find(f => f.id === "mcp_server_token");
207 - if (tokenField) token = tokenField.value || "";
208 - }
209 - }
203 + token = response?.settings?.mcp_server_token || "";
204 } catch (e) {
205 console.error("Failed to fetch token:", e);
206 }
webui/components/settings/external/api_keys.html
+17 -129
@@ -4,137 +4,26 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('api_keys');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data>
8 + <template x-if="$store.settingsStore.settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
20 -
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
10 + <div class="section-title">API Keys</div>
11 + <div class="section-description">
12 + API keys for model providers and services used by Agent Zero. You can set multiple API keys separated by a comma (,). They will be used in round-robin fashion.<br>
13 + For more information about Agent Zero Venice provider, see <a href="http://agent-zero.ai/?community/api-dashboard/about" target="_blank">Agent Zero Venice</a>.
14 + </div>
15 +
16 + <template x-for="provider in $store.settingsStore.apiKeyProviders" :key="provider.value">
17 + <div class="field">
18 + <div class="field-label">
19 + <div class="field-title" x-text="provider.label"></div>
20 </div>
34 -
21 <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
134 -
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
22 + <input
23 + type="text"
24 + x-model="$store.settingsStore.settings.api_keys[provider.value]"
25 + autocomplete="off"
26 + />
27 </div>
28 </div>
29 </template>
@@ -143,4 +32,3 @@
32 </div>
33 </body>
34 </html>
146 -
webui/components/settings/external/auth.html
+35 -124
@@ -6,135 +6,47 @@
6 <body>
7 <div
8 x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('auth');
13 - },
9 + get settings() { return $store.settingsStore.settings },
10 + get additional() { return $store.settingsStore.additional || {} }
11 }"
12 >
16 - <template x-if="section">
13 + <template x-if="settings">
14 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
15 + <div class="section-title">Authentication</div>
16 + <div class="section-description">
17 + Settings for authentication to use Agent Zero Web UI.
18 + </div>
19 +
20 + <div class="field">
21 + <div class="field-label">
22 + <div class="field-title">UI Login</div>
23 + <div class="field-description">Set user name for web UI</div>
24 + </div>
25 + <div class="field-control">
26 + <input type="text" x-model="settings.auth_login" />
27 + </div>
28 + </div>
29
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
30 + <div class="field">
31 + <div class="field-label">
32 + <div class="field-title">UI Password</div>
33 + <div class="field-description">Set user password for web UI</div>
34 + </div>
35 + <div class="field-control">
36 + <input type="password" autocomplete="off" x-model="settings.auth_password" />
37 + </div>
38 + </div>
39 +
40 + <template x-if="additional.is_dockerized">
41 + <div class="field">
42 + <div class="field-label">
43 + <div class="field-title">root Password</div>
44 + <div class="field-description">
45 + Change linux root password in docker container. This password can be used for SSH access. Original password was randomly generated during setup.
46 + </div>
47 </div>
34 -
48 <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
134 -
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
49 + <input type="password" autocomplete="off" x-model="settings.root_password" />
50 </div>
51 </div>
52 </template>
@@ -143,4 +55,3 @@
55 </div>
56 </body>
57 </html>
146 -
webui/components/settings/external/external-settings.html
+54 -44
@@ -9,22 +9,42 @@
9 <div>
10 <nav>
11 <ul>
12 - <template
13 - x-for="(section, index) in $store.settingsStore.filteredSections"
14 - :key="section.id"
15 - >
16 - <li>
17 - <a :href="'#section' + (index + 1)">
18 - <img
19 - :src="'/public/' + section.id + '.svg'"
20 - :alt="section.title"
21 - />
22 - <span x-text="section.title"></span>
23 - </a>
24 - </li>
25 - </template>
26 -
27 - <!-- Tunnel navigation entry -->
12 + <li>
13 + <a href="#section-api-keys">
14 + <img src="/public/api_keys.svg" alt="API Keys" />
15 + <span>API Keys</span>
16 + </a>
17 + </li>
18 + <li>
19 + <a href="#section-litellm">
20 + <img src="/public/litellm.svg" alt="LiteLLM Global Settings" />
21 + <span>LiteLLM Global Settings</span>
22 + </a>
23 + </li>
24 + <li>
25 + <a href="#section-secrets">
26 + <img src="/public/secrets.svg" alt="Secrets Management" />
27 + <span>Secrets Management</span>
28 + </a>
29 + </li>
30 + <li>
31 + <a href="#section-auth">
32 + <img src="/public/auth.svg" alt="Authentication" />
33 + <span>Authentication</span>
34 + </a>
35 + </li>
36 + <li>
37 + <a href="#section-external-api">
38 + <img src="/public/external_api.svg" alt="External API" />
39 + <span>External API</span>
40 + </a>
41 + </li>
42 + <li>
43 + <a href="#section-update-checker">
44 + <img src="/public/update_checker.svg" alt="Update Checker" />
45 + <span>Update Checker</span>
46 + </a>
47 + </li>
48 <li>
49 <a href="#section-tunnel">
50 <img src="/public/tunnel.svg" alt="Tunnel" />
@@ -34,33 +54,24 @@
54 </ul>
55 </nav>
56
37 - <template
38 - x-for="(section, index) in $store.settingsStore.filteredSections"
39 - :key="section.id"
40 - >
41 - <div :id="'section' + (index + 1)" class="section">
42 - <template x-if="section.id === 'api_keys'">
43 - <x-component path="settings/external/api_keys.html"></x-component>
44 - </template>
45 - <template x-if="section.id === 'litellm'">
46 - <x-component path="settings/external/litellm.html"></x-component>
47 - </template>
48 - <template x-if="section.id === 'secrets'">
49 - <x-component path="settings/external/secrets.html"></x-component>
50 - </template>
51 - <template x-if="section.id === 'auth'">
52 - <x-component path="settings/external/auth.html"></x-component>
53 - </template>
54 - <template x-if="section.id === 'external_api'">
55 - <x-component path="settings/external/external_api.html"></x-component>
56 - </template>
57 - <template x-if="section.id === 'update_checker'">
58 - <x-component
59 - path="settings/external/update_checker.html"
60 - ></x-component>
61 - </template>
62 - </div>
63 - </template>
57 + <div id="section-api-keys" class="section">
58 + <x-component path="settings/external/api_keys.html"></x-component>
59 + </div>
60 + <div id="section-litellm" class="section">
61 + <x-component path="settings/external/litellm.html"></x-component>
62 + </div>
63 + <div id="section-secrets" class="section">
64 + <x-component path="settings/external/secrets.html"></x-component>
65 + </div>
66 + <div id="section-auth" class="section">
67 + <x-component path="settings/external/auth.html"></x-component>
68 + </div>
69 + <div id="section-external-api" class="section">
70 + <x-component path="settings/external/external_api.html"></x-component>
71 + </div>
72 + <div id="section-update-checker" class="section">
73 + <x-component path="settings/external/update_checker.html"></x-component>
74 + </div>
75
76 <!-- Tunnel section content -->
77 <div id="section-tunnel" class="section">
@@ -71,4 +82,3 @@
82 </div>
83 </body>
84 </html>
74 -
webui/components/settings/external/external_api.html
+21 -134
@@ -4,143 +4,30 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('external_api');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
17 - <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
20 -
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
33 - </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
7 + <div x-data>
8 + <div>
9 + <div class="section-title">External API</div>
10 + <div class="section-description">
11 + Agent Zero provides external API endpoints for integration with other applications. These endpoints use API key authentication and support text messages and file attachments.
12 + </div>
13
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
138 - </div>
14 + <div class="field">
15 + <div class="field-label">
16 + <div class="field-title">API Examples</div>
17 + <div class="field-description">
18 + View examples for using Agent Zero's external API endpoints with API key authentication.
19 </div>
140 - </template>
20 + </div>
21 + <div class="field-control">
22 + <button
23 + class="btn btn-field"
24 + @click="$store.settingsStore.handleFieldButton({ id: 'external_api_examples' })"
25 + >
26 + Show API Examples
27 + </button>
28 + </div>
29 </div>
142 - </template>
30 + </div>
31 </div>
32 </body>
33 </html>
146 -
webui/components/settings/external/litellm.html
+16 -132
@@ -4,143 +4,27 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('litellm');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data="{ get settings() { return $store.settingsStore.settings } }">
8 + <template x-if="settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
20 -
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
33 - </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
134 -
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
10 + <div class="section-title">LiteLLM Global Settings</div>
11 + <div class="section-description">
12 + Configure global parameters passed to LiteLLM for all providers.
13 + </div>
14 +
15 + <div class="field field-full">
16 + <div class="field-label">
17 + <div class="field-title">LiteLLM global parameters</div>
18 + <div class="field-description">
19 + Global LiteLLM params (e.g. timeout, stream_timeout) in .env format: one KEY=VALUE per line. Example: <code>stream_timeout=30</code>. Applied to all LiteLLM calls unless overridden. See <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a> and <a href='https://docs.litellm.ai/docs/proxy/timeout' target='_blank'>timeouts</a>.
20 </div>
21 </div>
140 - </template>
22 + <div class="field-control">
23 + <textarea style="height: 12em" x-model="settings.litellm_global_kwargs"></textarea>
24 + </div>
25 + </div>
26 </div>
27 </template>
28 </div>
29 </body>
30 </html>
146 -
webui/components/settings/external/secrets.html
+26 -130
@@ -4,143 +4,39 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('secrets');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data="{ get settings() { return $store.settingsStore.settings } }">
8 + <template x-if="settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
20 -
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
10 + <div class="section-title">Secrets Management</div>
11 + <div class="section-description">
12 + Manage secrets and credentials that agents can use without exposing values to LLMs, chat history or logs. Placeholders are automatically replaced with values just before tool calls. If bare passwords occur in tool results, they are masked back to placeholders.
13 + </div>
14 +
15 + <div class="field field-full">
16 + <div class="field-label">
17 + <div class="field-title">Variables Store</div>
18 + <div class="field-description">
19 + Store non-sensitive variables in .env format e.g. EMAIL_IMAP_SERVER="imap.gmail.com", one item per line. You can use comments starting with # to add descriptions for the agent. See <a href="javascript:openModal('settings/secrets/example-vars.html')">example</a>.<br>These variables are visible to LLMs and in chat history, they are not being masked.
20 </div>
21 + </div>
22 + <div class="field-control">
23 + <textarea style="height: 20em" x-model="settings.variables"></textarea>
24 + </div>
25 + </div>
26
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
134 -
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
27 + <div class="field field-full">
28 + <div class="field-label">
29 + <div class="field-title">Secrets Store</div>
30 + <div class="field-description">
31 + Store secrets and credentials in .env format e.g. EMAIL_PASSWORD="s3cret-p4$$w0rd", one item per line. You can use comments starting with # to add descriptions for the agent. See <a href="javascript:openModal('settings/secrets/example-secrets.html')">example</a>.<br>These variables are not visile to LLMs and in chat history, they are being masked. ⚠️ only values with length >= 4 are being masked to prevent false positives.
32 </div>
33 </div>
140 - </template>
34 + <div class="field-control">
35 + <textarea style="height: 20em" x-model="settings.secrets"></textarea>
36 + </div>
37 + </div>
38 </div>
39 </template>
40 </div>
41 </body>
42 </html>
146 -
webui/components/settings/external/update_checker.html
+18 -133
@@ -4,143 +4,28 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('update_checker');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data="{ get settings() { return $store.settingsStore.settings } }">
8 + <template x-if="settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
20 -
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
33 - </div>
34 -
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
134 -
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
138 - </div>
10 + <div class="section-title">Update Checker</div>
11 + <div class="section-description">
12 + Update checker periodically checks for new releases of Agent Zero and will notify when an update is recommended.<br>No personal data is sent to the update server, only randomized+anonymized unique ID and current version number, which help us evaluate the importance of the update in case of critical bug fixes etc.
13 + </div>
14 +
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">Enable Update Checker</div>
18 + <div class="field-description">Enable update checker to notify about newer versions of Agent Zero.</div>
19 + </div>
20 + <div class="field-control">
21 + <label class="toggle">
22 + <input type="checkbox" x-model="settings.update_check_enabled" />
23 + <span class="toggler"></span>
24 + </label>
25 </div>
140 - </template>
26 + </div>
27 </div>
28 </template>
29 </div>
30 </body>
31 </html>
146 -
webui/components/settings/mcp/client/mcp-servers-store.js
+6 -4
@@ -60,14 +60,16 @@ const model = {
60 },
61
62 getSettingsFieldConfigJson() {
63 - // Use the new settings modal store to access the field
64 - return settingsStore.getField("mcp_client", "mcp_servers");
63 + return settingsStore.settings?.mcp_servers
64 + ?? settingsStore.settings?.mcpServers
65 + ?? "{\n \"mcpServers\": {}\n}";
66 },
67
68 onClose() {
69 const val = this.getEditorValue();
69 - // Update the field value in the settings modal store
70 - settingsStore.setFieldValue("mcp_client", "mcp_servers", val);
70 + if (settingsStore.settings) {
71 + settingsStore.settings.mcp_servers = val;
72 + }
73 this.stopStatusCheck();
74 },
75
webui/components/settings/mcp/mcp-settings.html
+27 -31
@@ -9,42 +9,38 @@
9 <div>
10 <nav>
11 <ul>
12 - <template
13 - x-for="(section, index) in $store.settingsStore.filteredSections"
14 - :key="section.id"
15 - >
16 - <li>
17 - <a :href="'#section' + (index + 1)">
18 - <img
19 - :src="'/public/' + section.id + '.svg'"
20 - :alt="section.title"
21 - />
22 - <span x-text="section.title"></span>
23 - </a>
24 - </li>
25 - </template>
12 + <li>
13 + <a href="#section-mcp-client">
14 + <img src="/public/mcp_client.svg" alt="External MCP Servers" />
15 + <span>External MCP Servers</span>
16 + </a>
17 + </li>
18 + <li>
19 + <a href="#section-mcp-server">
20 + <img src="/public/mcp_server.svg" alt="A0 MCP Server" />
21 + <span>A0 MCP Server</span>
22 + </a>
23 + </li>
24 + <li>
25 + <a href="#section-a2a-server">
26 + <img src="/public/a2a_server.svg" alt="A0 A2A Server" />
27 + <span>A0 A2A Server</span>
28 + </a>
29 + </li>
30 </ul>
31 </nav>
32
29 - <template
30 - x-for="(section, index) in $store.settingsStore.filteredSections"
31 - :key="section.id"
32 - >
33 - <div :id="'section' + (index + 1)" class="section">
34 - <template x-if="section.id === 'mcp_client'">
35 - <x-component path="settings/mcp/mcp_client.html"></x-component>
36 - </template>
37 - <template x-if="section.id === 'mcp_server'">
38 - <x-component path="settings/mcp/mcp_server.html"></x-component>
39 - </template>
40 - <template x-if="section.id === 'a2a_server'">
41 - <x-component path="settings/a2a/a2a-server.html"></x-component>
42 - </template>
43 - </div>
44 - </template>
33 + <div id="section-mcp-client" class="section">
34 + <x-component path="settings/mcp/mcp_client.html"></x-component>
35 + </div>
36 + <div id="section-mcp-server" class="section">
37 + <x-component path="settings/mcp/mcp_server.html"></x-component>
38 + </div>
39 + <div id="section-a2a-server" class="section">
40 + <x-component path="settings/a2a/a2a-server.html"></x-component>
41 + </div>
42 </div>
43 </template>
44 </div>
45 </body>
46 </html>
50 -
webui/components/settings/mcp/mcp_client.html
+40 -129
@@ -4,143 +4,54 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('mcp_client');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data="{ get settings() { return $store.settingsStore.settings } }">
8 + <template x-if="settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
10 + <div class="section-title">External MCP Servers</div>
11 + <div class="section-description">
12 + Agent Zero can use external MCP servers, local or remote as tools.
13 + </div>
14 +
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">MCP Servers Configuration</div>
18 + <div class="field-description">External MCP servers can be configured here.</div>
19 + </div>
20 + <div class="field-control">
21 + <button
22 + class="btn btn-field"
23 + @click="$store.settingsStore.handleFieldButton({ id: 'mcp_servers_config' })"
24 + >
25 + Open
26 + </button>
27 + </div>
28 + </div>
29
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
30 + <div class="field">
31 + <div class="field-label">
32 + <div class="field-title">MCP Client Init Timeout</div>
33 + <div class="field-description">
34 + Timeout for MCP client initialization (in seconds). Higher values might be required for complex MCPs, but might also slowdown system startup.
35 </div>
36 + </div>
37 + <div class="field-control">
38 + <input type="number" x-model.number="settings.mcp_client_init_timeout" />
39 + </div>
40 + </div>
41
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
134 -
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
42 + <div class="field">
43 + <div class="field-label">
44 + <div class="field-title">MCP Client Tool Timeout</div>
45 + <div class="field-description">
46 + Timeout for MCP client tool execution. Higher values might be required for complex tools, but might also result in long responses with failing tools.
47 </div>
48 </div>
140 - </template>
49 + <div class="field-control">
50 + <input type="number" x-model.number="settings.mcp_client_tool_timeout" />
51 + </div>
52 + </div>
53 </div>
54 </template>
55 </div>
56 </body>
57 </html>
146 -
webui/components/settings/mcp/mcp_server.html
+28 -131
@@ -4,143 +4,40 @@
4 </head>
5
6 <body>
7 - <div
8 - x-data="{
9 - get section() {
10 - const store = $store.settingsStore;
11 - if (!store) return null;
12 - return store.getSectionById('mcp_server');
13 - },
14 - }"
15 - >
16 - <template x-if="section">
7 + <div x-data="{ get settings() { return $store.settingsStore.settings } }">
8 + <template x-if="settings">
9 <div>
18 - <div class="section-title" x-text="section.title"></div>
19 - <div class="section-description" x-html="section.description"></div>
20 -
21 - <template
22 - x-for="(field, fieldIndex) in (section.fields || []).filter((f) => !f.hidden)"
23 - :key="fieldIndex"
24 - >
25 - <div :class="{ field: true, 'field-full': field.type === 'textarea' }">
26 - <div class="field-label" x-show="field.title || field.description">
27 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
28 - <div
29 - class="field-description"
30 - x-html="field.description || ''"
31 - x-show="field.description"
32 - ></div>
10 + <div class="section-title">A0 MCP Server</div>
11 + <div class="section-description">
12 + Agent Zero can be exposed as an SSE MCP server. See <a href="javascript:openModal('settings/mcp/server/example.html')">connection example</a>.
13 + </div>
14 +
15 + <div class="field">
16 + <div class="field-label">
17 + <div class="field-title">Enable A0 MCP Server</div>
18 + <div class="field-description">
19 + Expose Agent Zero as an SSE/HTTP MCP server. This will make this A0 instance available to MCP clients.
20 </div>
21 + </div>
22 + <div class="field-control">
23 + <label class="toggle">
24 + <input type="checkbox" x-model="settings.mcp_server_enabled" />
25 + <span class="toggler"></span>
26 + </label>
27 + </div>
28 + </div>
29
35 - <div class="field-control">
36 - <template x-if="field.type === 'text'">
37 - <input
38 - type="text"
39 - :class="field.classes"
40 - :value="field.value"
41 - :readonly="field.readonly === true"
42 - @input="field.value = $event.target.value"
43 - />
44 - </template>
45 -
46 - <template x-if="field.type === 'number'">
47 - <input
48 - type="number"
49 - :class="field.classes"
50 - :value="field.value"
51 - :readonly="field.readonly === true"
52 - @input="field.value = $event.target.value"
53 - :min="field.min"
54 - :max="field.max"
55 - :step="field.step"
56 - />
57 - </template>
58 -
59 - <template x-if="field.type === 'password'">
60 - <input
61 - type="password"
62 - :class="field.classes"
63 - :value="field.value"
64 - :readonly="field.readonly === true"
65 - :id="field.id"
66 - autocomplete="off"
67 - @input="field.value = $event.target.value"
68 - />
69 - </template>
70 -
71 - <template x-if="field.type === 'textarea'">
72 - <textarea
73 - :class="field.classes"
74 - :value="field.value"
75 - :readonly="field.readonly === true"
76 - @input="field.value = $event.target.value"
77 - :style="field.style"
78 - ></textarea>
79 - </template>
80 -
81 - <template x-if="field.type === 'switch'">
82 - <label class="toggle">
83 - <input
84 - type="checkbox"
85 - :checked="field.value"
86 - :disabled="field.readonly === true"
87 - @change="field.value = $event.target.checked"
88 - />
89 - <span class="toggler"></span>
90 - </label>
91 - </template>
92 -
93 - <template x-if="field.type === 'range'">
94 - <div class="field-control">
95 - <input
96 - type="range"
97 - :min="field.min"
98 - :max="field.max"
99 - :step="field.step"
100 - :value="field.value"
101 - :disabled="field.readonly === true"
102 - @input="field.value = $event.target.value"
103 - :class="field.classes"
104 - />
105 - <span class="range-value" x-text="field.value"></span>
106 - </div>
107 - </template>
108 -
109 - <template x-if="field.type === 'button'">
110 - <button
111 - class="btn btn-field"
112 - :class="field.classes"
113 - :disabled="field.readonly === true"
114 - @click="$store.settingsStore.handleFieldButton(field)"
115 - x-text="field.value"
116 - ></button>
117 - </template>
118 -
119 - <template x-if="field.type === 'select'">
120 - <select
121 - :class="field.classes"
122 - x-model="field.value"
123 - :disabled="field.readonly === true"
124 - >
125 - <template x-for="option in field.options" :key="option.value">
126 - <option
127 - :value="option.value"
128 - x-text="option.label"
129 - :selected="option.value === field.value"
130 - ></option>
131 - </template>
132 - </select>
133 - </template>
134 -
135 - <template x-if="field.type === 'html'">
136 - <div :class="field.classes" x-html="field.value"></div>
137 - </template>
138 - </div>
30 + <div class="field field-full">
31 + <div class="field-label">
32 + <div class="field-title">MCP Server Token</div>
33 + <div class="field-description">Token for MCP server authentication.</div>
34 + </div>
35 + <div class="field-control">
36 + <input type="text" readonly x-model="settings.mcp_server_token" />
37 </div>
140 - </template>
38 + </div>
39 </div>
40 </template>
41 </div>
42 </body>
43 </html>
146 -
webui/components/settings/mcp/server/example.html
+1 -7
@@ -43,13 +43,7 @@
43 let token = "";
44 try {
45 const response = await API.callJsonApi("settings_get", null);
46 - if (response && response.settings && response.settings.sections) {
47 - const mcpSection = response.settings.sections.find(s => s.id === "mcp_server");
48 - if (mcpSection) {
49 - const tokenField = (mcpSection.fields || []).find(f => f.id === "mcp_server_token");
50 - if (tokenField) token = tokenField.value || "";
51 - }
52 - }
46 + token = response?.settings?.mcp_server_token || "";
47 } catch (e) {
48 console.error("Failed to fetch token:", e);
49 }
webui/components/settings/settings-store.js
+19 -62
@@ -18,7 +18,7 @@ const model = {
18 isLoading: false,
19 error: null,
20 settings: null,
21 - sectionsById: {},
21 + additional: null,
22
23 // Tab state
24 _activeTab: DEFAULT_TAB,
@@ -48,7 +48,7 @@ const model = {
48 const response = await API.callJsonApi("settings_get", null);
49 if (response && response.settings) {
50 this.settings = response.settings;
51 - this.rebuildSectionsIndex();
51 + this.additional = response.additional || null;
52 } else {
53 throw new Error("Invalid settings response");
54 }
@@ -72,7 +72,7 @@ const model = {
72 schedulerStore.onModalClosed?.();
73
74 this.settings = null;
75 - this.sectionsById = {};
75 + this.additional = null;
76 this.error = null;
77 this.isLoading = false;
78 },
@@ -97,30 +97,22 @@ const model = {
97 this.activeTab = tabName;
98 },
99
100 - // Computed: sections for current tab
101 - get filteredSections() {
102 - if (!this.settings || !this.settings.sections) return [];
103 - const sections = this.settings.sections.filter(
104 - (section) => section.tab === this._activeTab
105 - );
106 - return sections;
107 - },
100
109 - rebuildSectionsIndex() {
110 - const map = {};
111 - if (this.settings && Array.isArray(this.settings.sections)) {
112 - for (const section of this.settings.sections) {
113 - if (section && section.id) {
114 - map[section.id] = section;
115 - }
116 - }
117 - }
118 - this.sectionsById = map;
119 - },
101
121 - getSectionById(sectionId) {
122 - if (!sectionId) return null;
123 - return this.sectionsById[sectionId] || null;
102 + get apiKeyProviders() {
103 + const seen = new Set();
104 + const options = [];
105 + const addProvider = (prov) => {
106 + if (!prov?.value) return;
107 + const key = prov.value.toLowerCase();
108 + if (seen.has(key)) return;
109 + seen.add(key);
110 + options.push({ value: prov.value, label: prov.label || prov.value });
111 + };
112 + (this.additional?.chat_providers || []).forEach(addProvider);
113 + (this.additional?.embedding_providers || []).forEach(addProvider);
114 + options.sort((a, b) => a.label.localeCompare(b.label));
115 + return options;
116 },
117
118 // Save settings
@@ -132,10 +124,10 @@ const model = {
124
125 this.isLoading = true;
126 try {
135 - const response = await API.callJsonApi("settings_set", this.settings);
127 + const response = await API.callJsonApi("settings_set", { settings: this.settings });
128 if (response && response.settings) {
129 this.settings = response.settings;
138 - this.rebuildSectionsIndex();
130 + this.additional = response.additional || this.additional;
131 toast("Settings saved successfully", "success");
132 document.dispatchEvent(
133 new CustomEvent("settings-updated", { detail: response.settings })
@@ -167,41 +159,6 @@ const model = {
159 },
160
161 // Field helpers for external components
170 - getField(sectionId, fieldId) {
171 - if (!this.settings || !this.settings.sections) return null;
172 - for (const section of this.settings.sections) {
173 - if (section.id === sectionId) {
174 - for (const field of section.fields) {
175 - if (field.id === fieldId) {
176 - return field;
177 - }
178 - }
179 - }
180 - }
181 - return null;
182 - },
183 -
184 - setFieldValue(sectionId, fieldId, value) {
185 - const field = this.getField(sectionId, fieldId);
186 - if (field) {
187 - field.value = value;
188 - return true;
189 - }
190 - return false;
191 - },
192 -
193 - findFieldValue(fieldId) {
194 - if (!this.settings || !this.settings.sections) return null;
195 - for (const section of this.settings.sections) {
196 - for (const field of section.fields) {
197 - if (field.id === fieldId) {
198 - return field.value;
199 - }
200 - }
201 - }
202 - return null;
203 - },
204 -
162 // Handle button field clicks (opens sub-modals)
163 async handleFieldButton(field) {
164 if (field.id === "mcp_servers_config") {
webui/components/settings/settings.html
+1 -1
@@ -87,7 +87,7 @@
87 <!-- Footer -->
88 <div class="modal-footer" data-modal-footer x-show="$store.settingsStore.settings">
89 <button class="btn btn-ok"
90 - @click="$store.settingsStore.saveSettings()"
90 + @click="$store.settingsStore.saveAndClose()"
91 :disabled="$store.settingsStore?.isLoading">
92 Save
93 </button>
webui/components/settings/tunnel/tunnel-store.js
+3 -21
@@ -162,27 +162,9 @@ const model = {
162 // Find the auth_login and auth_password in the settings
163 let hasAuth = false;
164
165 - if (authData && authData.settings && authData.settings.sections) {
166 - for (const section of authData.settings.sections) {
167 - if (section.fields) {
168 - const authLoginField = section.fields.find(
169 - (field) => field.id === "auth_login"
170 - );
171 - const authPasswordField = section.fields.find(
172 - (field) => field.id === "auth_password"
173 - );
174 -
175 - if (
176 - authLoginField &&
177 - authPasswordField &&
178 - authLoginField.value &&
179 - authPasswordField.value
180 - ) {
181 - hasAuth = true;
182 - break;
183 - }
184 - }
185 - }
165 + if (authData && authData.settings) {
166 + const { auth_login, auth_password } = authData.settings;
167 + hasAuth = Boolean(auth_login && auth_password);
168 }
169
170 // If no authentication is set, warn the user