1129 lines
38 KiB
HTML
1129 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>
|