botui/ui/suite/settings/organizations.html
Rodrigo Rodriguez (Pragmatismo) dcdc9837e0
Some checks failed
GBCI / build (push) Failing after 9s
feat: add Organizations & Bot Tree settings UI
- Add organizations.html with org selector and bot hierarchy tree
- Add bot configuration form with enabled tabs
- Update PROMPT.md with weekly maintenance checklist
2025-12-18 16:18:09 -03:00

1128 lines
38 KiB
HTML

<!-- Organizations & Bot Tree Settings Section -->
<section id="organizations-section" class="settings-section">
<div class="section-header">
<h1>Organizations & Bots</h1>
<p class="subtitle">Manage your organizations and bot hierarchy</p>
</div>
<!-- Organization Selector -->
<div class="setting-card">
<div class="card-header">
<h2>Current Organization</h2>
<p>Select the organization you want to manage</p>
</div>
<div class="org-selector-container">
<div
class="org-selector"
hx-get="/api/user/organizations"
hx-trigger="load"
hx-target="#org-dropdown-list"
>
<div class="selected-org" id="current-org">
<div class="org-avatar">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
</div>
<div class="org-info">
<span class="org-name">Loading...</span>
<span class="org-role">--</span>
</div>
<svg
class="dropdown-arrow"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
<div
class="org-dropdown"
id="org-dropdown"
style="display: none"
>
<div class="org-dropdown-list" id="org-dropdown-list">
<!-- Organizations loaded via HTMX -->
</div>
<div class="org-dropdown-actions">
<button
type="button"
class="btn-text"
onclick="showCreateOrgModal()"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Create New Organization
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Bot Tree -->
<div class="setting-card">
<div class="card-header">
<h2>Bot Hierarchy</h2>
<p>
Manage your bots and sub-bots. Apps created in Tasks become
sub-bots of the current bot.
</p>
</div>
<div
class="bot-tree-container"
hx-get="/api/bots/tree"
hx-trigger="load, orgChanged from:body"
hx-target="#bot-tree"
>
<div class="bot-tree-toolbar">
<button
type="button"
class="btn-secondary btn-sm"
onclick="expandAllBots()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="15 3 21 3 21 9"></polyline>
<polyline points="9 21 3 21 3 15"></polyline>
<line x1="21" y1="3" x2="14" y2="10"></line>
<line x1="3" y1="21" x2="10" y2="14"></line>
</svg>
Expand All
</button>
<button
type="button"
class="btn-secondary btn-sm"
onclick="collapseAllBots()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="4 14 10 14 10 20"></polyline>
<polyline points="20 10 14 10 14 4"></polyline>
<line x1="14" y1="10" x2="21" y2="3"></line>
<line x1="3" y1="21" x2="10" y2="14"></line>
</svg>
Collapse All
</button>
<button
type="button"
class="btn-primary btn-sm"
onclick="showCreateBotModal()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
New Bot
</button>
</div>
<div class="bot-tree" id="bot-tree">
<!-- Bot tree loaded via HTMX -->
<div class="bot-tree-loading">
<div class="spinner"></div>
<span>Loading bot hierarchy...</span>
</div>
</div>
</div>
</div>
<!-- Selected Bot Configuration -->
<div class="setting-card" id="bot-config-card" style="display: none">
<div class="card-header">
<h2>Bot Configuration</h2>
<p id="bot-config-subtitle">
Configure settings for the selected bot
</p>
</div>
<form
id="bot-config-form"
hx-put="/api/bots/config"
hx-swap="none"
hx-indicator="#saving-bot"
>
<input type="hidden" id="bot-id" name="bot_id" />
<!-- Basic Info -->
<div class="config-section">
<h3>Basic Information</h3>
<div class="form-row">
<div class="form-group">
<label for="bot-name">Bot Name</label>
<input type="text" id="bot-name" name="name" required />
</div>
<div class="form-group">
<label for="bot-description">Description</label>
<input
type="text"
id="bot-description"
name="description"
/>
</div>
</div>
</div>
<!-- Enabled Tabs (only for root bots or based on parent) -->
<div class="config-section" id="tabs-config">
<h3>Enabled Tabs</h3>
<p class="config-note">
Select which tabs are available for this bot. Sub-bots
inherit from parent.
</p>
<div class="tabs-grid">
<label class="tab-checkbox">
<input
type="checkbox"
name="tabs[]"
value="chat"
checked
disabled
/>
<span class="tab-label">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
></path>
</svg>
Chat
</span>
</label>
<label class="tab-checkbox">
<input type="checkbox" name="tabs[]" value="drive" />
<span class="tab-label">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"
></path>
</svg>
Drive
</span>
</label>
<label class="tab-checkbox">
<input type="checkbox" name="tabs[]" value="tasks" />
<span class="tab-label">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M9 11l3 3L22 4"></path>
<path
d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"
></path>
</svg>
Tasks
</span>
</label>
<label class="tab-checkbox">
<input type="checkbox" name="tabs[]" value="calendar" />
<span class="tab-label">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="4"
width="18"
height="18"
rx="2"
ry="2"
></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
Calendar
</span>
</label>
<label class="tab-checkbox">
<input type="checkbox" name="tabs[]" value="mail" />
<span class="tab-label">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
></path>
<polyline points="22,6 12,13 2,6"></polyline>
</svg>
Mail
</span>
</label>
<label class="tab-checkbox">
<input type="checkbox" name="tabs[]" value="meet" />
<span class="tab-label">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polygon
points="23 7 16 12 23 17 23 7"
></polygon>
<rect
x="1"
y="5"
width="15"
height="14"
rx="2"
ry="2"
></rect>
</svg>
Meet
</span>
</label>
<label class="tab-checkbox">
<input
type="checkbox"
name="tabs[]"
value="analytics"
/>
<span class="tab-label">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="20" x2="18" y2="10"></line>
<line x1="12" y1="20" x2="12" y2="4"></line>
<line x1="6" y1="20" x2="6" y2="14"></line>
</svg>
Analytics
</span>
</label>
<label class="tab-checkbox">
<input type="checkbox" name="tabs[]" value="settings" />
<span class="tab-label">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="3"></circle>
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
></path>
</svg>
Settings
</span>
</label>
</div>
</div>
<!-- LLM Configuration -->
<div class="config-section">
<h3>LLM Configuration</h3>
<div class="form-row">
<div class="form-group">
<label for="llm-provider">Provider</label>
<select id="llm-provider" name="llm_provider">
<option value="openai">OpenAI</option>
<option value="anthropic">Anthropic</option>
<option value="google">Google AI</option>
<option value="azure">Azure OpenAI</option>
<option value="ollama">Ollama (Local)</option>
<option value="custom">Custom</option>
</select>
</div>
<div class="form-group">
<label for="llm-model">Model</label>
<input
type="text"
id="llm-model"
name="llm_model"
placeholder="gpt-4o"
/>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="llm-url">API URL (optional)</label>
<input
type="url"
id="llm-url"
name="llm_url"
placeholder="https://api.openai.com/v1"
/>
</div>
<div class="form-group">
<label for="llm-temperature">Temperature</label>
<input
type="number"
id="llm-temperature"
name="llm_temperature"
min="0"
max="2"
step="0.1"
value="0.7"
/>
</div>
</div>
</div>
<!-- Inherit from Parent Toggle -->
<div
class="config-section"
id="inherit-config"
style="display: none"
>
<h3>Configuration Inheritance</h3>
<div class="setting-row">
<div class="setting-info">
<span class="setting-title"
>Inherit Parent Configuration</span
>
<span class="setting-desc"
>When enabled, missing config values are inherited
from parent bot</span
>
</div>
<label class="toggle">
<input
type="checkbox"
name="inherit_parent_config"
checked
/>
<span class="toggle-slider"></span>
</label>
</div>
</div>
<!-- Save Button -->
<div class="form-actions">
<button type="submit" class="btn-primary">
<span class="btn-text">Save Bot Configuration</span>
<span id="saving-bot" class="htmx-indicator"
>Saving...</span
>
</button>
<button
type="button"
class="btn-secondary"
onclick="hideBotConfig()"
>
Cancel
</button>
</div>
</form>
</div>
</section>
<!-- Create Organization Modal -->
<dialog id="create-org-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Create Organization</h2>
<button
type="button"
class="close-btn"
onclick="closeModal('create-org-modal')"
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<form
hx-post="/api/organizations"
hx-target="#org-dropdown-list"
hx-swap="innerHTML"
hx-on::after-request="closeModal('create-org-modal')"
>
<div class="form-group">
<label for="new-org-name">Organization Name</label>
<input
type="text"
id="new-org-name"
name="name"
required
placeholder="My Company"
/>
</div>
<div class="form-group">
<label for="new-org-slug">Slug (URL-friendly name)</label>
<input
type="text"
id="new-org-slug"
name="slug"
required
placeholder="my-company"
pattern="[a-z0-9-]+"
/>
<span class="form-hint"
>Used for drive buckets: org-botname.gbai</span
>
</div>
<div class="modal-footer">
<button
type="button"
class="btn-secondary"
onclick="closeModal('create-org-modal')"
>
Cancel
</button>
<button type="submit" class="btn-primary">
Create Organization
</button>
</div>
</form>
</div>
</dialog>
<!-- Create Bot Modal -->
<dialog id="create-bot-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Create Bot</h2>
<button
type="button"
class="close-btn"
onclick="closeModal('create-bot-modal')"
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<form
hx-post="/api/bots"
hx-target="#bot-tree"
hx-swap="innerHTML"
hx-on::after-request="closeModal('create-bot-modal')"
>
<div class="form-group">
<label for="new-bot-name">Bot Name</label>
<input
type="text"
id="new-bot-name"
name="name"
required
placeholder="My Assistant"
/>
</div>
<div class="form-group">
<label for="new-bot-parent">Parent Bot (optional)</label>
<select id="new-bot-parent" name="parent_bot_id">
<option value="">-- Root Bot (no parent) --</option>
<!-- Options loaded dynamically -->
</select>
<span class="form-hint"
>Sub-bots inherit configuration from their parent</span
>
</div>
<div class="form-group">
<label for="new-bot-description">Description</label>
<textarea
id="new-bot-description"
name="description"
rows="2"
placeholder="What does this bot do?"
></textarea>
</div>
<div class="modal-footer">
<button
type="button"
class="btn-secondary"
onclick="closeModal('create-bot-modal')"
>
Cancel
</button>
<button type="submit" class="btn-primary">Create Bot</button>
</div>
</form>
</div>
</dialog>
<style>
/* Organization Selector */
.org-selector-container {
padding: 1rem;
}
.org-selector {
position: relative;
max-width: 400px;
}
.selected-org {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
}
.selected-org:hover {
border-color: var(--primary);
}
.org-avatar {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--primary);
color: white;
border-radius: 8px;
}
.org-info {
flex: 1;
display: flex;
flex-direction: column;
}
.org-name {
font-weight: 600;
color: var(--text-primary);
}
.org-role {
font-size: 0.75rem;
color: var(--text-secondary);
}
.dropdown-arrow {
color: var(--text-secondary);
transition: transform 0.2s ease;
}
.org-selector.open .dropdown-arrow {
transform: rotate(180deg);
}
.org-dropdown {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 100;
max-height: 300px;
overflow-y: auto;
}
.org-dropdown-list {
padding: 0.5rem;
}
.org-dropdown-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
}
.org-dropdown-item:hover {
background: var(--bg-secondary);
}
.org-dropdown-item.active {
background: var(--primary-light);
}
.org-dropdown-actions {
border-top: 1px solid var(--border-color);
padding: 0.5rem;
}
/* Bot Tree */
.bot-tree-container {
padding: 1rem;
}
.bot-tree-toolbar {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.bot-tree {
border: 1px solid var(--border-color);
border-radius: 8px;
min-height: 200px;
max-height: 400px;
overflow-y: auto;
}
.bot-tree-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
color: var(--text-secondary);
gap: 1rem;
}
.bot-tree-node {
padding: 0.5rem;
}
.bot-tree-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.bot-tree-item:hover {
background: var(--bg-secondary);
}
.bot-tree-item.selected {
background: var(--primary-light);
border: 1px solid var(--primary);
}
.bot-tree-item.root {
font-weight: 600;
}
.bot-toggle {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
cursor: pointer;
}
.bot-toggle.collapsed svg {
transform: rotate(-90deg);
}
.bot-icon {
width: 24px;
height: 24px;
color: var(--primary);
}
.bot-name {
flex: 1;
}
.bot-badge {
font-size: 0.65rem;
padding: 0.125rem 0.375rem;
border-radius: 4px;
background: var(--bg-tertiary);
color: var(--text-secondary);
}
.bot-badge.root {
background: var(--primary);
color: white;
}
.bot-children {
margin-left: 1.5rem;
border-left: 1px dashed var(--border-color);
padding-left: 0.5rem;
}
.bot-children.collapsed {
display: none;
}
/* Configuration Sections */
.config-section {
margin-bottom: 1.5rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--border-color);
}
.config-section:last-child {
border-bottom: none;
margin-bottom: 0;
}
.config-section h3 {
font-size: 1rem;
margin-bottom: 0.75rem;
color: var(--text-primary);
}
.config-note {
font-size: 0.875rem;
color: var(--text-secondary);
margin-bottom: 1rem;
}
/* Tabs Grid */
.tabs-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 0.5rem;
}
.tab-checkbox {
display: flex;
align-items: center;
cursor: pointer;
}
.tab-checkbox input {
display: none;
}
.tab-label {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 0.875rem;
transition: all 0.2s ease;
width: 100%;
}
.tab-checkbox input:checked + .tab-label {
background: var(--primary-light);
border-color: var(--primary);
color: var(--primary);
}
.tab-checkbox input:disabled + .tab-label {
opacity: 0.6;
cursor: not-allowed;
}
/* Form Actions */
.form-actions {
display: flex;
gap: 0.75rem;
padding-top: 1rem;
}
.form-hint {
font-size: 0.75rem;
color: var(--text-secondary);
margin-top: 0.25rem;
}
/* Spinner */
.spinner {
width: 24px;
height: 24px;
border: 2px solid var(--border-color);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Responsive */
@media (max-width: 768px) {
.bot-tree-toolbar {
flex-wrap: wrap;
}
.tabs-grid {
grid-template-columns: repeat(2, 1fr);
}
.form-row {
flex-direction: column;
}
}
</style>
<script>
function toggleOrgDropdown() {
const selector = document.querySelector(".org-selector");
const dropdown = document.getElementById("org-dropdown");
if (dropdown.style.display === "none") {
dropdown.style.display = "block";
selector.classList.add("open");
} else {
dropdown.style.display = "none";
selector.classList.remove("open");
}
}
function selectOrganization(orgId, orgName, orgRole) {
document.querySelector(".org-name").textContent = orgName;
document.querySelector(".org-role").textContent = orgRole;
document.getElementById("org-dropdown").style.display = "none";
document.querySelector(".org-selector").classList.remove("open");
// Trigger bot tree reload
htmx.trigger(document.body, "orgChanged");
// Save to session
fetch("/api/user/current-org", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ org_id: orgId }),
});
}
function showCreateOrgModal() {
document.getElementById("create-org-modal").showModal();
}
function showCreateBotModal() {
// Load available parent bots
fetch("/api/bots/list")
.then((r) => r.json())
.then((bots) => {
const select = document.getElementById("new-bot-parent");
select.innerHTML =
'<option value="">-- Root Bot (no parent) --</option>';
bots.forEach((bot) => {
select.innerHTML += `<option value="${bot.id}">${bot.name}</option>`;
});
});
document.getElementById("create-bot-modal").showModal();
}
function closeModal(id) {
document.getElementById(id).close();
}
function selectBot(botId, botName, isRoot) {
// Highlight selected bot
document.querySelectorAll(".bot-tree-item").forEach((item) => {
item.classList.remove("selected");
});
event.currentTarget.classList.add("selected");
// Show config card
const configCard = document.getElementById("bot-config-card");
configCard.style.display = "block";
document.getElementById("bot-config-subtitle").textContent =
`Configure settings for "${botName}"`;
document.getElementById("bot-id").value = botId;
document.getElementById("bot-name").value = botName;
// Show/hide inheritance option for sub-bots
const inheritConfig = document.getElementById("inherit-config");
const tabsConfig = document.getElementById("tabs-config");
if (isRoot) {
inheritConfig.style.display = "none";
// Root bots can configure all tabs
tabsConfig.querySelectorAll("input").forEach((input) => {
input.disabled = input.value === "chat"; // Chat always enabled
});
} else {
inheritConfig.style.display = "block";
// Sub-bots inherit tabs from parent
tabsConfig.querySelectorAll("input").forEach((input) => {
input.disabled = true;
});
}
// Load bot configuration
fetch(`/api/bots/${botId}/config`)
.then((r) => r.json())
.then((config) => {
document.getElementById("bot-description").value =
config.description || "";
document.getElementById("llm-provider").value =
config.llm_provider || "openai";
document.getElementById("llm-model").value =
config.llm_model || "";
document.getElementById("llm-url").value = config.llm_url || "";
document.getElementById("llm-temperature").value =
config.llm_temperature || 0.7;
// Set enabled tabs
if (config.enabled_tabs) {
tabsConfig.querySelectorAll("input").forEach((input) => {
input.checked = config.enabled_tabs.includes(
input.value,
);
});
}
});
// Scroll to config
configCard.scrollIntoView({ behavior: "smooth", block: "start" });
}
function hideBotConfig() {
document.getElementById("bot-config-card").style.display = "none";
document.querySelectorAll(".bot-tree-item").forEach((item) => {
item.classList.remove("selected");
});
}
function toggleBotChildren(element) {
const node = element.closest(".bot-tree-node");
const children = node.querySelector(".bot-children");
const toggle = node.querySelector(".bot-toggle");
if (children) {
children.classList.toggle("collapsed");
toggle.classList.toggle("collapsed");
}
}
function expandAllBots() {
document.querySelectorAll(".bot-children").forEach((el) => {
el.classList.remove("collapsed");
});
document.querySelectorAll(".bot-toggle").forEach((el) => {
el.classList.remove("collapsed");
});
}
function collapseAllBots() {
document.querySelectorAll(".bot-children").forEach((el) => {
el.classList.add("collapsed");
});
document.querySelectorAll(".bot-toggle").forEach((el) => {
el.classList.add("collapsed");
});
}
// Initialize org dropdown toggle
document.addEventListener("DOMContentLoaded", function () {
const selectedOrg = document.querySelector(".selected-org");
if (selectedOrg) {
selectedOrg.addEventListener("click", toggleOrgDropdown);
}
// Close dropdown when clicking outside
document.addEventListener("click", function (e) {
if (!e.target.closest(".org-selector")) {
const dropdown = document.getElementById("org-dropdown");
if (dropdown) {
dropdown.style.display = "none";
document
.querySelector(".org-selector")
?.classList.remove("open");
}
}
});
});
// Auto-generate slug from org name
document
.getElementById("new-org-name")
?.addEventListener("input", function (e) {
const slug = e.target.value
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
document.getElementById("new-org-slug").value = slug;
});
</script>