//! The General page, row for row after T3 Code's: New threads,
//! Organization, Behavior, Projects & threads, Confirmations, Text
//! generation, About, Diagnostics and the folded Legacy features.
use gpui_kit::component::h_flex;
use gpui_kit::prelude::FluentBuilder as _;
use gpui_kit::*;
use super::widgets::{
RichOption, Section, SettingRow, choice, code, edit, folded, model_control, reasoning_control, reset,
rich_choice, stepper, switch, switch_then, text_field,
};
use super::{Page, SettingsView};
use crate::claude::PermissionMode;
use crate::models::Catalog;
use crate::session::Reasoning;
use crate::settings::{
BackgroundActivity, FollowUp, ProjectOrder, QuitShortcut, SendShortcut, Streaming, Submodules,
TimeFormat, UsageBar, WorkspaceMode,
};
use crate::ui::metal::{self, MetalButton};
use crate::ui::{pickers, workspace};
const WORKSPACES: &[(WorkspaceMode, &str)] =
&[(WorkspaceMode::Local, "Current checkout"), (WorkspaceMode::Worktree, "New worktree")];
const SUBMODULES: &[(Submodules, &str)] =
&[(Submodules::Recursive, "Recursive"), (Submodules::TopLevel, "Top level only"), (Submodules::Skip, "Skip")];
const PROJECT_ORDERS: &[(ProjectOrder, &str)] = &[
(ProjectOrder::LastMessage, "Last user message"),
(ProjectOrder::Added, "Created at"),
(ProjectOrder::Name, "Name"),
];
/// `(notify, sound)`.
const NOTIFICATIONS: &[((bool, bool), &str)] = &[
((false, false), "Off"),
((true, false), "Notifications only"),
((false, true), "Sound only"),
((true, true), "Notifications with sound"),
];
const TIME_FORMATS: &[(TimeFormat, &str)] =
&[(TimeFormat::System, "System default"), (TimeFormat::Hour12, "12-hour"), (TimeFormat::Hour24, "24-hour")];
const STREAMING: &[(Streaming, &str)] = &[
(Streaming::Turn, "Wait for the full response"),
(Streaming::Paragraphs, "Show finished paragraphs"),
(Streaming::Tokens, "Stream every token"),
];
const DIFF_STATES: &[(bool, &str)] = &[(false, "Expanded"), (true, "Collapsed")];
const DIFF_LAYOUTS: &[(bool, &str)] = &[(false, "Stacked"), (true, "Split")];
const SEND_SHORTCUTS: &[(SendShortcut, &str)] = &[
(SendShortcut::Enter, "Enter"),
(SendShortcut::ModEnterMultiline, "Ctrl + Enter for multiline prompts"),
(SendShortcut::ModEnter, "Ctrl + Enter always"),
];
const FOLLOW_UPS: &[(FollowUp, &str)] = &[(FollowUp::Queue, "Queue"), (FollowUp::Steer, "Steer")];
const USAGE_BARS: &[(UsageBar, &str)] = &[(UsageBar::Claude, "Claude"), (UsageBar::Codex, "Codex"), (UsageBar::Off, "Off")];
const BACKGROUND: &[(BackgroundActivity, &str)] = &[
(BackgroundActivity::Balanced, "Balanced"),
(BackgroundActivity::Always, "Performance"),
(BackgroundActivity::Minimal, "Battery saver"),
];
const QUIT_SHORTCUTS: &[(QuitShortcut, &str)] =
&[(QuitShortcut::Press, "Direct"), (QuitShortcut::Hold, "Hold"), (QuitShortcut::Double, "Double press")];
/// T3 Code's description of each permission mode.
fn permission_description(mode: PermissionMode) -> &'static str {
match mode {
PermissionMode::Default => "Ask before commands and file changes.",
PermissionMode::AcceptEdits => "Auto-approve edits, ask before other actions.",
PermissionMode::Auto => "Supported providers approve routine actions; others still ask.",
PermissionMode::Plan => "Plan first, without editing files or running commands.",
PermissionMode::BypassPermissions => "Allow commands and edits without prompts.",
}
}
impl SettingsView {
pub(super) fn render_general(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Vec<AnyElement> {
let p = metal::palette(cx);
let saved = super::scope::shown_settings(cx);
let view = cx.weak_entity();
let row = SettingRow::new;
// ---- New threads ----
let model_pick = {
let view = view.clone();
move |key: Option<String>, window: &mut Window, cx: &mut App| {
edit(&view, window, cx, move |settings| settings.default_model = key.clone())
}
};
let reasoning_pick = {
let view = view.clone();
move |reasoning: Option<Reasoning>, window: &mut Window, cx: &mut App| {
edit(&view, window, cx, move |settings| {
settings.default_reasoning = reasoning.map(|reasoning| reasoning.key().to_string())
})
}
};
let mut permissions = vec![RichOption {
value: None,
icon: None,
label: "Automatic",
description: "Start with the permissions of the thread you have open.",
}];
permissions.extend(PermissionMode::ALL.into_iter().map(|mode| RichOption {
value: Some(mode),
icon: Some(pickers::access_icon(mode)),
label: mode.label(),
description: permission_description(mode),
}));
let current_access = saved.default_access.as_deref().and_then(PermissionMode::from_cli);
let new_threads = Section::new("New threads")
.id("project-defaults")
.row(
row("Model")
.id("default-model")
.description("Default model for new threads. Projects can override it.")
.when(saved.default_model.is_none(), |row| row.status("Automatic"))
.reset(reset(&view, cx, |s| (s.default_model.clone(), s.default_reasoning.clone()), |s, (model, reasoning)| {
s.default_model = model;
s.default_reasoning = reasoning;
}))
.control(
h_flex()
.gap(px(6.))
.child(model_control("default-model", saved.default_model.clone(), "Automatic", false, model_pick, cx))
.child(reasoning_control(
"default-reasoning",
saved.default_reasoning.as_deref().and_then(Reasoning::from_key),
"Automatic",
reasoning_pick,
)),
),
)
.row(
row("Permissions")
.id("default-permissions")
.description("Default permissions for new threads. Pi and jcode threads always run with full access.")
.reset(reset(&view, cx, |s| s.default_access.clone(), |s, access| s.default_access = access))
.control(rich_choice("default-access", current_access, permissions, &view, |settings, mode| {
settings.default_access = mode.map(|mode: PermissionMode| mode.as_cli().to_string())
})),
)
.row(
row("Workspace")
.id("new-threads")
.description("Where new threads start. The start screen can switch it for each thread.")
.reset(reset(&view, cx, |s| s.workspace, |s, mode| s.workspace = mode))
.control(choice("workspace", saved.workspace, WORKSPACES, &view, |settings, mode| settings.workspace = mode)),
)
.row(
row("Submodules")
.id("worktree-submodules")
.description("How new worktrees populate git submodules.")
.reset(reset(&view, cx, |s| s.submodules, |s, mode| s.submodules = mode))
.control(choice("submodules", saved.submodules, SUBMODULES, &view, |settings, mode| settings.submodules = mode)),
)
.render(cx);
// ---- Organization ----
let mut organization = Section::new("Organization")
.row(
row("Project grouping")
.id("project-grouping")
.description("Combine matching repositories across environments.")
.reset(reset(&view, cx, |s| s.project_grouping, |s, grouping| s.project_grouping = grouping))
// On groups by repository, as T3 Code's switch does; off keeps every project apart.
.control(switch(
"project-grouping",
saved.project_grouping != crate::settings::ProjectGrouping::Separate,
&view,
p,
|settings, on| {
settings.project_grouping = if on {
crate::settings::ProjectGrouping::Repository
} else {
crate::settings::ProjectGrouping::Separate
}
},
)),
)
.row(
row("Project order")
.id("project-order")
.description("Order of projects in the sidebar project picker and command palette.")
.reset(reset(&view, cx, |s| s.project_order, |s, order| s.project_order = order))
.control(choice("project-order", saved.project_order, PROJECT_ORDERS, &view, |settings, order| {
settings.project_order = order
})),
)
.row(
row("Auto-resume limited threads")
.id("auto-resume-limited-threads")
.description(
"Resume usage-limit stops at the reported reset time. Each thread can cancel its scheduled continuation.",
)
.reset(reset(&view, cx, |s| s.auto_resume_limited, |s, on| s.auto_resume_limited = on))
.control(switch("auto-resume", saved.auto_resume_limited, &view, p, |settings, on| {
settings.auto_resume_limited = on
})),
)
.row(
row("Snooze limited threads")
.id("snooze-limited-threads")
.description(
"Snooze usage-limit stops until the reported reset time. Combine with auto-resume to continue when they wake.",
)
.reset(reset(&view, cx, |s| s.snooze_limited, |s, on| s.snooze_limited = on))
.control(switch("snooze-limited", saved.snooze_limited, &view, p, |settings, on| settings.snooze_limited = on)),
)
.row(
row("Working section (beta)")
.id("working-shelf")
.description(
"Fold working and monitoring threads into a Working section. They return to the top of the inbox when they need you.",
)
.reset(reset(&view, cx, |s| s.working_section, |s, on| s.working_section = on))
.control(switch("working-section", saved.working_section, &view, p, |settings, on| {
settings.working_section = on
})),
)
.row(
row("Auto-settle merged threads")
.id("auto-settle-merged-threads")
.description("Settle a thread when its pull request merges. Closed pull requests still settle automatically.")
.reset(reset(&view, cx, |s| s.auto_settle_merged, |s, on| s.auto_settle_merged = on))
.control(switch("auto-settle-merged", saved.auto_settle_merged, &view, p, |settings, on| {
settings.auto_settle_merged = on
})),
)
.row(
row("Auto-settle inactive threads")
.id("auto-settle-inactive-threads")
.description("Sidebar threads with no activity for this long settle automatically.")
.reset(reset(&view, cx, |s| (s.auto_settle_inactive, s.inactive_days), |s, (on, days)| {
s.auto_settle_inactive = on;
s.inactive_days = days;
}))
.control(switch("auto-settle-inactive", saved.auto_settle_inactive, &view, p, |settings, on| {
settings.auto_settle_inactive = on;
if on {
settings.inactive_days = 3;
}
})),
);
if saved.auto_settle_inactive {
organization = organization.row(
row("Days of inactivity before auto-settle")
.id("days-before-auto-settle")
.description("Any new activity un-settles a thread automatically.")
.control(stepper("inactive-days", u64::from(saved.inactive_days), 1, (1, 90), "", &view, p, |settings, days| {
settings.inactive_days = days as u32
})),
);
}
let organization = organization.render(cx);
// ---- Behavior ----
let can_notify = workspace::find_program(&["notify-send"]).is_some();
let notification_mode = (saved.notify, saved.notify_sound);
let streaming_description = match saved.streaming {
Streaming::Turn => "Text appears once the agent finishes its turn.",
Streaming::Paragraphs => "Each paragraph or code block appears as soon as it is complete.",
Streaming::Tokens => "Text appears token by token as the agent writes it.",
};
let follow_up_description = format!(
"Queue follow-ups while the agent runs or steer the current run. {}",
match saved.send_shortcut {
SendShortcut::Enter => "Press Ctrl + Enter to do the opposite for one message.",
SendShortcut::ModEnter => "Press Ctrl + Shift + Enter to do the opposite for one message.",
SendShortcut::ModEnterMultiline => {
"Press Ctrl + Enter for single-line prompts or Ctrl + Shift + Enter for multiline prompts to do the opposite for one message."
}
}
);
let background_description = match saved.background_activity {
BackgroundActivity::Balanced => {
"Pauses health checks and git refreshes while the window has been idle for a while, or in low power mode."
}
BackgroundActivity::Always => "Allows background checks whenever the app is open.",
BackgroundActivity::Minimal => "Also pauses background checks whenever the window is in the background.",
};
let behavior = Section::new("Behavior")
.row(
row("Thread notifications")
.id("thread-notifications")
.description(if can_notify || notification_mode.0 == false {
"System alerts when a thread finishes, fails, or needs input or approval. Applies to this device while Vectrum Code is open."
} else {
"Notifications need notify-send (libnotify), which isn't installed. Sound only is still available."
})
.reset(reset(&view, cx, |s| (s.notify, s.notify_sound), |s, (notify, sound)| {
s.notify = notify;
s.notify_sound = sound;
}))
.control(choice("thread-notifications", notification_mode, NOTIFICATIONS, &view, |settings, (notify, sound)| {
settings.notify = notify;
settings.notify_sound = sound;
})),
)
.row(
row("In-app notifications")
.id("in-app-notifications")
.description(
"Show a toast when another thread finishes, fails, or needs input or approval while this app has focus.",
)
.control(switch("in-app-notifications", saved.in_app_notifications, &view, p, |settings, on| {
settings.in_app_notifications = on
})),
)
.row(
row("Time format")
.id("time-format")
.description("System default follows your OS clock preference.")
.reset(reset(&view, cx, |s| s.time_format, |s, format| s.time_format = format))
.control(choice("time-format", saved.time_format, TIME_FORMATS, &view, |settings, format| {
settings.time_format = format
})),
)
.row(
row("Response streaming")
.id("response-streaming")
.description(streaming_description)
.reset(reset(&view, cx, |s| s.streaming, |s, streaming| s.streaming = streaming))
.control(choice("streaming", saved.streaming, STREAMING, &view, |settings, streaming| {
settings.streaming = streaming
})),
)
.row(
row("Fold finished turns")
.id("fold-turns")
.description("Tuck a finished turn's tool calls and interim messages into one \u{201c}Worked for\u{201d} row.")
.reset(reset(&view, cx, |s| s.fold_turns, |s, on| s.fold_turns = on))
.control(switch("fold-turns", saved.fold_turns, &view, p, |settings, on| settings.fold_turns = on)),
)
.row(
row("Show costs")
.id("show-costs")
.description("What each turn and thread cost, in the transcript and the project panel.")
.reset(reset(&view, cx, |s| s.show_costs, |s, on| s.show_costs = on))
.control(switch("show-costs", saved.show_costs, &view, p, |settings, on| settings.show_costs = on)),
)
.row(
row("Hide whitespace changes")
.id("hide-whitespace-changes")
.description("Set whether the diff panel ignores whitespace-only edits by default.")
.reset(reset(&view, cx, |s| s.hide_whitespace, |s, on| s.hide_whitespace = on))
.control(switch("hide-whitespace", saved.hide_whitespace, &view, p, |settings, on| {
settings.hide_whitespace = on
})),
)
.row(
row("Default diff file state")
.id("default-diff-file-state")
.description("Start with files expanded or collapsed when opening diffs.")
.reset(reset(&view, cx, |s| s.diff_collapsed, |s, collapsed| s.diff_collapsed = collapsed))
.control(choice("diff-collapsed", saved.diff_collapsed, DIFF_STATES, &view, |settings, collapsed| {
settings.diff_collapsed = collapsed
})),
)
.row(
row("Diff layout")
.id("diff-layout")
.description("Show diffs stacked or side by side. The toggle in the diff toolbar changes this too.")
.reset(reset(&view, cx, |s| s.diff_side_by_side, |s, split| s.diff_side_by_side = split))
.control(choice("diff-layout", saved.diff_side_by_side, DIFF_LAYOUTS, &view, |settings, split| {
settings.diff_side_by_side = split
})),
)
.row(
row("Proactive panels")
.id("proactive-panels")
.description("Open Changes for edits to at least 3 files or 50 lines.")
.reset(reset(&view, cx, |s| s.proactive_panels, |s, on| s.proactive_panels = on))
.control(switch("proactive-panels", saved.proactive_panels, &view, p, |settings, on| {
settings.proactive_panels = on
})),
)
.row(
row("Show skills in slash menu")
.id("skills-in-slash-menu")
.description("Also include skills in the / command menu. Skills always appear when you type $.")
.reset(reset(&view, cx, |s| s.slash_skills, |s, on| s.slash_skills = on))
.control(switch("slash-skills", saved.slash_skills, &view, p, |settings, on| settings.slash_skills = on)),
)
.row(
row("Rich text composer")
.id("composer-rich-text")
.description("Show formatted Markdown as you type.")
.reset(reset(&view, cx, |s| s.rich_composer, |s, on| s.rich_composer = on))
.control(switch("rich-composer", saved.rich_composer, &view, p, |settings, on| settings.rich_composer = on)),
)
.row(
row("Collapse composer on scroll")
.id("composer-collapse")
.description(
"Rest the composer of an existing thread into a single line when you scroll the conversation. Focus the composer or start typing to expand it again.",
)
.reset(reset(&view, cx, |s| s.collapse_composer, |s, on| s.collapse_composer = on))
.control(switch("collapse-composer", saved.collapse_composer, &view, p, |settings, on| {
settings.collapse_composer = on
})),
)
.row(
row("Send shortcut")
.id("send-shortcut")
.description("Choose when Enter sends a prompt or inserts a new line")
.reset(reset(&view, cx, |s| s.send_shortcut, |s, shortcut| s.send_shortcut = shortcut))
.control(choice("send-shortcut", saved.send_shortcut, SEND_SHORTCUTS, &view, |settings, shortcut| {
settings.send_shortcut = shortcut
})),
)
.row(
row("Follow-up behavior")
.id("follow-up-behavior")
.description(follow_up_description)
.reset(reset(&view, cx, |s| s.follow_up, |s, follow_up| s.follow_up = follow_up))
.control(choice("follow-up", saved.follow_up, FOLLOW_UPS, &view, |settings, follow_up| {
settings.follow_up = follow_up
})),
)
.row(
row("Usage bar")
.id("usage-bar")
.description("Show plan limits, context and output speed under the composer, with limits from Claude or Codex.")
.reset(reset(&view, cx, |s| s.usage_bar, |s, bar| s.usage_bar = bar))
.control(choice("usage-bar", saved.usage_bar, USAGE_BARS, &view, |settings, bar| settings.usage_bar = bar)),
)
.row(
row("Provider update checks")
.id("provider-update-checks")
.description("Check installed provider CLIs for newer available versions.")
.reset(reset(&view, cx, |s| s.update_checks, |s, on| s.update_checks = on))
.control(switch_then(
"update-checks",
saved.update_checks,
&view,
p,
|settings, on| settings.update_checks = on,
Catalog::refresh_all,
)),
)
.row(
row("Continue threads after restarts")
.id("continue-threads-after-server-update")
.description("Automatically resume interrupted threads after an update, crash, or machine restart.")
.reset(reset(&view, cx, |s| s.continue_after_restart, |s, on| s.continue_after_restart = on))
.control(switch("continue-after-restart", saved.continue_after_restart, &view, p, |settings, on| {
settings.continue_after_restart = on
})),
)
.row(
row("Background activity")
.id("background-activity")
.description(background_description)
.reset(reset(&view, cx, |s| s.background_activity, |s, activity| s.background_activity = activity))
.control(choice("background-activity", saved.background_activity, BACKGROUND, &view, |settings, activity| {
settings.background_activity = activity
})),
)
.render(cx);
// ---- Projects & threads ----
let projects = Section::new("Projects & threads")
.row(
row("Start from origin")
.id("start-from-origin")
.description("Creates the worktree from the latest matching branch on origin instead of your local branch.")
.reset(reset(&view, cx, |s| s.start_from_origin, |s, on| s.start_from_origin = on))
.control(switch("start-from-origin", saved.start_from_origin, &view, p, |settings, on| {
settings.start_from_origin = on
})),
)
.row(
row("Add project starts in")
.id("add-project-starts-in")
.description("Leave empty to use \u{201c}~/\u{201d} when the Add Project browser opens.")
.reset(reset(&view, cx, |s| s.add_project_dir.clone(), |s, dir| s.add_project_dir = dir))
.control(text_field(&self.add_project_dir, 288., window, cx)),
)
.render(cx);
// ---- Confirmations ----
let confirmations = Section::new("Confirmations")
.row(
row("Unpin confirmation")
.id("unpin-confirmation")
.description("Ask before unpinning a thread from the pinned section.")
.reset(reset(&view, cx, |s| s.confirm_unpin, |s, on| s.confirm_unpin = on))
.control(switch("confirm-unpin", saved.confirm_unpin, &view, p, |settings, on| settings.confirm_unpin = on)),
)
.row(
row("Archive confirmation")
.id("archive-confirmation")
.description("Require a second click on the inline archive action before a thread is archived.")
.reset(reset(&view, cx, |s| s.confirm_settle, |s, on| s.confirm_settle = on))
.control(switch("confirm-settle", saved.confirm_settle, &view, p, |settings, on| settings.confirm_settle = on)),
)
.row(
row("Delete confirmation")
.id("delete-confirmation")
.description("Ask before deleting a thread and its chat history.")
.reset(reset(&view, cx, |s| s.confirm_delete, |s, on| s.confirm_delete = on))
.control(switch("confirm-delete", saved.confirm_delete, &view, p, |settings, on| settings.confirm_delete = on)),
)
.row(
row("Quit shortcut")
.id("quit-confirmation")
.description("Hold mode also quits on two quick presses.")
.reset(reset(&view, cx, |s| s.quit_shortcut, |s, shortcut| s.quit_shortcut = shortcut))
.control(choice("quit-shortcut", saved.quit_shortcut, QUIT_SHORTCUTS, &view, |settings, shortcut| {
settings.quit_shortcut = shortcut
})),
)
.render(cx);
// ---- Text generation ----
let text_pick = {
let view = view.clone();
move |key: Option<String>, window: &mut Window, cx: &mut App| {
edit(&view, window, cx, move |settings| settings.text_model = key.clone().unwrap_or_default())
}
};
let text_reasoning_pick = {
let view = view.clone();
move |reasoning: Option<Reasoning>, window: &mut Window, cx: &mut App| {
edit(&view, window, cx, move |settings| {
settings.text_reasoning = reasoning.map(|reasoning| reasoning.key().to_string())
})
}
};
let text_model = Some(saved.text_model.clone()).filter(|key| !key.is_empty());
let text_generation = Section::new("Text generation")
.row(
row("Text generation model")
.id("text-generation-model")
.description("Used for thread titles and worktree branch names. Off keeps the first line of the first message.")
.reset(reset(&view, cx, |s| (s.text_model.clone(), s.text_reasoning.clone()), |s, (model, reasoning)| {
s.text_model = model;
s.text_reasoning = reasoning;
}))
.control(
h_flex()
.gap(px(6.))
.child(model_control("text-model", text_model.clone(), "Off", true, text_pick, cx))
.when(text_model.is_some(), |this| {
this.child(reasoning_control(
"text-reasoning",
saved.text_reasoning.as_deref().and_then(Reasoning::from_key),
"Default",
text_reasoning_pick,
))
}),
),
)
.render(cx);
// ---- About ----
let about = Section::new("About")
.row(
row("Version")
.id("version")
.title_suffix(code(env!("CARGO_PKG_VERSION"), p))
.description("Current version of the application."),
)
.render(cx);
// ---- Diagnostics ----
let diagnostics = Section::new("Diagnostics")
.row(
row("Diagnostics")
.id("diagnostics")
.description("Inspect processes, resource use, and logs on this environment.")
.control(
MetalButton::new("view-diagnostics")
.compact()
.label("View diagnostics")
.on_click(cx.listener(|this, _, _, cx| this.open_page(Page::Diagnostics, cx))),
),
)
.row(
row("Open source licenses")
.id("open-source-licenses")
.description("Notices for dependencies, assets, and optional tools used by Vectrum Code.")
.control(
MetalButton::new("view-licenses")
.compact()
.label("View licenses")
.on_click(cx.listener(|this, _, _, cx| this.open_page(Page::Licenses, cx))),
),
)
.render(cx);
// ---- Legacy features ----
let legacy = folded(
"legacy",
"Legacy features",
self.open_folds.contains("legacy"),
vec![
row("Plan mode (legacy)")
.id("legacy-plan-mode")
.description("Restore Build/Plan, /plan, /default, and Shift+Tab. Off uses build mode.")
.reset(reset(&view, cx, |s| s.plan_mode, |s, on| s.plan_mode = on))
.control(switch("plan-mode", saved.plan_mode, &view, p, |settings, on| settings.plan_mode = on))
.into_any_element(),
row("Context window indicator (legacy)")
.id("legacy-context-window-indicator")
.description("Shows context window usage as a circular indicator in the composer.")
.reset(reset(&view, cx, |s| s.context_meter, |s, on| s.context_meter = on))
.control(switch("context-meter", saved.context_meter, &view, p, |settings, on| settings.context_meter = on))
.into_any_element(),
row("Sidebar (legacy)")
.id("legacy-sidebar")
.description("Restore per-project thread trees instead of the default flat sidebar.")
.reset(reset(&view, cx, |s| s.legacy_sidebar, |s, on| s.legacy_sidebar = on))
.control(switch("legacy-sidebar", saved.legacy_sidebar, &view, p, |settings, on| settings.legacy_sidebar = on))
.into_any_element(),
],
&view,
p,
);
vec![new_threads, organization, behavior, projects, confirmations, text_generation, about, diagnostics, legacy]
}
}