d suggestions

- ADD SUGGESTION TOOL "name" WITH params AS "text" for pre-filled
    params

- Add secrets module for Vault integration with minimal .env approach

- Update LLM providers documentation with model recommendations

- Refactor template dialogs for consistency:
  - Use PARAM with proper types and DESCRIPTION
  - Use WITH blocks for structured data
  - Simplify TALK messages (remove emoji prefixes)
  - Add RETURN statements to tools
  - Add proper CLEAR SUGGESTIONS and ADD TOOL patterns

- Add analytics-dashboard template demonstrating KB Statistics usage ```
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-11-30 16:40:11 -03:00
parent 5d21bba1e1
commit 10e578b1a3
18 changed files with 2236 additions and 326 deletions

View file

@ -1,96 +1,226 @@
<svg width="800" height="400" xmlns="http://www.w3.org/2000/svg">
<svg width="1400" height="900" xmlns="http://www.w3.org/2000/svg">
<style>
/* Light theme defaults */
.neon-blue { stroke: #4A90E2; stroke-width: 2.6; fill: none; }
.neon-orange { stroke: #F5A623; stroke-width: 2.6; fill: none; }
.neon-purple { stroke: #BD10E0; stroke-width: 2.6; fill: none; }
.neon-green { stroke: #7ED321; stroke-width: 2.6; fill: none; }
.neon-cyan { stroke: #50E3C2; stroke-width: 2.6; fill: none; }
.neon-gray { stroke: #666666; stroke-width: 2.6; fill: none; }
.main-text { fill: #1a1a1a; font-family: Arial, sans-serif; }
.secondary-text { fill: #666; font-family: Arial, sans-serif; }
.arrow-color { stroke: #666; fill: #666; }
/* Dark theme with subtle neon effects */
@media (prefers-color-scheme: dark) {
.neon-blue {
stroke: #00D4FF;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00D4FF) drop-shadow(0 0 8px #00A0FF);
}
.neon-orange {
stroke: #FF9500;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #FF9500) drop-shadow(0 0 8px #FF7700);
}
.neon-purple {
stroke: #E040FB;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #E040FB) drop-shadow(0 0 8px #D500F9);
}
.neon-green {
stroke: #00FF88;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00FF88) drop-shadow(0 0 8px #00E676);
}
.neon-cyan {
stroke: #00E5EA;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00E5EA) drop-shadow(0 0 8px #00BCD4);
}
.neon-gray {
stroke: #888888;
stroke-width: 2.8;
filter: drop-shadow(0 0 2px #888888);
}
.main-text { fill: #FFFFFF; }
.secondary-text { fill: #B0B0B0; }
.arrow-color { stroke: #B0B0B0; fill: #B0B0B0; }
}
</style>
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#666"/>
<marker id="arrow" markerWidth="13" markerHeight="13" refX="11.7" refY="3.9" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,7.8 L11.7,3.9 z" class="arrow-color"/>
</marker>
<linearGradient id="flowGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#4A90E2;stop-opacity:0.3" />
<stop offset="25%" style="stop-color:#F5A623;stop-opacity:0.3" />
<stop offset="50%" style="stop-color:#BD10E0;stop-opacity:0.3" />
<stop offset="75%" style="stop-color:#7ED321;stop-opacity:0.3" />
<stop offset="100%" style="stop-color:#50E3C2;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- Title -->
<text x="400" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" font-weight="600" fill="#333">General Bots Architecture</text>
<text x="700" y="45" text-anchor="middle" font-size="32" font-weight="600" class="main-text">General Bots Architecture</text>
<text x="700" y="75" text-anchor="middle" font-size="21" class="secondary-text">Single binary with everything included - no external dependencies</text>
<!-- Top Layer Components -->
<!-- Phase Labels -->
<text x="180" y="115" text-anchor="middle" font-size="21" font-weight="500" class="secondary-text">Interface Layer</text>
<text x="700" y="115" text-anchor="middle" font-size="21" font-weight="500" class="secondary-text">Core Runtime</text>
<text x="1220" y="115" text-anchor="middle" font-size="21" font-weight="500" class="secondary-text">Processing</text>
<!-- MAIN FLOW DIAGRAM (Upper Section) -->
<g id="main-flow">
<!-- Top Layer - Interface Components -->
<!-- Web Server -->
<rect x="20" y="60" width="100" height="40" fill="none" stroke="#4A90E2" stroke-width="2" rx="5"/>
<text x="70" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Web Server</text>
<!-- BASIC Interpreter -->
<rect x="160" y="60" width="120" height="40" fill="none" stroke="#F5A623" stroke-width="2" rx="5"/>
<text x="220" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">BASIC Interpreter</text>
<!-- LLM Integration -->
<rect x="320" y="60" width="100" height="40" fill="none" stroke="#BD10E0" stroke-width="2" rx="5"/>
<text x="370" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">LLM Integration</text>
<!-- Package Manager -->
<rect x="460" y="60" width="120" height="40" fill="none" stroke="#7ED321" stroke-width="2" rx="5"/>
<text x="520" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Package Manager</text>
<rect x="50" y="140" width="130" height="60" class="neon-blue" rx="6.5"/>
<text x="115" y="178" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Web Server</text>
<!-- Console UI -->
<rect x="620" y="60" width="100" height="40" fill="none" stroke="#50E3C2" stroke-width="2" rx="5"/>
<text x="670" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Console UI</text>
<rect x="200" y="140" width="130" height="60" class="neon-cyan" rx="6.5"/>
<text x="265" y="178" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Console UI</text>
<!-- Session Manager (Middle Layer) -->
<rect x="250" y="160" width="300" height="40" fill="none" stroke="#4A90E2" stroke-width="2" rx="5"/>
<text x="400" y="185" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Session Manager (Tokio Async Runtime)</text>
<!-- BASIC Interpreter -->
<rect x="480" y="140" width="180" height="60" class="neon-orange" rx="6.5"/>
<text x="570" y="178" text-anchor="middle" font-size="22" font-weight="500" class="main-text">BASIC Interpreter</text>
<!-- LLM Integration -->
<rect x="680" y="140" width="160" height="60" class="neon-purple" rx="6.5"/>
<text x="760" y="178" text-anchor="middle" font-size="22" font-weight="500" class="main-text">LLM Integration</text>
<!-- Package Manager -->
<rect x="860" y="140" width="170" height="60" class="neon-green" rx="6.5"/>
<text x="945" y="178" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Package Manager</text>
<!-- BotModels -->
<rect x="1135" y="140" width="160" height="60" class="neon-purple" rx="6.5"/>
<text x="1215" y="178" text-anchor="middle" font-size="22" font-weight="500" class="main-text">BotModels (AI)</text>
<!-- Middle Layer - Session Manager -->
<rect x="350" y="280" width="400" height="60" class="neon-blue" rx="6.5"/>
<text x="550" y="318" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Session Manager (Tokio Async Runtime)</text>
<!-- Channels Box -->
<rect x="800" y="280" width="150" height="60" class="neon-cyan" rx="6.5"/>
<text x="875" y="318" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Channels</text>
<!-- External API -->
<rect x="980" y="280" width="140" height="60" class="neon-orange" rx="6.5"/>
<text x="1050" y="318" text-anchor="middle" font-size="22" font-weight="500" class="main-text">External API</text>
<!-- Data Layer Components -->
<!-- PostgreSQL -->
<rect x="20" y="260" width="100" height="40" fill="none" stroke="#F5A623" stroke-width="2" rx="5"/>
<text x="70" y="285" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">PostgreSQL</text>
<rect x="50" y="420" width="140" height="60" class="neon-orange" rx="6.5"/>
<text x="120" y="458" text-anchor="middle" font-size="22" font-weight="500" class="main-text">PostgreSQL</text>
<!-- Valkey Cache -->
<rect x="160" y="260" width="100" height="40" fill="none" stroke="#BD10E0" stroke-width="2" rx="5"/>
<text x="210" y="285" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Valkey Cache</text>
<rect x="210" y="420" width="140" height="60" class="neon-purple" rx="6.5"/>
<text x="280" y="458" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Valkey Cache</text>
<!-- Qdrant Vectors -->
<rect x="300" y="260" width="100" height="40" fill="none" stroke="#7ED321" stroke-width="2" rx="5"/>
<text x="350" y="285" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Qdrant Vectors</text>
<rect x="370" y="420" width="150" height="60" class="neon-green" rx="6.5"/>
<text x="445" y="458" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Qdrant Vectors</text>
<!-- Object Storage -->
<rect x="440" y="260" width="100" height="40" fill="none" stroke="#50E3C2" stroke-width="2" rx="5"/>
<text x="490" y="285" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Object Storage</text>
<!-- SeaweedFS -->
<rect x="540" y="420" width="150" height="60" class="neon-cyan" rx="6.5"/>
<text x="615" y="458" text-anchor="middle" font-size="22" font-weight="500" class="main-text">SeaweedFS</text>
<!-- Channels -->
<rect x="580" y="260" width="100" height="40" fill="none" stroke="#4A90E2" stroke-width="2" rx="5"/>
<text x="630" y="285" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Channels</text>
<!-- Storage Contents Box -->
<rect x="720" y="420" width="350" height="60" class="neon-gray" rx="6.5"/>
<text x="895" y="448" text-anchor="middle" font-size="18" class="secondary-text">.gbkb (Docs) | .gbdialog (Scripts) | .gbot (Config)</text>
<text x="895" y="470" text-anchor="middle" font-size="18" class="secondary-text">Templates | User Assets</text>
<!-- External Services -->
<rect x="700" y="260" width="80" height="40" fill="none" stroke="#F5A623" stroke-width="2" rx="5"/>
<text x="740" y="285" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">External API</text>
<!-- Arrows -->
<g stroke="#666" stroke-width="2" fill="none">
<!-- Top layer to Session Manager -->
<line x1="70" y1="100" x2="250" y2="160" opacity="0.6"/>
<line x1="220" y1="100" x2="300" y2="160" opacity="0.6"/>
<line x1="370" y1="100" x2="400" y2="160" opacity="0.6"/>
<line x1="520" y1="100" x2="500" y2="160" opacity="0.6"/>
<line x1="670" y1="100" x2="550" y2="160" opacity="0.6"/>
<!-- Session Manager to Data Layer -->
<line x1="260" y1="200" x2="70" y2="260" opacity="0.6"/>
<line x1="320" y1="200" x2="210" y2="260" opacity="0.6"/>
<line x1="400" y1="200" x2="350" y2="260" opacity="0.6"/>
<line x1="480" y1="200" x2="490" y2="260" opacity="0.6"/>
<line x1="540" y1="200" x2="630" y2="260" opacity="0.6"/>
<!-- External API connections (dashed) -->
<path d="M740,260 Q740,220 550,180" stroke-dasharray="3,3" marker-end="url(#arrow)" opacity="0.4"/>
<!-- Directory Services -->
<rect x="1090" y="420" width="140" height="60" class="neon-blue" rx="6.5"/>
<text x="1160" y="458" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Zitadel (IAM)</text>
</g>
<!-- Storage Detail Box -->
<g transform="translate(20, 330)">
<rect width="760" height="50" fill="none" stroke="#666" stroke-width="1" rx="5" opacity="0.3"/>
<text x="10" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">Storage Contents:</text>
<text x="130" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">.gbkb (Documents)</text>
<text x="280" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">.gbdialog (Scripts)</text>
<text x="430" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">.gbot (Configs)</text>
<text x="560" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">Templates</text>
<text x="660" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">User Assets</text>
<!-- Arrows -->
<g stroke="#666" stroke-width="2.6" fill="none" opacity="0.7">
<!-- Top layer to Session Manager -->
<line x1="115" y1="200" x2="400" y2="280" marker-end="url(#arrow)"/>
<line x1="265" y1="200" x2="450" y2="280" marker-end="url(#arrow)"/>
<line x1="570" y1="200" x2="550" y2="280" marker-end="url(#arrow)"/>
<line x1="760" y1="200" x2="650" y2="280" marker-end="url(#arrow)"/>
<line x1="945" y1="200" x2="750" y2="280" marker-end="url(#arrow)"/>
<!-- BotModels connection -->
<line x1="1215" y1="200" x2="1050" y2="280" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.5"/>
<!-- Session Manager to Data Layer -->
<line x1="400" y1="340" x2="120" y2="420" marker-end="url(#arrow)"/>
<line x1="450" y1="340" x2="280" y2="420" marker-end="url(#arrow)"/>
<line x1="550" y1="340" x2="445" y2="420" marker-end="url(#arrow)"/>
<line x1="650" y1="340" x2="615" y2="420" marker-end="url(#arrow)"/>
<line x1="700" y1="340" x2="820" y2="420" marker-end="url(#arrow)"/>
<!-- Channels/External connections -->
<line x1="875" y1="340" x2="920" y2="420" marker-end="url(#arrow)"/>
<line x1="1050" y1="340" x2="1160" y2="420" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.5"/>
</g>
<!-- PROGRESS INDICATOR (Lower Section) -->
<g id="progress-legend">
<!-- Background gradient bar -->
<rect x="50" y="560" width="1300" height="100" fill="url(#flowGradient)" rx="10" opacity="0.2"/>
<!-- Stage markers -->
<circle cx="150" cy="610" r="12" class="neon-blue"/>
<circle cx="400" cy="610" r="12" class="neon-orange"/>
<circle cx="650" cy="610" r="12" class="neon-purple"/>
<circle cx="900" cy="610" r="12" class="neon-green"/>
<circle cx="1150" cy="610" r="12" class="neon-cyan"/>
<!-- Connecting lines -->
<line x1="162" y1="610" x2="388" y2="610" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="412" y1="610" x2="638" y2="610" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="662" y1="610" x2="888" y2="610" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="912" y1="610" x2="1138" y2="610" class="arrow-color" stroke-width="2" opacity="0.4"/>
<!-- Stage labels -->
<text x="150" y="580" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Request</text>
<text x="150" y="650" text-anchor="middle" font-size="16" class="secondary-text">Web/Console</text>
<text x="400" y="580" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Process</text>
<text x="400" y="650" text-anchor="middle" font-size="16" class="secondary-text">BASIC + LLM</text>
<text x="650" y="580" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Decide</text>
<text x="650" y="650" text-anchor="middle" font-size="16" class="secondary-text">AI Routing</text>
<text x="900" y="580" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Execute</text>
<text x="900" y="650" text-anchor="middle" font-size="16" class="secondary-text">Tools + APIs</text>
<text x="1150" y="580" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Respond</text>
<text x="1150" y="650" text-anchor="middle" font-size="16" class="secondary-text">Multi-Channel</text>
</g>
<!-- Legend -->
<g id="legend" transform="translate(50, 720)">
<text x="0" y="0" font-size="18" font-weight="500" class="main-text">Component Types:</text>
<rect x="0" y="20" width="24" height="24" class="neon-blue" rx="4"/>
<text x="35" y="38" font-size="16" class="secondary-text">Interface / Routing</text>
<rect x="200" y="20" width="24" height="24" class="neon-orange" rx="4"/>
<text x="235" y="38" font-size="16" class="secondary-text">Processing / Scripts</text>
<rect x="420" y="20" width="24" height="24" class="neon-purple" rx="4"/>
<text x="455" y="38" font-size="16" class="secondary-text">AI / ML / Decision</text>
<rect x="640" y="20" width="24" height="24" class="neon-green" rx="4"/>
<text x="675" y="38" font-size="16" class="secondary-text">Execution / Storage</text>
<rect x="870" y="20" width="24" height="24" class="neon-cyan" rx="4"/>
<text x="905" y="38" font-size="16" class="secondary-text">Output / Response</text>
</g>
<!-- Description -->
<text x="400" y="45" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#666">
Single binary with everything included - no external dependencies
<text x="700" y="810" text-anchor="middle" font-size="21" class="secondary-text">
Rust-powered single binary serving web UI, BASIC scripting, LLM orchestration, and multi-channel messaging
</text>
<text x="700" y="845" text-anchor="middle" font-size="21" class="secondary-text">
Auto-installed infrastructure: PostgreSQL, Valkey, Qdrant, SeaweedFS, Zitadel - zero external dependencies
</text>
</svg>

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,182 @@
<svg width="1400" height="900" xmlns="http://www.w3.org/2000/svg">
<style>
/* Light theme defaults */
.neon-blue { stroke: #4A90E2; stroke-width: 2.6; fill: none; }
.neon-orange { stroke: #F5A623; stroke-width: 2.6; fill: none; }
.neon-purple { stroke: #BD10E0; stroke-width: 2.6; fill: none; }
.neon-green { stroke: #7ED321; stroke-width: 2.6; fill: none; }
.neon-cyan { stroke: #50E3C2; stroke-width: 2.6; fill: none; }
.main-text { fill: #1a1a1a; font-family: Arial, sans-serif; }
.secondary-text { fill: #666; font-family: Arial, sans-serif; }
.arrow-color { stroke: #666; fill: #666; }
/* Dark theme with subtle neon effects */
@media (prefers-color-scheme: dark) {
.neon-blue {
stroke: #00D4FF;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00D4FF) drop-shadow(0 0 8px #00A0FF);
}
.neon-orange {
stroke: #FF9500;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #FF9500) drop-shadow(0 0 8px #FF7700);
}
.neon-purple {
stroke: #E040FB;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #E040FB) drop-shadow(0 0 8px #D500F9);
}
.neon-green {
stroke: #00FF88;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00FF88) drop-shadow(0 0 8px #00E676);
}
.neon-cyan {
stroke: #00E5EA;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00E5EA) drop-shadow(0 0 8px #00BCD4);
}
.main-text { fill: #FFFFFF; }
.secondary-text { fill: #B0B0B0; }
.arrow-color { stroke: #B0B0B0; fill: #B0B0B0; }
}
</style>
<defs>
<marker id="arrow" markerWidth="13" markerHeight="13" refX="11.7" refY="3.9" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,7.8 L11.7,3.9 z" class="arrow-color"/>
</marker>
<linearGradient id="flowGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#4A90E2;stop-opacity:0.3" />
<stop offset="50%" style="stop-color:#BD10E0;stop-opacity:0.3" />
<stop offset="100%" style="stop-color:#7ED321;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- Title -->
<text x="700" y="45" text-anchor="middle" font-size="32" font-weight="600" class="main-text">BotServer ↔ BotModels Integration</text>
<text x="700" y="75" text-anchor="middle" font-size="18" class="secondary-text">Rust backend calling Python AI services over HTTPS</text>
<!-- MAIN ARCHITECTURE DIAGRAM -->
<g id="main-architecture">
<!-- BotServer Box (Left) -->
<rect x="80" y="120" width="400" height="380" class="neon-blue" rx="10"/>
<text x="280" y="155" text-anchor="middle" font-size="26" font-weight="600" class="main-text">botserver (Rust)</text>
<!-- BotServer Components -->
<rect x="110" y="180" width="160" height="55" class="neon-orange" rx="6.5"/>
<text x="190" y="215" text-anchor="middle" font-size="18" font-weight="500" class="main-text">BASIC Interpreter</text>
<rect x="290" y="180" width="160" height="55" class="neon-purple" rx="6.5"/>
<text x="370" y="215" text-anchor="middle" font-size="18" font-weight="500" class="main-text">LLM Integration</text>
<!-- BASIC Keywords -->
<rect x="110" y="260" width="340" height="120" class="neon-green" rx="6.5"/>
<text x="280" y="290" text-anchor="middle" font-size="18" font-weight="500" class="main-text">BASIC Keywords</text>
<text x="140" y="320" font-size="16" class="secondary-text">• IMAGE prompt</text>
<text x="140" y="345" font-size="16" class="secondary-text">• VIDEO prompt</text>
<text x="300" y="320" font-size="16" class="secondary-text">• AUDIO text</text>
<text x="300" y="345" font-size="16" class="secondary-text">• SEE image</text>
<text x="140" y="370" font-size="16" class="secondary-text">• HEAR AS AUDIO</text>
<!-- Config -->
<rect x="110" y="400" width="340" height="70" class="neon-cyan" rx="6.5"/>
<text x="280" y="430" text-anchor="middle" font-size="16" font-weight="500" class="main-text">config.csv</text>
<text x="280" y="455" text-anchor="middle" font-size="14" class="secondary-text">botmodels-url, botmodels-enabled</text>
<!-- BotModels Box (Right) -->
<rect x="920" y="120" width="400" height="380" class="neon-purple" rx="10"/>
<text x="1120" y="155" text-anchor="middle" font-size="26" font-weight="600" class="main-text">botmodels (Python)</text>
<!-- BotModels Services -->
<rect x="950" y="180" width="160" height="55" class="neon-orange" rx="6.5"/>
<text x="1030" y="208" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Image Service</text>
<text x="1030" y="228" text-anchor="middle" font-size="12" class="secondary-text">Stable Diffusion</text>
<rect x="1130" y="180" width="160" height="55" class="neon-green" rx="6.5"/>
<text x="1210" y="208" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Video Service</text>
<text x="1210" y="228" text-anchor="middle" font-size="12" class="secondary-text">Zeroscope</text>
<rect x="950" y="260" width="160" height="55" class="neon-cyan" rx="6.5"/>
<text x="1030" y="288" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Speech Service</text>
<text x="1030" y="308" text-anchor="middle" font-size="12" class="secondary-text">TTS / Whisper</text>
<rect x="1130" y="260" width="160" height="55" class="neon-blue" rx="6.5"/>
<text x="1210" y="288" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Vision Service</text>
<text x="1210" y="308" text-anchor="middle" font-size="12" class="secondary-text">BLIP2 / QRCode</text>
<!-- API Endpoints -->
<rect x="950" y="340" width="340" height="130" class="neon-orange" rx="6.5"/>
<text x="1120" y="370" text-anchor="middle" font-size="16" font-weight="500" class="main-text">FastAPI Endpoints</text>
<text x="980" y="400" font-size="14" class="secondary-text">/api/v1/image/generate</text>
<text x="980" y="422" font-size="14" class="secondary-text">/api/v1/video/generate</text>
<text x="980" y="444" font-size="14" class="secondary-text">/api/v1/speech/to-text</text>
<text x="980" y="466" font-size="14" class="secondary-text">/api/v1/vision/describe</text>
<!-- Connection Arrow -->
<line x1="480" y1="310" x2="920" y2="310" class="arrow-color" stroke-width="4" marker-end="url(#arrow)"/>
<rect x="600" y="280" width="120" height="60" fill="white" class="neon-cyan" rx="6.5"/>
<text x="660" y="305" text-anchor="middle" font-size="16" font-weight="600" class="main-text">HTTPS</text>
<text x="660" y="325" text-anchor="middle" font-size="12" class="secondary-text">JSON / Binary</text>
<!-- Outputs -->
<rect x="920" y="530" width="400" height="80" class="neon-green" rx="6.5"/>
<text x="1120" y="565" text-anchor="middle" font-size="18" font-weight="500" class="main-text">outputs/ (Generated Files)</text>
<text x="1120" y="590" text-anchor="middle" font-size="14" class="secondary-text">Images, Videos, Audio files served via /outputs</text>
<!-- Arrow from API to outputs -->
<line x1="1120" y1="470" x2="1120" y2="530" class="arrow-color" stroke-width="2.6" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.6"/>
</g>
<!-- PROGRESS INDICATOR -->
<g id="progress-legend">
<rect x="80" y="650" width="1240" height="80" fill="url(#flowGradient)" rx="10" opacity="0.2"/>
<!-- Stage markers -->
<circle cx="200" cy="690" r="12" class="neon-blue"/>
<circle cx="460" cy="690" r="12" class="neon-orange"/>
<circle cx="720" cy="690" r="12" class="neon-purple"/>
<circle cx="980" cy="690" r="12" class="neon-green"/>
<circle cx="1200" cy="690" r="12" class="neon-cyan"/>
<!-- Connecting lines -->
<line x1="212" y1="690" x2="448" y2="690" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="472" y1="690" x2="708" y2="690" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="732" y1="690" x2="968" y2="690" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="992" y1="690" x2="1188" y2="690" class="arrow-color" stroke-width="2" opacity="0.4"/>
<!-- Stage labels -->
<text x="200" y="720" text-anchor="middle" font-size="14" class="secondary-text">BASIC Keyword</text>
<text x="460" y="720" text-anchor="middle" font-size="14" class="secondary-text">HTTP Request</text>
<text x="720" y="720" text-anchor="middle" font-size="14" class="secondary-text">AI Processing</text>
<text x="980" y="720" text-anchor="middle" font-size="14" class="secondary-text">Generate</text>
<text x="1200" y="720" text-anchor="middle" font-size="14" class="secondary-text">Return URL</text>
</g>
<!-- Legend -->
<g id="legend" transform="translate(80, 770)">
<rect x="0" y="0" width="24" height="24" class="neon-blue" rx="4"/>
<text x="35" y="18" font-size="14" class="secondary-text">Rust Backend</text>
<rect x="180" y="0" width="24" height="24" class="neon-purple" rx="4"/>
<text x="215" y="18" font-size="14" class="secondary-text">Python AI</text>
<rect x="340" y="0" width="24" height="24" class="neon-orange" rx="4"/>
<text x="375" y="18" font-size="14" class="secondary-text">Processing</text>
<rect x="500" y="0" width="24" height="24" class="neon-green" rx="4"/>
<text x="535" y="18" font-size="14" class="secondary-text">Output</text>
<rect x="660" y="0" width="24" height="24" class="neon-cyan" rx="4"/>
<text x="695" y="18" font-size="14" class="secondary-text">Config/API</text>
</g>
<!-- Description -->
<text x="700" y="830" text-anchor="middle" font-size="18" class="secondary-text">
BotModels runs as a separate Python service for GPU-accelerated AI inference
</text>
<text x="700" y="855" text-anchor="middle" font-size="18" class="secondary-text">
Enable with: botmodels-enabled=true and botmodels-url=http://localhost:8001
</text>
</svg>

After

Width:  |  Height:  |  Size: 9.8 KiB

View file

@ -1,73 +1,216 @@
<svg width="800" height="320" xmlns="http://www.w3.org/2000/svg">
<svg width="1400" height="900" xmlns="http://www.w3.org/2000/svg">
<style>
/* Light theme defaults */
.neon-blue { stroke: #4A90E2; stroke-width: 2.6; fill: none; }
.neon-orange { stroke: #F5A623; stroke-width: 2.6; fill: none; }
.neon-purple { stroke: #BD10E0; stroke-width: 2.6; fill: none; }
.neon-green { stroke: #7ED321; stroke-width: 2.6; fill: none; }
.neon-cyan { stroke: #50E3C2; stroke-width: 2.6; fill: none; }
.main-text { fill: #1a1a1a; }
.secondary-text { fill: #666; }
.arrow-color { stroke: #666; fill: #666; }
/* Dark theme with subtle neon effects */
@media (prefers-color-scheme: dark) {
.neon-blue {
stroke: #00D4FF;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00D4FF) drop-shadow(0 0 8px #00A0FF);
}
.neon-orange {
stroke: #FF9500;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #FF9500) drop-shadow(0 0 8px #FF7700);
}
.neon-purple {
stroke: #E040FB;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #E040FB) drop-shadow(0 0 8px #D500F9);
}
.neon-green {
stroke: #00FF88;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00FF88) drop-shadow(0 0 8px #00E676);
}
.neon-cyan {
stroke: #00E5EA;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00E5EA) drop-shadow(0 0 8px #00BCD4);
}
.main-text { fill: #FFFFFF; }
.secondary-text { fill: #B0B0B0; }
.arrow-color { stroke: #B0B0B0; fill: #B0B0B0; }
}
</style>
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#666"/>
<marker id="arrow" markerWidth="13" markerHeight="13" refX="11.7" refY="3.9" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,7.8 L11.7,3.9 z" class="arrow-color"/>
</marker>
<linearGradient id="flowGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#4A90E2;stop-opacity:0.3" />
<stop offset="25%" style="stop-color:#F5A623;stop-opacity:0.3" />
<stop offset="50%" style="stop-color:#BD10E0;stop-opacity:0.3" />
<stop offset="75%" style="stop-color:#7ED321;stop-opacity:0.3" />
<stop offset="100%" style="stop-color:#50E3C2;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- Title -->
<text x="400" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" font-weight="600" fill="#333">The Flow</text>
<!-- Title (positioned well above content) -->
<text x="700" y="45" text-anchor="middle" font-family="Arial, sans-serif" font-size="32" font-weight="600" class="main-text">Conversation Flow</text>
<text x="700" y="75" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="secondary-text">How General Bots processes user messages through the AI pipeline</text>
<!-- Main Flow Components -->
<g id="mainFlow">
<!-- MAIN FLOW DIAGRAM (Upper Section) -->
<g id="main-flow">
<!-- Phase labels -->
<text x="140" y="120" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Input</text>
<text x="380" y="120" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Script</text>
<text x="620" y="120" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">AI Decision</text>
<text x="900" y="120" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Execution</text>
<text x="1180" y="120" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Response</text>
<!-- Main flow components -->
<!-- User Input -->
<rect x="20" y="60" width="100" height="40" fill="none" stroke="#4A90E2" stroke-width="2" rx="5"/>
<text x="70" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">User Input</text>
<rect x="50" y="145" width="180" height="70" class="neon-blue" rx="6.5"/>
<text x="140" y="185" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="500" class="main-text">User Input</text>
<text x="140" y="205" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="secondary-text">Web / WhatsApp / Teams</text>
<!-- BASIC Script -->
<rect x="160" y="60" width="100" height="40" fill="none" stroke="#F5A623" stroke-width="2" rx="5"/>
<text x="210" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">BASIC Script</text>
<rect x="290" y="145" width="180" height="70" class="neon-orange" rx="6.5"/>
<text x="380" y="185" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="500" class="main-text">BASIC Script</text>
<text x="380" y="205" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="secondary-text">start.bas / tools</text>
<!-- LLM Decision -->
<rect x="300" y="60" width="100" height="40" fill="none" stroke="#BD10E0" stroke-width="2" rx="5"/>
<text x="350" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">LLM Decision</text>
<rect x="530" y="145" width="180" height="70" class="neon-purple" rx="6.5"/>
<text x="620" y="185" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="500" class="main-text">LLM Decision</text>
<text x="620" y="205" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="secondary-text">Intent / Tool Selection</text>
<!-- Bot Executor -->
<rect x="440" y="60" width="100" height="40" fill="none" stroke="#7ED321" stroke-width="2" rx="5"/>
<text x="490" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Bot Executor</text>
<rect x="770" y="145" width="200" height="70" class="neon-green" rx="6.5"/>
<text x="870" y="185" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="500" class="main-text">Bot Executor</text>
<text x="870" y="205" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="secondary-text">Run Keywords</text>
<!-- Bot Response -->
<rect x="580" y="60" width="100" height="40" fill="none" stroke="#50E3C2" stroke-width="2" rx="5"/>
<text x="630" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Bot Response</text>
</g>
<rect x="1030" y="145" width="200" height="70" class="neon-cyan" rx="6.5"/>
<text x="1130" y="185" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="500" class="main-text">Bot Response</text>
<text x="1130" y="205" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="secondary-text">TALK / Send</text>
<!-- Parallel Processes -->
<g id="parallelProcesses">
<!-- Search Knowledge -->
<rect x="360" y="160" width="120" height="40" fill="none" stroke="#4A90E2" stroke-width="2" rx="5"/>
<text x="420" y="185" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Search Knowledge</text>
<!-- Parallel processes section -->
<text x="700" y="290" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Parallel Operations</text>
<!-- Call API -->
<rect x="500" y="160" width="100" height="40" fill="none" stroke="#F5A623" stroke-width="2" rx="5"/>
<text x="550" y="185" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Call API</text>
<!-- Vector Search -->
<rect x="420" y="320" width="200" height="65" class="neon-blue" rx="6.5"/>
<text x="520" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="500" class="main-text">Vector Search</text>
<text x="520" y="375" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="secondary-text">Qdrant / USE KB</text>
<!-- External APIs -->
<rect x="680" y="320" width="200" height="65" class="neon-orange" rx="6.5"/>
<text x="780" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="500" class="main-text">External APIs</text>
<text x="780" y="375" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="secondary-text">GET / POST / GraphQL</text>
<!-- Database -->
<rect x="940" y="320" width="180" height="65" class="neon-purple" rx="6.5"/>
<text x="1030" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="500" class="main-text">Database</text>
<text x="1030" y="375" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="secondary-text">TABLE / SAVE</text>
<!-- Knowledge Base Box -->
<rect x="100" y="320" width="260" height="65" class="neon-cyan" rx="6.5"/>
<text x="230" y="355" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="500" class="main-text">Knowledge Base</text>
<text x="230" y="375" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="secondary-text">.gbkb Documents</text>
</g>
<!-- Flow Arrows -->
<g id="arrows" stroke="#666" stroke-width="2" fill="none">
<g id="arrows" stroke-width="2.6" fill="none">
<!-- Main horizontal flow -->
<line x1="120" y1="80" x2="160" y2="80" marker-end="url(#arrow)"/>
<line x1="260" y1="80" x2="300" y2="80" marker-end="url(#arrow)"/>
<line x1="400" y1="80" x2="440" y2="80" marker-end="url(#arrow)"/>
<line x1="540" y1="80" x2="580" y2="80" marker-end="url(#arrow)"/>
<line x1="230" y1="180" x2="290" y2="180" class="arrow-color" marker-end="url(#arrow)" opacity="0.7"/>
<line x1="470" y1="180" x2="530" y2="180" class="arrow-color" marker-end="url(#arrow)" opacity="0.7"/>
<line x1="710" y1="180" x2="770" y2="180" class="arrow-color" marker-end="url(#arrow)" opacity="0.7"/>
<line x1="970" y1="180" x2="1030" y2="180" class="arrow-color" marker-end="url(#arrow)" opacity="0.7"/>
<!-- Branch down to parallel processes -->
<line x1="490" y1="100" x2="490" y2="130" stroke-dasharray="3,3" opacity="0.6"/>
<line x1="490" y1="130" x2="420" y2="160" marker-end="url(#arrow)" opacity="0.6"/>
<line x1="490" y1="130" x2="550" y2="160" marker-end="url(#arrow)" opacity="0.6"/>
<line x1="870" y1="215" x2="870" y2="260" class="arrow-color" stroke-dasharray="3.9,3.9" opacity="0.5"/>
<line x1="870" y1="260" x2="520" y2="320" class="arrow-color" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.5"/>
<line x1="870" y1="260" x2="780" y2="320" class="arrow-color" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.5"/>
<line x1="870" y1="260" x2="1030" y2="320" class="arrow-color" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.5"/>
<!-- Feedback from parallel processes to response -->
<path d="M420,200 Q420,240 630,240 Q630,140 630,100" stroke-dasharray="3,3" marker-end="url(#arrow)" opacity="0.4"/>
<path d="M550,200 Q550,230 620,230 Q620,130 620,100" stroke-dasharray="3,3" marker-end="url(#arrow)" opacity="0.4"/>
<!-- Knowledge base to Vector Search -->
<line x1="360" y1="352" x2="420" y2="352" class="arrow-color" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.5"/>
<!-- Direct feedback loop -->
<path d="M70,60 Q70,30 630,30 Q630,50 630,60" stroke-dasharray="2,2" marker-end="url(#arrow)" opacity="0.3"/>
<!-- Feedback from parallel to response -->
<path d="M520,385 Q520,440 1130,440 Q1130,240 1130,215" class="arrow-color" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.4"/>
<path d="M780,385 Q780,430 1110,430 Q1110,235 1110,215" class="arrow-color" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.4"/>
<path d="M1030,385 Q1030,420 1090,420 Q1090,230 1090,215" class="arrow-color" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.4"/>
<!-- Feedback loop from response to input -->
<path d="M140,145 Q140,50 1130,50 Q1130,100 1130,145" class="arrow-color" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.3"/>
</g>
<!-- Description -->
<text x="400" y="280" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#666">
The AI handles everything else - understanding intent, collecting information, executing tools,
<!-- PROGRESS INDICATOR (Lower Section) -->
<g id="progress-legend">
<!-- Background gradient bar -->
<rect x="100" y="520" width="1200" height="100" fill="url(#flowGradient)" rx="13" opacity="0.2"/>
<!-- Stage markers -->
<circle cx="200" cy="570" r="13" class="neon-blue"/>
<circle cx="440" cy="570" r="13" class="neon-orange"/>
<circle cx="680" cy="570" r="13" class="neon-purple"/>
<circle cx="920" cy="570" r="13" class="neon-green"/>
<circle cx="1160" cy="570" r="13" class="neon-cyan"/>
<!-- Connecting lines between stages -->
<line x1="213" y1="570" x2="427" y2="570" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="453" y1="570" x2="667" y2="570" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="693" y1="570" x2="907" y2="570" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="933" y1="570" x2="1147" y2="570" class="arrow-color" stroke-width="2" opacity="0.4"/>
<!-- Stage labels -->
<text x="200" y="605" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="main-text">Receive</text>
<text x="440" y="605" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="main-text">Parse</text>
<text x="680" y="605" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="main-text">Decide</text>
<text x="920" y="605" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="main-text">Execute</text>
<text x="1160" y="605" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="main-text">Respond</text>
</g>
<!-- Legend -->
<g id="legend" transform="translate(100, 670)">
<text x="0" y="0" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Legend:</text>
<line x1="100" y1="-5" x2="150" y2="-5" stroke="#666" stroke-width="2.6" opacity="0.7"/>
<text x="160" y="0" font-family="Arial, sans-serif" font-size="16" class="secondary-text">Main Flow</text>
<line x1="300" y1="-5" x2="350" y2="-5" stroke="#666" stroke-width="2.6" stroke-dasharray="3.9,3.9" opacity="0.5"/>
<text x="360" y="0" font-family="Arial, sans-serif" font-size="16" class="secondary-text">Parallel / Optional</text>
<line x1="550" y1="-5" x2="600" y2="-5" stroke="#666" stroke-width="2.6" stroke-dasharray="3.9,3.9" opacity="0.3"/>
<text x="610" y="0" font-family="Arial, sans-serif" font-size="16" class="secondary-text">Feedback Loop</text>
</g>
<!-- Description text (bottom) -->
<text x="700" y="760" text-anchor="middle" font-family="Arial, sans-serif" font-size="21" class="secondary-text">
The AI handles everything automatically - understanding intent, searching knowledge,
</text>
<text x="400" y="298" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#666">
answering from documents. Zero configuration.
<text x="700" y="790" text-anchor="middle" font-family="Arial, sans-serif" font-size="21" class="secondary-text">
executing tools, and generating responses. Zero configuration required.
</text>
<!-- Feature highlights -->
<g id="features" transform="translate(100, 830)">
<rect x="0" y="0" width="180" height="40" class="neon-blue" rx="6.5" opacity="0.5"/>
<text x="90" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="main-text">Multi-Channel</text>
<rect x="200" y="0" width="180" height="40" class="neon-orange" rx="6.5" opacity="0.5"/>
<text x="290" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="main-text">BASIC Scripting</text>
<rect x="400" y="0" width="180" height="40" class="neon-purple" rx="6.5" opacity="0.5"/>
<text x="490" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="main-text">LLM Powered</text>
<rect x="600" y="0" width="180" height="40" class="neon-green" rx="6.5" opacity="0.5"/>
<text x="690" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="main-text">Tool Execution</text>
<rect x="800" y="0" width="180" height="40" class="neon-cyan" rx="6.5" opacity="0.5"/>
<text x="890" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="main-text">RAG Search</text>
<rect x="1000" y="0" width="180" height="40" class="neon-blue" rx="6.5" opacity="0.5"/>
<text x="1090" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" class="main-text">Real-time</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,248 @@
<svg width="1400" height="900" xmlns="http://www.w3.org/2000/svg">
<style>
/* Light theme defaults */
.neon-blue { stroke: #4A90E2; stroke-width: 2.6; fill: none; }
.neon-orange { stroke: #F5A623; stroke-width: 2.6; fill: none; }
.neon-purple { stroke: #BD10E0; stroke-width: 2.6; fill: none; }
.neon-green { stroke: #7ED321; stroke-width: 2.6; fill: none; }
.neon-cyan { stroke: #50E3C2; stroke-width: 2.6; fill: none; }
.neon-gray { stroke: #888888; stroke-width: 2.6; fill: none; }
.main-text { fill: #1a1a1a; font-family: Arial, sans-serif; }
.secondary-text { fill: #666; font-family: Arial, sans-serif; }
.arrow-color { stroke: #666; fill: #666; }
/* Dark theme with subtle neon effects */
@media (prefers-color-scheme: dark) {
.neon-blue {
stroke: #00D4FF;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00D4FF) drop-shadow(0 0 8px #00A0FF);
}
.neon-orange {
stroke: #FF9500;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #FF9500) drop-shadow(0 0 8px #FF7700);
}
.neon-purple {
stroke: #E040FB;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #E040FB) drop-shadow(0 0 8px #D500F9);
}
.neon-green {
stroke: #00FF88;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00FF88) drop-shadow(0 0 8px #00E676);
}
.neon-cyan {
stroke: #00E5EA;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00E5EA) drop-shadow(0 0 8px #00BCD4);
}
.neon-gray {
stroke: #AAAAAA;
stroke-width: 2.8;
filter: drop-shadow(0 0 2px #888888);
}
.main-text { fill: #FFFFFF; }
.secondary-text { fill: #B0B0B0; }
.arrow-color { stroke: #B0B0B0; fill: #B0B0B0; }
}
</style>
<defs>
<marker id="arrow" markerWidth="13" markerHeight="13" refX="11.7" refY="3.9" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,7.8 L11.7,3.9 z" class="arrow-color"/>
</marker>
<linearGradient id="flowGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#4A90E2;stop-opacity:0.3" />
<stop offset="33%" style="stop-color:#BD10E0;stop-opacity:0.3" />
<stop offset="66%" style="stop-color:#7ED321;stop-opacity:0.3" />
<stop offset="100%" style="stop-color:#50E3C2;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- Title -->
<text x="700" y="45" text-anchor="middle" font-size="32" font-weight="600" class="main-text">Infrastructure Architecture</text>
<text x="700" y="75" text-anchor="middle" font-size="18" class="secondary-text">Load Balancing, Multi-Tenant Isolation, and Auto-Scaling</text>
<!-- MAIN ARCHITECTURE DIAGRAM -->
<g id="main-architecture">
<!-- Internet/Clients -->
<text x="700" y="115" text-anchor="middle" font-size="18" font-weight="500" class="secondary-text">Clients</text>
<!-- Client Icons Row -->
<rect x="350" y="130" width="100" height="50" class="neon-blue" rx="6.5"/>
<text x="400" y="160" text-anchor="middle" font-size="16" class="main-text">Web</text>
<rect x="500" y="130" width="100" height="50" class="neon-blue" rx="6.5"/>
<text x="550" y="160" text-anchor="middle" font-size="16" class="main-text">Mobile</text>
<rect x="650" y="130" width="100" height="50" class="neon-blue" rx="6.5"/>
<text x="700" y="160" text-anchor="middle" font-size="16" class="main-text">WhatsApp</text>
<rect x="800" y="130" width="100" height="50" class="neon-blue" rx="6.5"/>
<text x="850" y="160" text-anchor="middle" font-size="16" class="main-text">Teams</text>
<rect x="950" y="130" width="100" height="50" class="neon-blue" rx="6.5"/>
<text x="1000" y="160" text-anchor="middle" font-size="16" class="main-text">API</text>
<!-- Load Balancer -->
<rect x="300" y="220" width="800" height="60" class="neon-orange" rx="6.5"/>
<text x="700" y="255" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Load Balancer (Caddy)</text>
<text x="700" y="273" text-anchor="middle" font-size="14" class="secondary-text">TLS Termination | Rate Limiting | Health Checks</text>
<!-- Connection lines from clients to LB -->
<g stroke="#666" stroke-width="2" opacity="0.5">
<line x1="400" y1="180" x2="500" y2="220"/>
<line x1="550" y1="180" x2="600" y2="220"/>
<line x1="700" y1="180" x2="700" y2="220"/>
<line x1="850" y1="180" x2="800" y2="220"/>
<line x1="1000" y1="180" x2="900" y2="220"/>
</g>
<!-- BotServer Instances -->
<text x="700" y="315" text-anchor="middle" font-size="18" font-weight="500" class="secondary-text">BotServer Instances (Auto-Scaled)</text>
<rect x="150" y="335" width="200" height="80" class="neon-green" rx="6.5"/>
<text x="250" y="370" text-anchor="middle" font-size="18" font-weight="500" class="main-text">BotServer 1</text>
<text x="250" y="395" text-anchor="middle" font-size="14" class="secondary-text">LXC Container</text>
<rect x="400" y="335" width="200" height="80" class="neon-green" rx="6.5"/>
<text x="500" y="370" text-anchor="middle" font-size="18" font-weight="500" class="main-text">BotServer 2</text>
<text x="500" y="395" text-anchor="middle" font-size="14" class="secondary-text">LXC Container</text>
<rect x="650" y="335" width="200" height="80" class="neon-green" rx="6.5"/>
<text x="750" y="370" text-anchor="middle" font-size="18" font-weight="500" class="main-text">BotServer 3</text>
<text x="750" y="395" text-anchor="middle" font-size="14" class="secondary-text">LXC Container</text>
<rect x="900" y="335" width="200" height="80" class="neon-gray" rx="6.5" stroke-dasharray="5,5"/>
<text x="1000" y="370" text-anchor="middle" font-size="18" font-weight="500" class="main-text">BotServer N</text>
<text x="1000" y="395" text-anchor="middle" font-size="14" class="secondary-text">Auto-Scale</text>
<!-- Connection lines from LB to BotServers -->
<g stroke="#666" stroke-width="2.6" opacity="0.7">
<line x1="400" y1="280" x2="250" y2="335" marker-end="url(#arrow)"/>
<line x1="550" y1="280" x2="500" y2="335" marker-end="url(#arrow)"/>
<line x1="700" y1="280" x2="750" y2="335" marker-end="url(#arrow)"/>
<line x1="850" y1="280" x2="1000" y2="335" marker-end="url(#arrow)" stroke-dasharray="5,5"/>
</g>
<!-- Data Layer Label -->
<text x="700" y="460" text-anchor="middle" font-size="18" font-weight="500" class="secondary-text">Shared Data Layer</text>
<!-- Data Layer Components -->
<rect x="50" y="480" width="160" height="70" class="neon-purple" rx="6.5"/>
<text x="130" y="515" text-anchor="middle" font-size="18" font-weight="500" class="main-text">PostgreSQL</text>
<text x="130" y="535" text-anchor="middle" font-size="12" class="secondary-text">Relational Data</text>
<rect x="230" y="480" width="140" height="70" class="neon-purple" rx="6.5"/>
<text x="300" y="515" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Valkey</text>
<text x="300" y="535" text-anchor="middle" font-size="12" class="secondary-text">Cache + Sessions</text>
<rect x="390" y="480" width="140" height="70" class="neon-purple" rx="6.5"/>
<text x="460" y="515" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Qdrant</text>
<text x="460" y="535" text-anchor="middle" font-size="12" class="secondary-text">Vector Search</text>
<rect x="550" y="480" width="160" height="70" class="neon-purple" rx="6.5"/>
<text x="630" y="515" text-anchor="middle" font-size="18" font-weight="500" class="main-text">SeaweedFS</text>
<text x="630" y="535" text-anchor="middle" font-size="12" class="secondary-text">Object Storage</text>
<rect x="730" y="480" width="140" height="70" class="neon-cyan" rx="6.5"/>
<text x="800" y="515" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Zitadel</text>
<text x="800" y="535" text-anchor="middle" font-size="12" class="secondary-text">IAM / Auth</text>
<rect x="890" y="480" width="140" height="70" class="neon-cyan" rx="6.5"/>
<text x="960" y="515" text-anchor="middle" font-size="18" font-weight="500" class="main-text">LiveKit</text>
<text x="960" y="535" text-anchor="middle" font-size="12" class="secondary-text">Real-time Meet</text>
<rect x="1050" y="480" width="140" height="70" class="neon-cyan" rx="6.5"/>
<text x="1120" y="515" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Stalwart</text>
<text x="1120" y="535" text-anchor="middle" font-size="12" class="secondary-text">Email Server</text>
<!-- Connection lines from BotServers to Data Layer -->
<g stroke="#666" stroke-width="2" opacity="0.4">
<line x1="250" y1="415" x2="130" y2="480"/>
<line x1="250" y1="415" x2="300" y2="480"/>
<line x1="500" y1="415" x2="460" y2="480"/>
<line x1="500" y1="415" x2="630" y2="480"/>
<line x1="750" y1="415" x2="800" y2="480"/>
<line x1="750" y1="415" x2="960" y2="480"/>
<line x1="1000" y1="415" x2="1120" y2="480"/>
</g>
</g>
<!-- PROGRESS INDICATOR (Lower Section) -->
<g id="progress-legend">
<rect x="50" y="600" width="1300" height="80" fill="url(#flowGradient)" rx="10" opacity="0.2"/>
<!-- Stage markers -->
<circle cx="200" cy="640" r="12" class="neon-blue"/>
<circle cx="500" cy="640" r="12" class="neon-orange"/>
<circle cx="800" cy="640" r="12" class="neon-green"/>
<circle cx="1100" cy="640" r="12" class="neon-purple"/>
<!-- Connecting lines -->
<line x1="212" y1="640" x2="488" y2="640" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="512" y1="640" x2="788" y2="640" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="812" y1="640" x2="1088" y2="640" class="arrow-color" stroke-width="2" opacity="0.4"/>
<!-- Stage labels -->
<text x="200" y="610" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Request</text>
<text x="200" y="672" text-anchor="middle" font-size="14" class="secondary-text">Multi-Channel</text>
<text x="500" y="610" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Route</text>
<text x="500" y="672" text-anchor="middle" font-size="14" class="secondary-text">Load Balance</text>
<text x="800" y="610" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Process</text>
<text x="800" y="672" text-anchor="middle" font-size="14" class="secondary-text">BotServer</text>
<text x="1100" y="610" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Store</text>
<text x="1100" y="672" text-anchor="middle" font-size="14" class="secondary-text">Data Layer</text>
</g>
<!-- Legend -->
<g id="legend" transform="translate(50, 720)">
<text x="0" y="0" font-size="18" font-weight="500" class="main-text">Component Types:</text>
<rect x="0" y="20" width="24" height="24" class="neon-blue" rx="4"/>
<text x="35" y="38" font-size="16" class="secondary-text">Client / Interface</text>
<rect x="220" y="20" width="24" height="24" class="neon-orange" rx="4"/>
<text x="255" y="38" font-size="16" class="secondary-text">Load Balancer</text>
<rect x="440" y="20" width="24" height="24" class="neon-green" rx="4"/>
<text x="475" y="38" font-size="16" class="secondary-text">Compute Instance</text>
<rect x="680" y="20" width="24" height="24" class="neon-purple" rx="4"/>
<text x="715" y="38" font-size="16" class="secondary-text">Data Storage</text>
<rect x="900" y="20" width="24" height="24" class="neon-cyan" rx="4"/>
<text x="935" y="38" font-size="16" class="secondary-text">Services</text>
</g>
<!-- Features row -->
<g id="features" transform="translate(50, 790)">
<rect x="0" y="0" width="200" height="45" class="neon-blue" rx="6.5" opacity="0.5"/>
<text x="100" y="28" text-anchor="middle" font-size="14" class="main-text">Auto-Scaling</text>
<rect x="220" y="0" width="200" height="45" class="neon-orange" rx="6.5" opacity="0.5"/>
<text x="320" y="28" text-anchor="middle" font-size="14" class="main-text">TLS Everywhere</text>
<rect x="440" y="0" width="200" height="45" class="neon-purple" rx="6.5" opacity="0.5"/>
<text x="540" y="28" text-anchor="middle" font-size="14" class="main-text">Multi-Tenant</text>
<rect x="660" y="0" width="200" height="45" class="neon-green" rx="6.5" opacity="0.5"/>
<text x="760" y="28" text-anchor="middle" font-size="14" class="main-text">Health Checks</text>
<rect x="880" y="0" width="200" height="45" class="neon-cyan" rx="6.5" opacity="0.5"/>
<text x="980" y="28" text-anchor="middle" font-size="14" class="main-text">Zero Downtime</text>
<rect x="1100" y="0" width="200" height="45" class="neon-gray" rx="6.5" opacity="0.5"/>
<text x="1200" y="28" text-anchor="middle" font-size="14" class="main-text">LXC Containers</text>
</g>
<!-- Description -->
<text x="700" y="870" text-anchor="middle" font-size="18" class="secondary-text">
Production-ready infrastructure with automatic scaling, load balancing, and multi-tenant isolation
</text>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,255 @@
<svg width="1400" height="900" xmlns="http://www.w3.org/2000/svg">
<style>
/* Light theme defaults */
.neon-blue { stroke: #4A90E2; stroke-width: 2.6; fill: none; }
.neon-orange { stroke: #F5A623; stroke-width: 2.6; fill: none; }
.neon-purple { stroke: #BD10E0; stroke-width: 2.6; fill: none; }
.neon-green { stroke: #7ED321; stroke-width: 2.6; fill: none; }
.neon-cyan { stroke: #50E3C2; stroke-width: 2.6; fill: none; }
.neon-gray { stroke: #888888; stroke-width: 2.6; fill: none; }
.main-text { fill: #1a1a1a; font-family: Arial, sans-serif; }
.secondary-text { fill: #666; font-family: Arial, sans-serif; }
.arrow-color { stroke: #666; fill: #666; }
/* Dark theme with subtle neon effects */
@media (prefers-color-scheme: dark) {
.neon-blue {
stroke: #00D4FF;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00D4FF) drop-shadow(0 0 8px #00A0FF);
}
.neon-orange {
stroke: #FF9500;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #FF9500) drop-shadow(0 0 8px #FF7700);
}
.neon-purple {
stroke: #E040FB;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #E040FB) drop-shadow(0 0 8px #D500F9);
}
.neon-green {
stroke: #00FF88;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00FF88) drop-shadow(0 0 8px #00E676);
}
.neon-cyan {
stroke: #00E5EA;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00E5EA) drop-shadow(0 0 8px #00BCD4);
}
.neon-gray {
stroke: #AAAAAA;
stroke-width: 2.8;
filter: drop-shadow(0 0 2px #AAAAAA);
}
.main-text { fill: #FFFFFF; }
.secondary-text { fill: #B0B0B0; }
.arrow-color { stroke: #B0B0B0; fill: #B0B0B0; }
}
</style>
<defs>
<marker id="arrow" markerWidth="13" markerHeight="13" refX="11.7" refY="3.9" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,7.8 L11.7,3.9 z" class="arrow-color"/>
</marker>
<linearGradient id="flowGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#4A90E2;stop-opacity:0.3" />
<stop offset="33%" style="stop-color:#F5A623;stop-opacity:0.3" />
<stop offset="66%" style="stop-color:#BD10E0;stop-opacity:0.3" />
<stop offset="100%" style="stop-color:#7ED321;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- Title -->
<text x="700" y="45" text-anchor="middle" font-size="32" font-weight="600" class="main-text">Observability Flow</text>
<text x="700" y="75" text-anchor="middle" font-size="18" class="secondary-text">Vector Agent collects logs from BotServer without code changes</text>
<!-- MAIN FLOW DIAGRAM -->
<g id="main-flow">
<!-- Phase Labels -->
<text x="200" y="115" text-anchor="middle" font-size="18" font-weight="500" class="secondary-text">Application</text>
<text x="550" y="115" text-anchor="middle" font-size="18" font-weight="500" class="secondary-text">Collection</text>
<text x="900" y="115" text-anchor="middle" font-size="18" font-weight="500" class="secondary-text">Processing</text>
<text x="1200" y="115" text-anchor="middle" font-size="18" font-weight="500" class="secondary-text">Outputs</text>
<!-- BotServer Application Box -->
<rect x="50" y="140" width="300" height="200" class="neon-blue" rx="6.5"/>
<text x="200" y="170" text-anchor="middle" font-size="22" font-weight="500" class="main-text">BotServer Application</text>
<!-- Log calls inside BotServer -->
<text x="80" y="210" font-family="monospace" font-size="14" class="secondary-text">log::trace!()</text>
<text x="80" y="235" font-family="monospace" font-size="14" class="secondary-text">log::debug!()</text>
<text x="80" y="260" font-family="monospace" font-size="14" class="secondary-text">log::info!()</text>
<text x="200" y="210" font-family="monospace" font-size="14" class="secondary-text">log::warn!()</text>
<text x="200" y="235" font-family="monospace" font-size="14" class="secondary-text">log::error!()</text>
<!-- Arrow to log files -->
<text x="200" y="290" text-anchor="middle" font-size="14" class="secondary-text">↓ writes to</text>
<rect x="80" y="305" width="240" height="30" class="neon-gray" rx="4"/>
<text x="200" y="325" text-anchor="middle" font-family="monospace" font-size="12" class="secondary-text">./botserver-stack/logs/</text>
<!-- Vector Agent Box -->
<rect x="400" y="140" width="300" height="200" class="neon-orange" rx="6.5"/>
<text x="550" y="170" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Vector Agent</text>
<text x="550" y="195" text-anchor="middle" font-size="14" class="secondary-text">(Collects from log files)</text>
<!-- Vector components -->
<rect x="420" y="220" width="80" height="50" class="neon-blue" rx="4"/>
<text x="460" y="250" text-anchor="middle" font-size="14" class="main-text">Sources</text>
<rect x="510" y="220" width="90" height="50" class="neon-purple" rx="4"/>
<text x="555" y="250" text-anchor="middle" font-size="14" class="main-text">Transforms</text>
<rect x="610" y="220" width="70" height="50" class="neon-green" rx="4"/>
<text x="645" y="250" text-anchor="middle" font-size="14" class="main-text">Sinks</text>
<!-- Vector sub-labels -->
<text x="460" y="290" text-anchor="middle" font-size="11" class="secondary-text">(Files)</text>
<text x="555" y="290" text-anchor="middle" font-size="11" class="secondary-text">(Parse)</text>
<text x="645" y="290" text-anchor="middle" font-size="11" class="secondary-text">(Output)</text>
<!-- Arrows between Vector components -->
<line x1="500" y1="245" x2="510" y2="245" class="arrow-color" stroke-width="2" marker-end="url(#arrow)" opacity="0.7"/>
<line x1="600" y1="245" x2="610" y2="245" class="arrow-color" stroke-width="2" marker-end="url(#arrow)" opacity="0.7"/>
<!-- Processing/Routing Box -->
<rect x="750" y="140" width="300" height="200" class="neon-purple" rx="6.5"/>
<text x="900" y="170" text-anchor="middle" font-size="22" font-weight="500" class="main-text">Processing</text>
<!-- Filter boxes -->
<rect x="770" y="200" width="130" height="40" class="neon-orange" rx="4"/>
<text x="835" y="225" text-anchor="middle" font-size="14" class="main-text">Filter Errors</text>
<rect x="770" y="250" width="130" height="40" class="neon-cyan" rx="4"/>
<text x="835" y="275" text-anchor="middle" font-size="14" class="main-text">Filter Warnings</text>
<rect x="910" y="200" width="130" height="40" class="neon-green" rx="4"/>
<text x="975" y="225" text-anchor="middle" font-size="14" class="main-text">Log to Metrics</text>
<rect x="910" y="250" width="130" height="40" class="neon-blue" rx="4"/>
<text x="975" y="275" text-anchor="middle" font-size="14" class="main-text">Enrich Data</text>
<!-- Output Sinks -->
<!-- InfluxDB -->
<rect x="1100" y="140" width="150" height="55" class="neon-green" rx="6.5"/>
<text x="1175" y="172" text-anchor="middle" font-size="18" font-weight="500" class="main-text">InfluxDB</text>
<text x="1175" y="190" text-anchor="middle" font-size="12" class="secondary-text">(Metrics)</text>
<!-- Grafana -->
<rect x="1100" y="210" width="150" height="55" class="neon-purple" rx="6.5"/>
<text x="1175" y="242" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Grafana</text>
<text x="1175" y="260" text-anchor="middle" font-size="12" class="secondary-text">(Dashboard)</text>
<!-- Alerts -->
<rect x="1100" y="280" width="150" height="55" class="neon-orange" rx="6.5"/>
<text x="1175" y="312" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Alerts</text>
<text x="1175" y="330" text-anchor="middle" font-size="12" class="secondary-text">(Webhook)</text>
<!-- Flow Arrows -->
<g stroke="#666" stroke-width="2.6" fill="none" opacity="0.7">
<!-- BotServer to Vector -->
<line x1="350" y1="240" x2="400" y2="240" marker-end="url(#arrow)"/>
<!-- Vector to Processing -->
<line x1="700" y1="240" x2="750" y2="240" marker-end="url(#arrow)"/>
<!-- Processing to Outputs -->
<line x1="1050" y1="167" x2="1100" y2="167" marker-end="url(#arrow)"/>
<line x1="1050" y1="237" x2="1100" y2="237" marker-end="url(#arrow)"/>
<line x1="1050" y1="307" x2="1100" y2="307" marker-end="url(#arrow)"/>
</g>
</g>
<!-- Log Sources Detail -->
<g id="log-sources" transform="translate(50, 380)">
<rect width="400" height="160" class="neon-gray" rx="6.5"/>
<text x="200" y="30" text-anchor="middle" font-size="20" font-weight="500" class="main-text">Log Directory Structure</text>
<text x="20" y="60" font-family="monospace" font-size="14" class="secondary-text">logs/system/ → BotServer logs</text>
<text x="20" y="85" font-family="monospace" font-size="14" class="secondary-text">logs/drive/ → SeaweedFS logs</text>
<text x="20" y="110" font-family="monospace" font-size="14" class="secondary-text">logs/tables/ → PostgreSQL logs</text>
<text x="20" y="135" font-family="monospace" font-size="14" class="secondary-text">logs/cache/ → Valkey logs</text>
<text x="220" y="60" font-family="monospace" font-size="14" class="secondary-text">logs/vectordb/ → Qdrant</text>
<text x="220" y="85" font-family="monospace" font-size="14" class="secondary-text">logs/email/ → Stalwart</text>
<text x="220" y="110" font-family="monospace" font-size="14" class="secondary-text">logs/directory/ → Zitadel</text>
<text x="220" y="135" font-family="monospace" font-size="14" class="secondary-text">logs/meet/ → LiveKit</text>
</g>
<!-- Vector Config -->
<g id="vector-config" transform="translate(500, 380)">
<rect width="400" height="160" class="neon-orange" rx="6.5"/>
<text x="200" y="30" text-anchor="middle" font-size="20" font-weight="500" class="main-text">Vector Configuration</text>
<text x="20" y="60" font-family="monospace" font-size="13" class="secondary-text">[sources.botserver_logs]</text>
<text x="20" y="80" font-family="monospace" font-size="13" class="secondary-text">type = "file"</text>
<text x="20" y="100" font-family="monospace" font-size="13" class="secondary-text">include = ["logs/system/*.log"]</text>
<text x="20" y="125" font-family="monospace" font-size="13" class="secondary-text">[transforms.parse_botserver]</text>
<text x="20" y="145" font-family="monospace" font-size="13" class="secondary-text">type = "remap"</text>
</g>
<!-- Key Benefits -->
<g id="benefits" transform="translate(950, 380)">
<rect width="300" height="160" class="neon-cyan" rx="6.5"/>
<text x="150" y="30" text-anchor="middle" font-size="20" font-weight="500" class="main-text">Key Benefits</text>
<text x="20" y="60" font-size="16" class="secondary-text">✓ Zero code changes required</text>
<text x="20" y="85" font-size="16" class="secondary-text">✓ Works with existing logging</text>
<text x="20" y="110" font-size="16" class="secondary-text">✓ Add/remove without recompile</text>
<text x="20" y="135" font-size="16" class="secondary-text">✓ Scales independently</text>
</g>
<!-- PROGRESS INDICATOR -->
<g id="progress-legend">
<rect x="50" y="580" width="1300" height="90" fill="url(#flowGradient)" rx="10" opacity="0.2"/>
<!-- Stage markers -->
<circle cx="200" cy="625" r="13" class="neon-blue"/>
<circle cx="550" cy="625" r="13" class="neon-orange"/>
<circle cx="900" cy="625" r="13" class="neon-purple"/>
<circle cx="1200" cy="625" r="13" class="neon-green"/>
<!-- Connecting lines -->
<line x1="213" y1="625" x2="537" y2="625" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="563" y1="625" x2="887" y2="625" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="913" y1="625" x2="1187" y2="625" class="arrow-color" stroke-width="2" opacity="0.4"/>
<!-- Stage labels -->
<text x="200" y="600" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Generate</text>
<text x="200" y="655" text-anchor="middle" font-size="14" class="secondary-text">log::*!()</text>
<text x="550" y="600" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Collect</text>
<text x="550" y="655" text-anchor="middle" font-size="14" class="secondary-text">Vector Agent</text>
<text x="900" y="600" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Process</text>
<text x="900" y="655" text-anchor="middle" font-size="14" class="secondary-text">Parse & Route</text>
<text x="1200" y="600" text-anchor="middle" font-size="16" font-weight="500" class="main-text">Store/Alert</text>
<text x="1200" y="655" text-anchor="middle" font-size="14" class="secondary-text">InfluxDB/Grafana</text>
</g>
<!-- Metrics Info -->
<g id="metrics-info" transform="translate(50, 710)">
<text x="0" y="0" font-size="18" font-weight="500" class="main-text">Automatic Metrics:</text>
<rect x="0" y="20" width="200" height="35" class="neon-blue" rx="4" opacity="0.5"/>
<text x="100" y="43" text-anchor="middle" font-size="14" class="main-text">log_events_total</text>
<rect x="220" y="20" width="200" height="35" class="neon-orange" rx="4" opacity="0.5"/>
<text x="320" y="43" text-anchor="middle" font-size="14" class="main-text">errors_total</text>
<rect x="440" y="20" width="200" height="35" class="neon-purple" rx="4" opacity="0.5"/>
<text x="540" y="43" text-anchor="middle" font-size="14" class="main-text">warnings_total</text>
<rect x="660" y="20" width="200" height="35" class="neon-green" rx="4" opacity="0.5"/>
<text x="760" y="43" text-anchor="middle" font-size="14" class="main-text">llm_latency_seconds</text>
<rect x="880" y="20" width="200" height="35" class="neon-cyan" rx="4" opacity="0.5"/>
<text x="980" y="43" text-anchor="middle" font-size="14" class="main-text">sessions_active</text>
</g>
<!-- Description -->
<text x="700" y="810" text-anchor="middle" font-size="21" class="secondary-text">
Keep using standard Rust log macros - Vector handles collection, parsing, and routing
</text>
<text x="700" y="845" text-anchor="middle" font-size="18" class="secondary-text">
Install with: ./botserver install observability
</text>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,93 +1,205 @@
<svg width="800" height="400" xmlns="http://www.w3.org/2000/svg">
<svg width="1400" height="900" xmlns="http://www.w3.org/2000/svg">
<style>
/* Light theme defaults */
.neon-blue { stroke: #4A90E2; stroke-width: 2.6; fill: none; }
.neon-orange { stroke: #F5A623; stroke-width: 2.6; fill: none; }
.neon-purple { stroke: #BD10E0; stroke-width: 2.6; fill: none; }
.neon-green { stroke: #7ED321; stroke-width: 2.6; fill: none; }
.neon-cyan { stroke: #50E3C2; stroke-width: 2.6; fill: none; }
.main-text { fill: #1a1a1a; }
.secondary-text { fill: #666; }
.arrow-color { stroke: #666; fill: #666; }
/* Dark theme with subtle neon effects */
@media (prefers-color-scheme: dark) {
.neon-blue {
stroke: #00D4FF;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00D4FF) drop-shadow(0 0 8px #00A0FF);
}
.neon-orange {
stroke: #FF9500;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #FF9500) drop-shadow(0 0 8px #FF7700);
}
.neon-purple {
stroke: #E040FB;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #E040FB) drop-shadow(0 0 8px #D500F9);
}
.neon-green {
stroke: #00FF88;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00FF88) drop-shadow(0 0 8px #00E676);
}
.neon-cyan {
stroke: #00E5EA;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00E5EA) drop-shadow(0 0 8px #00BCD4);
}
.main-text { fill: #FFFFFF; }
.secondary-text { fill: #B0B0B0; }
.arrow-color { stroke: #B0B0B0; fill: #B0B0B0; }
}
</style>
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#666"/>
<marker id="arrow" markerWidth="13" markerHeight="13" refX="11.7" refY="3.9" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,7.8 L11.7,3.9 z" class="arrow-color"/>
</marker>
<linearGradient id="flowGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#4A90E2;stop-opacity:0.3" />
<stop offset="25%" style="stop-color:#F5A623;stop-opacity:0.3" />
<stop offset="50%" style="stop-color:#BD10E0;stop-opacity:0.3" />
<stop offset="75%" style="stop-color:#7ED321;stop-opacity:0.3" />
<stop offset="100%" style="stop-color:#50E3C2;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- Title -->
<text x="400" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" font-weight="600" fill="#333">Package System Flow</text>
<text x="700" y="45" text-anchor="middle" font-family="Arial, sans-serif" font-size="32" font-weight="600" class="main-text">Package System Flow</text>
<text x="700" y="75" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="secondary-text">How .gbai packages orchestrate bot behavior</text>
<!-- Main Components -->
<!-- MAIN FLOW DIAGRAM -->
<g id="main-flow">
<!-- Phase Labels -->
<text x="120" y="115" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Input</text>
<text x="320" y="115" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Script</text>
<text x="560" y="115" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Intelligence</text>
<text x="820" y="115" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Execution</text>
<text x="1060" y="115" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="500" class="secondary-text">Output</text>
<!-- Main Flow Components -->
<!-- User Request -->
<rect x="20" y="60" width="100" height="40" fill="none" stroke="#4A90E2" stroke-width="2" rx="5"/>
<text x="70" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">User Request</text>
<rect x="50" y="140" width="140" height="70" class="neon-blue" rx="6.5"/>
<text x="120" y="180" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="500" class="main-text">User Request</text>
<!-- start.bas -->
<rect x="160" y="60" width="100" height="40" fill="none" stroke="#F5A623" stroke-width="2" rx="5"/>
<text x="210" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">start.bas</text>
<rect x="250" y="140" width="140" height="70" class="neon-orange" rx="6.5"/>
<text x="320" y="180" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="500" class="main-text">start.bas</text>
<!-- LLM Engine -->
<rect x="300" y="60" width="100" height="40" fill="none" stroke="#BD10E0" stroke-width="2" rx="5"/>
<text x="350" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">LLM Engine</text>
<rect x="470" y="140" width="180" height="70" class="neon-purple" rx="6.5"/>
<text x="560" y="180" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="500" class="main-text">LLM Engine</text>
<!-- Tool Executor -->
<rect x="730" y="140" width="180" height="70" class="neon-green" rx="6.5"/>
<text x="820" y="180" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="500" class="main-text">Tool Executor</text>
<!-- Bot Response -->
<rect x="440" y="60" width="100" height="40" fill="none" stroke="#50E3C2" stroke-width="2" rx="5"/>
<text x="490" y="85" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Bot Response</text>
<rect x="990" y="140" width="140" height="70" class="neon-cyan" rx="6.5"/>
<text x="1060" y="180" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="500" class="main-text">Bot Response</text>
<!-- Supporting Components -->
<!-- Supporting Components Row -->
<!-- Vector Search -->
<rect x="240" y="160" width="120" height="40" fill="none" stroke="#4A90E2" stroke-width="2" rx="5"/>
<text x="300" y="185" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Vector Search</text>
<rect x="450" y="280" width="160" height="60" class="neon-blue" rx="6.5"/>
<text x="530" y="315" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="500" class="main-text">Vector Search</text>
<!-- .gbkb docs -->
<rect x="240" y="240" width="120" height="40" fill="none" stroke="#F5A623" stroke-width="2" rx="5"/>
<text x="300" y="265" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">.gbkb docs</text>
<!-- Knowledge Base -->
<rect x="650" y="280" width="160" height="60" class="neon-orange" rx="6.5"/>
<text x="730" y="315" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="500" class="main-text">.gbkb Docs</text>
<!-- BASIC Commands Detail -->
<g transform="translate(580, 60)">
<rect width="200" height="120" fill="none" stroke="#7ED321" stroke-width="2" rx="5"/>
<text x="100" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">BASIC Commands</text>
<text x="10" y="50" font-family="monospace" font-size="12" fill="#666">USE KB "docs"</text>
<text x="10" y="70" font-family="monospace" font-size="12" fill="#666">answer = HEAR</text>
<text x="10" y="90" font-family="monospace" font-size="12" fill="#666">result = LLM()</text>
<text x="10" y="110" font-family="monospace" font-size="12" fill="#666">TALK result</text>
</g>
<!-- Package Structure -->
<g transform="translate(580, 210)">
<rect width="200" height="140" fill="none" stroke="#4A90E2" stroke-width="2" rx="5"/>
<text x="100" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Package Structure</text>
<text x="10" y="50" font-family="monospace" font-size="12" fill="#666">my-bot.gbai/</text>
<text x="20" y="70" font-family="monospace" font-size="12" fill="#666">├─ .gbdialog/</text>
<text x="20" y="90" font-family="monospace" font-size="12" fill="#666">├─ .gbkb/</text>
<text x="20" y="110" font-family="monospace" font-size="12" fill="#666">└─ .gbot/</text>
</g>
<!-- External APIs -->
<rect x="850" y="280" width="160" height="60" class="neon-purple" rx="6.5"/>
<text x="930" y="315" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="500" class="main-text">External APIs</text>
<!-- Flow Arrows -->
<g stroke="#666" stroke-width="2" fill="none">
<!-- Main flow -->
<line x1="120" y1="80" x2="160" y2="80" marker-end="url(#arrow)"/>
<line x1="260" y1="80" x2="300" y2="80" marker-end="url(#arrow)"/>
<line x1="400" y1="80" x2="440" y2="80" marker-end="url(#arrow)"/>
<g stroke="#666" stroke-width="2.6" fill="none" opacity="0.7">
<!-- Main horizontal flow -->
<line x1="190" y1="175" x2="250" y2="175" marker-end="url(#arrow)"/>
<line x1="390" y1="175" x2="470" y2="175" marker-end="url(#arrow)"/>
<line x1="650" y1="175" x2="730" y2="175" marker-end="url(#arrow)"/>
<line x1="910" y1="175" x2="990" y2="175" marker-end="url(#arrow)"/>
<!-- LLM to Vector Search (downward) -->
<line x1="350" y1="100" x2="300" y2="160" stroke-dasharray="3,3" marker-end="url(#arrow)" opacity="0.6"/>
<!-- LLM to Vector Search -->
<line x1="530" y1="210" x2="530" y2="280" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.5"/>
<!-- Vector Search to .gbkb docs -->
<line x1="300" y1="200" x2="300" y2="240" marker-end="url(#arrow)" opacity="0.6"/>
<!-- Vector Search to Knowledge Base -->
<line x1="610" y1="310" x2="650" y2="310" marker-end="url(#arrow)" opacity="0.6"/>
<!-- Feedback from start.bas to Vector Search (Results) -->
<path d="M240,100 Q240,130 270,130 Q270,160 270,160" stroke-dasharray="3,3" marker-end="url(#arrow)" opacity="0.6"/>
<!-- Tool Executor to External APIs -->
<line x1="850" y1="210" x2="930" y2="280" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.5"/>
<!-- Feedback from Vector Search to LLM (Context) -->
<path d="M330,160 Q330,130 350,130 Q350,110 350,100" stroke-dasharray="3,3" marker-end="url(#arrow)" opacity="0.6"/>
<!-- Connection from start.bas to BASIC Commands (top right) -->
<path d="M260,70 Q400,40 580,80" stroke-dasharray="2,2" opacity="0.3"/>
<!-- Connection from .gbkb docs to Package Structure (bottom right) -->
<path d="M360,265 Q470,280 580,290" stroke-dasharray="2,2" opacity="0.3"/>
<!-- Context feedback to LLM -->
<path d="M730,280 Q680,250 620,210" stroke-dasharray="3.9,3.9" marker-end="url(#arrow)" opacity="0.5"/>
</g>
<!-- Labels on key connections -->
<text x="340" y="50" font-family="Arial, sans-serif" font-size="11" fill="#666">Commands</text>
<text x="230" y="145" font-family="Arial, sans-serif" font-size="11" fill="#666">Results</text>
<text x="365" y="145" font-family="Arial, sans-serif" font-size="11" fill="#666">Query</text>
<text x="340" y="125" font-family="Arial, sans-serif" font-size="11" fill="#666">Context</text>
<!-- Labels -->
<text x="460" y="250" font-family="Arial, sans-serif" font-size="14" class="secondary-text">Query</text>
<text x="680" y="250" font-family="Arial, sans-serif" font-size="14" class="secondary-text">Context</text>
</g>
<!-- Package Structure Detail -->
<g id="package-structure" transform="translate(50, 380)">
<rect width="300" height="180" class="neon-green" rx="6.5"/>
<text x="150" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="500" class="main-text">Package Structure</text>
<text x="20" y="60" font-family="monospace" font-size="16" class="secondary-text">my-bot.gbai/</text>
<text x="35" y="85" font-family="monospace" font-size="16" class="secondary-text">├── .gbdialog/</text>
<text x="55" y="105" font-family="monospace" font-size="14" class="secondary-text">└── start.bas, tools.bas</text>
<text x="35" y="130" font-family="monospace" font-size="16" class="secondary-text">├── .gbkb/</text>
<text x="55" y="150" font-family="monospace" font-size="14" class="secondary-text">└── docs, PDFs, data</text>
<text x="35" y="170" font-family="monospace" font-size="16" class="secondary-text">└── .gbot/config.csv</text>
</g>
<!-- BASIC Commands Detail -->
<g id="basic-commands" transform="translate(400, 380)">
<rect width="300" height="180" class="neon-purple" rx="6.5"/>
<text x="150" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="500" class="main-text">BASIC Commands</text>
<text x="20" y="60" font-family="monospace" font-size="16" class="secondary-text">USE KB "knowledge"</text>
<text x="20" y="85" font-family="monospace" font-size="16" class="secondary-text">question = HEAR</text>
<text x="20" y="110" font-family="monospace" font-size="16" class="secondary-text">answer = LLM(question)</text>
<text x="20" y="135" font-family="monospace" font-size="16" class="secondary-text">TALK answer</text>
<text x="20" y="165" font-family="monospace" font-size="14" class="secondary-text">// Zero configuration AI</text>
</g>
<!-- Key Features -->
<g id="features" transform="translate(750, 380)">
<rect width="300" height="180" class="neon-cyan" rx="6.5"/>
<text x="150" y="30" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="500" class="main-text">Key Features</text>
<text x="20" y="60" font-family="Arial, sans-serif" font-size="16" class="secondary-text">✓ Auto tool registration</text>
<text x="20" y="85" font-family="Arial, sans-serif" font-size="16" class="secondary-text">✓ RAG from any document</text>
<text x="20" y="110" font-family="Arial, sans-serif" font-size="16" class="secondary-text">✓ Multi-channel deploy</text>
<text x="20" y="135" font-family="Arial, sans-serif" font-size="16" class="secondary-text">✓ Hot reload packages</text>
<text x="20" y="160" font-family="Arial, sans-serif" font-size="16" class="secondary-text">✓ Session persistence</text>
</g>
<!-- Connection lines from boxes to main flow -->
<g stroke="#666" stroke-width="1.5" fill="none" opacity="0.3">
<path d="M200,380 Q200,350 320,210"/>
<path d="M550,380 Q550,350 560,210"/>
<path d="M900,380 Q900,350 850,210"/>
</g>
<!-- PROGRESS INDICATOR -->
<g id="progress-legend">
<rect x="50" y="600" width="1300" height="80" fill="url(#flowGradient)" rx="10" opacity="0.2"/>
<!-- Stage markers -->
<circle cx="150" cy="640" r="12" class="neon-blue"/>
<circle cx="400" cy="640" r="12" class="neon-orange"/>
<circle cx="650" cy="640" r="12" class="neon-purple"/>
<circle cx="900" cy="640" r="12" class="neon-green"/>
<circle cx="1150" cy="640" r="12" class="neon-cyan"/>
<!-- Connecting lines -->
<line x1="162" y1="640" x2="388" y2="640" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="412" y1="640" x2="638" y2="640" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="662" y1="640" x2="888" y2="640" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="912" y1="640" x2="1138" y2="640" class="arrow-color" stroke-width="2" opacity="0.4"/>
<!-- Stage labels -->
<text x="150" y="695" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" class="secondary-text">Receive</text>
<text x="400" y="695" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" class="secondary-text">Parse Script</text>
<text x="650" y="695" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" class="secondary-text">LLM Process</text>
<text x="900" y="695" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" class="secondary-text">Execute Tools</text>
<text x="1150" y="695" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" class="secondary-text">Respond</text>
</g>
<!-- Description -->
<text x="400" y="380" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#666">
<text x="700" y="760" text-anchor="middle" font-family="Arial, sans-serif" font-size="21" class="secondary-text">
BASIC scripts orchestrate LLM decisions, vector search, and responses with zero configuration
</text>
<text x="700" y="790" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" class="secondary-text">
Drop files in .gbkb → Write scripts in .gbdialog → Deploy instantly
</text>
</svg>

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,239 @@
<svg width="1400" height="900" xmlns="http://www.w3.org/2000/svg">
<style>
/* Light theme defaults */
.neon-blue { stroke: #4A90E2; stroke-width: 2.6; fill: none; }
.neon-orange { stroke: #F5A623; stroke-width: 2.6; fill: none; }
.neon-purple { stroke: #BD10E0; stroke-width: 2.6; fill: none; }
.neon-green { stroke: #7ED321; stroke-width: 2.6; fill: none; }
.neon-cyan { stroke: #50E3C2; stroke-width: 2.6; fill: none; }
.neon-gray { stroke: #888888; stroke-width: 2.6; fill: none; }
.main-text { fill: #1a1a1a; font-family: Arial, sans-serif; }
.secondary-text { fill: #666; font-family: Arial, sans-serif; }
.arrow-color { stroke: #666; fill: #666; }
/* Dark theme with subtle neon effects */
@media (prefers-color-scheme: dark) {
.neon-blue {
stroke: #00D4FF;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00D4FF) drop-shadow(0 0 8px #00A0FF);
}
.neon-orange {
stroke: #FF9500;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #FF9500) drop-shadow(0 0 8px #FF7700);
}
.neon-purple {
stroke: #E040FB;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #E040FB) drop-shadow(0 0 8px #D500F9);
}
.neon-green {
stroke: #00FF88;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00FF88) drop-shadow(0 0 8px #00E676);
}
.neon-cyan {
stroke: #00E5EA;
stroke-width: 2.8;
filter: drop-shadow(0 0 4px #00E5EA) drop-shadow(0 0 8px #00BCD4);
}
.neon-gray {
stroke: #AAAAAA;
stroke-width: 2.8;
filter: drop-shadow(0 0 2px #888888);
}
.main-text { fill: #FFFFFF; }
.secondary-text { fill: #B0B0B0; }
.arrow-color { stroke: #B0B0B0; fill: #B0B0B0; }
}
</style>
<defs>
<marker id="arrow" markerWidth="13" markerHeight="13" refX="11.7" refY="3.9" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,7.8 L11.7,3.9 z" class="arrow-color"/>
</marker>
<linearGradient id="flowGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#4A90E2;stop-opacity:0.3" />
<stop offset="20%" style="stop-color:#F5A623;stop-opacity:0.3" />
<stop offset="40%" style="stop-color:#BD10E0;stop-opacity:0.3" />
<stop offset="60%" style="stop-color:#7ED321;stop-opacity:0.3" />
<stop offset="80%" style="stop-color:#50E3C2;stop-opacity:0.3" />
<stop offset="100%" style="stop-color:#4A90E2;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- Title -->
<text x="700" y="45" text-anchor="middle" font-size="32" font-weight="600" class="main-text">Script Execution Flow</text>
<text x="700" y="75" text-anchor="middle" font-size="18" class="secondary-text">BASIC Compilation Pipeline - From Source to Execution</text>
<!-- MAIN FLOW DIAGRAM -->
<g id="main-flow">
<!-- Entry Points Section -->
<text x="200" y="115" text-anchor="middle" font-size="18" font-weight="500" class="secondary-text">Entry Points</text>
<!-- start.bas -->
<rect x="50" y="135" width="140" height="65" class="neon-blue" rx="6.5"/>
<text x="120" y="165" text-anchor="middle" font-size="16" font-weight="500" class="main-text">start.bas</text>
<text x="120" y="185" text-anchor="middle" font-size="12" class="secondary-text">(Bot Start)</text>
<!-- SET SCHEDULE -->
<rect x="210" y="135" width="140" height="65" class="neon-orange" rx="6.5"/>
<text x="280" y="165" text-anchor="middle" font-size="16" font-weight="500" class="main-text">SET SCHEDULE</text>
<text x="280" y="185" text-anchor="middle" font-size="12" class="secondary-text">(Cron Jobs)</text>
<!-- WEBHOOK -->
<rect x="370" y="135" width="140" height="65" class="neon-purple" rx="6.5"/>
<text x="440" y="165" text-anchor="middle" font-size="16" font-weight="500" class="main-text">WEBHOOK</text>
<text x="440" y="185" text-anchor="middle" font-size="12" class="secondary-text">(HTTP POST)</text>
<!-- Compilation Pipeline Box -->
<rect x="50" y="240" width="550" height="220" class="neon-gray" rx="10"/>
<text x="325" y="270" text-anchor="middle" font-size="20" font-weight="600" class="main-text">BASIC Compiler &amp; Runtime</text>
<!-- Pipeline Steps -->
<rect x="70" y="295" width="120" height="50" class="neon-blue" rx="6.5"/>
<text x="130" y="325" text-anchor="middle" font-size="14" font-weight="500" class="main-text">1. Load Source</text>
<rect x="210" y="295" width="120" height="50" class="neon-orange" rx="6.5"/>
<text x="270" y="325" text-anchor="middle" font-size="14" font-weight="500" class="main-text">2. Load Config</text>
<rect x="350" y="295" width="120" height="50" class="neon-purple" rx="6.5"/>
<text x="410" y="325" text-anchor="middle" font-size="14" font-weight="500" class="main-text">3. Preprocess</text>
<rect x="490" y="295" width="90" height="50" class="neon-green" rx="6.5"/>
<text x="535" y="325" text-anchor="middle" font-size="14" font-weight="500" class="main-text">4. Compile</text>
<!-- Pipeline descriptions -->
<text x="130" y="365" text-anchor="middle" font-size="11" class="secondary-text">Read .bas file</text>
<text x="270" y="365" text-anchor="middle" font-size="11" class="secondary-text">param-* vars</text>
<text x="410" y="365" text-anchor="middle" font-size="11" class="secondary-text">Transform</text>
<text x="535" y="365" text-anchor="middle" font-size="11" class="secondary-text">Rhai AST</text>
<!-- Execute Box -->
<rect x="70" y="390" width="510" height="55" class="neon-cyan" rx="6.5"/>
<text x="325" y="415" text-anchor="middle" font-size="16" font-weight="500" class="main-text">5. Execute with Injected Scope</text>
<text x="325" y="435" text-anchor="middle" font-size="12" class="secondary-text">Keywords call registered handlers → LLM Tools → API Calls</text>
<!-- Arrows between pipeline steps -->
<g stroke="#666" stroke-width="2" opacity="0.7">
<line x1="190" y1="320" x2="210" y2="320" marker-end="url(#arrow)"/>
<line x1="330" y1="320" x2="350" y2="320" marker-end="url(#arrow)"/>
<line x1="470" y1="320" x2="490" y2="320" marker-end="url(#arrow)"/>
</g>
<!-- Entry points to pipeline -->
<g stroke="#666" stroke-width="2.6" opacity="0.7">
<line x1="120" y1="200" x2="120" y2="240" marker-end="url(#arrow)"/>
<line x1="280" y1="200" x2="280" y2="240" marker-end="url(#arrow)"/>
<line x1="440" y1="200" x2="440" y2="240" marker-end="url(#arrow)"/>
</g>
<!-- Output Components -->
<text x="900" y="115" text-anchor="middle" font-size="18" font-weight="500" class="secondary-text">Execution Outputs</text>
<!-- LLM Tools -->
<rect x="650" y="135" width="160" height="65" class="neon-purple" rx="6.5"/>
<text x="730" y="165" text-anchor="middle" font-size="16" font-weight="500" class="main-text">LLM Tools</text>
<text x="730" y="185" text-anchor="middle" font-size="12" class="secondary-text">(ADD TOOL)</text>
<!-- ON Events -->
<rect x="830" y="135" width="160" height="65" class="neon-green" rx="6.5"/>
<text x="910" y="165" text-anchor="middle" font-size="16" font-weight="500" class="main-text">ON Events</text>
<text x="910" y="185" text-anchor="middle" font-size="12" class="secondary-text">(Triggers)</text>
<!-- API Calls -->
<rect x="1010" y="135" width="160" height="65" class="neon-cyan" rx="6.5"/>
<text x="1090" y="165" text-anchor="middle" font-size="16" font-weight="500" class="main-text">API Calls</text>
<text x="1090" y="185" text-anchor="middle" font-size="12" class="secondary-text">(External)</text>
<!-- Connection from Execute to outputs -->
<g stroke="#666" stroke-width="2.6" opacity="0.7">
<line x1="580" y1="420" x2="620" y2="420"/>
<line x1="620" y1="420" x2="620" y2="167"/>
<line x1="620" y1="167" x2="650" y2="167" marker-end="url(#arrow)"/>
<line x1="620" y1="167" x2="830" y2="167" marker-end="url(#arrow)"/>
<line x1="620" y1="167" x2="1010" y2="167" marker-end="url(#arrow)"/>
</g>
</g>
<!-- Package Structure -->
<g id="package-structure" transform="translate(650, 240)">
<rect width="520" height="220" class="neon-orange" rx="10"/>
<text x="260" y="35" text-anchor="middle" font-size="20" font-weight="600" class="main-text">Package Structure (mybot.gbai)</text>
<text x="30" y="70" font-family="monospace" font-size="14" class="secondary-text">mybot.gbai/</text>
<text x="45" y="95" font-family="monospace" font-size="14" class="secondary-text">├── mybot.gbdialog/</text>
<text x="65" y="118" font-family="monospace" font-size="13" class="secondary-text">├── start.bas ' Entry point</text>
<text x="65" y="138" font-family="monospace" font-size="13" class="secondary-text">├── tables.bas ' TABLE definitions</text>
<text x="65" y="158" font-family="monospace" font-size="13" class="secondary-text">├── create-order.bas ' Tool script</text>
<text x="65" y="178" font-family="monospace" font-size="13" class="secondary-text">└── sync-data.bas ' Scheduled script</text>
<text x="45" y="200" font-family="monospace" font-size="14" class="secondary-text">└── mybot.gbot/config.csv</text>
</g>
<!-- Config Details -->
<g id="config-details" transform="translate(50, 500)">
<rect width="400" height="140" class="neon-blue" rx="6.5"/>
<text x="200" y="30" text-anchor="middle" font-size="18" font-weight="500" class="main-text">config.csv (Injected Variables)</text>
<text x="20" y="60" font-family="monospace" font-size="14" class="secondary-text">param-api-key,sk-xxxxx</text>
<text x="20" y="85" font-family="monospace" font-size="14" class="secondary-text">param-webhook-url,https://...</text>
<text x="20" y="110" font-family="monospace" font-size="14" class="secondary-text">param-max-retries,3</text>
<text x="20" y="130" font-family="monospace" font-size="12" class="secondary-text">→ Available as variables in BASIC</text>
</g>
<!-- Preprocessing Details -->
<g id="preprocess-details" transform="translate(500, 500)">
<rect width="400" height="140" class="neon-purple" rx="6.5"/>
<text x="200" y="30" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Preprocessing Steps</text>
<text x="20" y="60" font-size="14" class="secondary-text">• Case normalization (TALK → talk)</text>
<text x="20" y="85" font-size="14" class="secondary-text">• Multi-word keyword transform</text>
<text x="20" y="110" font-size="14" class="secondary-text">• FOR EACH block handling</text>
<text x="20" y="130" font-size="14" class="secondary-text">• Variable scope injection</text>
</g>
<!-- Keyword Execution -->
<g id="keyword-exec" transform="translate(950, 500)">
<rect width="370" height="140" class="neon-green" rx="6.5"/>
<text x="185" y="30" text-anchor="middle" font-size="18" font-weight="500" class="main-text">Keyword Execution</text>
<text x="20" y="60" font-size="14" class="secondary-text">• TALK → Send message to channel</text>
<text x="20" y="85" font-size="14" class="secondary-text">• HEAR → Wait for user input</text>
<text x="20" y="110" font-size="14" class="secondary-text">• LLM → Call language model</text>
<text x="20" y="130" font-size="14" class="secondary-text">• GET/POST → HTTP requests</text>
</g>
<!-- PROGRESS INDICATOR -->
<g id="progress-legend">
<rect x="50" y="680" width="1300" height="80" fill="url(#flowGradient)" rx="10" opacity="0.2"/>
<!-- Stage markers -->
<circle cx="150" cy="720" r="12" class="neon-blue"/>
<circle cx="380" cy="720" r="12" class="neon-orange"/>
<circle cx="610" cy="720" r="12" class="neon-purple"/>
<circle cx="840" cy="720" r="12" class="neon-green"/>
<circle cx="1070" cy="720" r="12" class="neon-cyan"/>
<circle cx="1250" cy="720" r="12" class="neon-blue"/>
<!-- Connecting lines -->
<line x1="162" y1="720" x2="368" y2="720" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="392" y1="720" x2="598" y2="720" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="622" y1="720" x2="828" y2="720" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="852" y1="720" x2="1058" y2="720" class="arrow-color" stroke-width="2" opacity="0.4"/>
<line x1="1082" y1="720" x2="1238" y2="720" class="arrow-color" stroke-width="2" opacity="0.4"/>
<!-- Stage labels -->
<text x="150" y="750" text-anchor="middle" font-size="14" class="secondary-text">Load</text>
<text x="380" y="750" text-anchor="middle" font-size="14" class="secondary-text">Config</text>
<text x="610" y="750" text-anchor="middle" font-size="14" class="secondary-text">Preprocess</text>
<text x="840" y="750" text-anchor="middle" font-size="14" class="secondary-text">Compile</text>
<text x="1070" y="750" text-anchor="middle" font-size="14" class="secondary-text">Execute</text>
<text x="1250" y="750" text-anchor="middle" font-size="14" class="secondary-text">Respond</text>
</g>
<!-- Description -->
<text x="700" y="810" text-anchor="middle" font-size="21" class="secondary-text">
BASIC scripts compile to Rhai AST and execute with registered keyword handlers
</text>
<text x="700" y="845" text-anchor="middle" font-size="18" class="secondary-text">
Scripts in .gbdialog/ are hot-reloaded on change - no restart required
</text>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1 @@
# KB COLLECTION STATS

View file

@ -0,0 +1 @@
# KB DOCUMENTS ADDED SINCE

View file

@ -0,0 +1 @@
# KB DOCUMENTS COUNT

View file

@ -0,0 +1 @@
# KB LIST COLLECTIONS

View file

@ -0,0 +1 @@
# KB STATISTICS

View file

@ -0,0 +1 @@
# KB STORAGE SIZE

View file

@ -6,33 +6,9 @@ Understanding how General Bots BASIC scripts are loaded, compiled, and executed
Scripts in General Bots can be triggered through several entry points:
```
┌─────────────────────────────────────────────────────────────────┐
│ SCRIPT ENTRY POINTS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ start.bas │ │ SET SCHEDULE│ │ WEBHOOK │ │
│ │ (Bot Start) │ │ (Cron Jobs) │ │ (HTTP POST) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ BASIC COMPILER & RUNTIME │ │
│ │ 1. Load config.csv param-* variables │ │
│ │ 2. Preprocess (case normalization, syntax transform) │ │
│ │ 3. Compile to AST │ │
│ │ 4. Execute with injected scope │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ LLM Tools │ │ ON Events │ │ API Calls │ │
│ │ (ADD TOOL) │ │ (Triggers) │ │ (External) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
![Script Execution Flow](../assets/script-execution-flow.svg)
*BASIC scripts compile to Rhai AST and execute with registered keyword handlers. Scripts in .gbdialog/ are hot-reloaded on change.*
### 1. Bot Startup (`start.bas`)

View file

@ -12,36 +12,11 @@ General Bots uses a modular architecture where each component runs in isolated L
- **Portability**: Move containers between hosts easily
## Component Diagram
## High Availability Architecture
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ Load Balancer (Caddy) │
│ Rate Limiting │ TLS Termination │
└─────────────────────────────────┬────────────────────────────────────────────┘
┌────────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ BotServer 1 │ │ BotServer 2 │ │ BotServer N │
│ (LXC/Auto) │ │ (LXC/Auto) │ │ (LXC/Auto) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└──────────────────────┼──────────────────────┘
┌───────────────────────────┼───────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────────┐ ┌──────────┐
│ Secrets │ │ Data Layer │ │ Services │
│ (Vault) │ │ │ │ │
└─────────┘ │ PostgreSQL │ │ Zitadel │
│ Redis │ │ LiveKit │
│ Qdrant │ │ Stalwart │
│ InfluxDB │ │ MinIO │
│ MinIO │ │ Forgejo │
└─────────────┘ └──────────┘
```
![Infrastructure Architecture](../assets/infrastructure-architecture.svg)
*Production-ready infrastructure with automatic scaling, load balancing, and multi-tenant isolation.*
## Encryption at Rest
@ -232,23 +207,15 @@ messaging-retention-hours,24
### Option 1: Tenant-Based Sharding (Recommended)
Each tenant/organization gets isolated databases:
## Multi-Tenant Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Router │
│ (tenant_id → database mapping) │
└─────────────────────────────┬───────────────────────────────────┘
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ tenant_001 │ │ tenant_002 │ │ tenant_003 │
│ PostgreSQL │ │ PostgreSQL │ │ PostgreSQL │
│ Redis │ │ Redis │ │ Redis │
│ Qdrant │ │ Qdrant │ │ Qdrant │
└───────────────┘ └───────────────┘ └───────────────┘
```
Each tenant gets isolated resources with dedicated database schemas, cache namespaces, and vector collections. The router maps tenant IDs to their respective data stores automatically.
**Key isolation features:**
- Database-per-tenant or schema-per-tenant options
- Namespace isolation in Valkey cache
- Collection isolation in Qdrant vectors
- Bucket isolation in SeaweedFS storage
Configuration:
@ -332,21 +299,17 @@ shard-regions,us-east,eu-west,ap-south
shard-default,us-east
shard-detection,ip
```
## Geographic Distribution
```
┌─────────────────────────────────────────────────────────────┐
│ Global Router │
│ (GeoIP → Region mapping) │
└─────────────────────────────┬───────────────────────────────┘
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ US-East │ │ EU-West │ │ AP-South │
│ Cluster │ │ Cluster │ │ Cluster │
└───────────────┘ └───────────────┘ └───────────────┘
```
Global router uses GeoIP to direct users to the nearest regional cluster:
| Region | Location | Services |
|--------|----------|----------|
| US-East | Virginia | Full cluster |
| EU-West | Frankfurt | Full cluster |
| AP-South | Singapore | Full cluster |
Each regional cluster runs independently with data replication between regions for disaster recovery.
## Auto-Scaling with LXC
@ -394,20 +357,19 @@ RestartSec=10
WantedBy=multi-user.target
```
### Container Lifecycle
## Container Lifecycle
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Create │ ──▶ │Configure │ ──▶ │ Start │ ──▶ │ Ready │
│Container │ │Resources │ │BotServer │ │(In Pool) │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Delete │ ◀── │ Stop │ ◀── │ Drain │ ◀── │ Active │
│Container │ │BotServer │ │ Conns │ │(Serving) │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
```
**Startup Flow:**
1. **Create** → LXC container created from template
2. **Configure** → Resources allocated (CPU, memory, storage)
3. **Start** → BotServer binary launched
4. **Ready** → Added to load balancer pool
**Shutdown Flow:**
1. **Active** → Container serving requests
2. **Drain** → Stop accepting new connections
3. **Stop** → Graceful BotServer shutdown
4. **Delete** → Container removed (or returned to pool)
## Load Balancing
@ -502,20 +464,17 @@ States:
### Database Failover
PostgreSQL with streaming replication:
## Database Replication
```
┌──────────────┐ ┌──────────────┐
│ Primary │ ──────▶ │ Replica │
│ PostgreSQL │ (sync) │ PostgreSQL │
└──────────────┘ └──────────────┘
│ │
└────────┬───────────────┘
┌──────┴──────┐
│ Patroni │
│ (Failover) │
└─────────────┘
```
PostgreSQL replication is managed by Patroni for automatic failover:
| Component | Role | Description |
|-----------|------|-------------|
| Primary | Write leader | Handles all write operations |
| Replica | Read replica | Synchronous replication from primary |
| Patroni | Failover manager | Automatic leader election on failure |
Failover happens automatically within seconds, with clients redirected via connection pooler.
### Graceful Degradation

View file

@ -4,38 +4,9 @@ General Bots uses a comprehensive observability stack for monitoring, logging, a
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ BotServer Application │
│ │
│ log::trace!() ──┐ │
│ log::debug!() ──┼──▶ Log Files (./botserver-stack/logs/) │
│ log::info!() ──┤ │
│ log::warn!() ──┤ │
│ log::error!() ──┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Vector Agent │
│ (Collects from log files) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Sources │ ──▶ │ Transforms │ ──▶ │ Sinks │ │
│ │ (Files) │ │ (Parse) │ │ (Outputs) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ InfluxDB │ │ Grafana │ │ Alerts │
│ (Metrics) │ │(Dashboard)│ │(Webhook) │
└───────────┘ └───────────┘ └───────────┘
```
![Observability Flow](../assets/observability-flow.svg)
*Vector Agent collects logs from BotServer without requiring any code changes.*
## No Code Changes Required

View file

@ -9,6 +9,8 @@
//! - POST /files/write - Write file content
//! - POST /files/delete - Delete file/folder
//! - POST /files/create-folder - Create new folder
//! - GET /files/versions - List file versions
//! - POST /files/restore - Restore file to specific version
#[cfg(feature = "console")]
use crate::console::file_tree::FileTree;
@ -147,6 +149,44 @@ pub struct SyncStatus {
pub bytes_synced: i64,
}
// ===== File Versioning Structures =====
#[derive(Debug, Deserialize)]
pub struct VersionsQuery {
pub bucket: Option<String>,
pub path: String,
}
#[derive(Debug, Serialize)]
pub struct FileVersion {
pub version_id: String,
pub modified: String,
pub size: i64,
pub is_latest: bool,
pub etag: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct VersionsResponse {
pub path: String,
pub versions: Vec<FileVersion>,
}
#[derive(Debug, Deserialize)]
pub struct RestoreRequest {
pub bucket: Option<String>,
pub path: String,
pub version_id: String,
}
#[derive(Debug, Serialize)]
pub struct RestoreResponse {
pub success: bool,
pub message: String,
pub restored_version: String,
pub new_version_id: Option<String>,
}
// ===== API Configuration =====
/// Configure drive API routes
@ -182,6 +222,9 @@ pub fn configure() -> Router<Arc<AppState>> {
.route("/files/sync/status", get(sync_status))
.route("/files/sync/start", post(start_sync))
.route("/files/sync/stop", post(stop_sync))
// File versioning
.route("/files/versions", get(list_versions))
.route("/files/restore", post(restore_version))
// Document processing
.route("/docs/merge", post(document_processing::merge_documents))
.route("/docs/convert", post(document_processing::convert_document))
@ -914,3 +957,122 @@ pub async fn stop_sync(
message: Some("Sync stopped".to_string()),
}))
}
// ===== File Versioning API =====
/// GET /files/versions - List all versions of a file
///
/// SeaweedFS/S3 supports object versioning. This endpoint lists all versions
/// of a specific file, allowing users to restore previous versions.
///
/// Query parameters:
/// - path: The file path to get versions for
/// - bucket: Optional bucket name (defaults to bot's bucket)
pub async fn list_versions(
State(state): State<Arc<AppState>>,
Query(params): Query<VersionsQuery>,
) -> Result<Json<VersionsResponse>, (StatusCode, Json<serde_json::Value>)> {
let bucket = params.bucket.unwrap_or_else(|| "default".to_string());
let path = params.path;
// Get S3 client from state
let s3_client = state.s3_client.as_ref().ok_or_else(|| {
(
StatusCode::SERVICE_UNAVAILABLE,
Json(serde_json::json!({ "error": "S3 storage not configured" })),
)
})?;
// List object versions using S3 API
let versions_result = s3_client
.list_object_versions()
.bucket(&bucket)
.prefix(&path)
.send()
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({ "error": format!("Failed to list versions: {}", e) })),
)
})?;
let mut versions: Vec<FileVersion> = Vec::new();
// Process version list
for version in versions_result.versions() {
if version.key().unwrap_or_default() == path {
versions.push(FileVersion {
version_id: version.version_id().unwrap_or("null").to_string(),
modified: version
.last_modified()
.map(|t| t.to_string())
.unwrap_or_else(|| chrono::Utc::now().to_rfc3339()),
size: version.size().unwrap_or(0),
is_latest: version.is_latest().unwrap_or(false),
etag: version.e_tag().map(|s| s.to_string()),
});
}
}
// Sort by modified date, newest first
versions.sort_by(|a, b| b.modified.cmp(&a.modified));
Ok(Json(VersionsResponse {
path: path.clone(),
versions,
}))
}
/// POST /files/restore - Restore a file to a specific version
///
/// Restores a file to a previous version by copying the old version
/// to become the new current version. The previous versions are preserved.
///
/// Request body:
/// - path: The file path to restore
/// - version_id: The version ID to restore to
/// - bucket: Optional bucket name (defaults to bot's bucket)
pub async fn restore_version(
State(state): State<Arc<AppState>>,
Json(payload): Json<RestoreRequest>,
) -> Result<Json<RestoreResponse>, (StatusCode, Json<serde_json::Value>)> {
let bucket = payload.bucket.unwrap_or_else(|| "default".to_string());
let path = payload.path;
let version_id = payload.version_id;
// Get S3 client from state
let s3_client = state.s3_client.as_ref().ok_or_else(|| {
(
StatusCode::SERVICE_UNAVAILABLE,
Json(serde_json::json!({ "error": "S3 storage not configured" })),
)
})?;
// Copy the specific version to itself, making it the latest version
// S3 copy with version-id copies that version as a new object
let copy_source = format!("{}/{}?versionId={}", bucket, path, version_id);
let copy_result = s3_client
.copy_object()
.bucket(&bucket)
.key(&path)
.copy_source(&copy_source)
.send()
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({ "error": format!("Failed to restore version: {}", e) })),
)
})?;
let new_version_id = copy_result.version_id().map(|s| s.to_string());
Ok(Json(RestoreResponse {
success: true,
message: format!("Successfully restored {} to version {}", path, version_id),
restored_version: version_id,
new_version_id,
}))
}

527
templates/drive.html Normal file
View file

@ -0,0 +1,527 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Drive - General Bots</title>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script src="https://unpkg.com/htmx.org@1.9.10/dist/ext/ws.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>
[data-loading] { display: none; }
.htmx-request [data-loading] { display: block; }
.htmx-request [data-content] { opacity: 0.5; }
.file-item:hover { background-color: var(--hover-bg, #f3f4f6); }
.dark .file-item:hover { --hover-bg: #374151; }
.selected { background-color: #dbeafe !important; }
.dark .selected { background-color: #1e3a5a !important; }
.drop-target { border: 2px dashed #3b82f6 !important; background-color: #eff6ff !important; }
.context-menu { position: fixed; z-index: 1000; }
</style>
</head>
<body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">
<!-- Top Navigation -->
<nav class="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 py-3">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<a href="/" class="flex items-center gap-2">
<svg class="w-8 h-8 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14l-5-5 1.41-1.41L12 14.17l4.59-4.58L18 11l-6 6z"/>
</svg>
<span class="text-xl font-semibold">Drive</span>
</a>
<!-- Search -->
<div class="relative ml-4">
<input type="search"
name="query"
placeholder="Search files..."
class="w-80 px-4 py-2 pl-10 rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
hx-get="/api/files/search"
hx-trigger="input changed delay:300ms"
hx-target="#file-list"
hx-indicator="#search-spinner">
<svg class="w-5 h-5 absolute left-3 top-2.5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<div id="search-spinner" class="htmx-indicator absolute right-3 top-2.5">
<svg class="animate-spin h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
</div>
</div>
<div class="flex items-center gap-3">
<!-- View Toggle -->
<div class="flex rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden">
<button onclick="setView('grid')" id="view-grid" class="px-3 py-2 bg-blue-600 text-white">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M4 4h4v4H4V4zm6 0h4v4h-4V4zm6 0h4v4h-4V4zM4 10h4v4H4v-4zm6 0h4v4h-4v-4zm6 0h4v4h-4v-4zM4 16h4v4H4v-4zm6 0h4v4h-4v-4zm6 0h4v4h-4v-4z"/>
</svg>
</button>
<button onclick="setView('list')" id="view-list" class="px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"/>
</svg>
</button>
</div>
<!-- User Menu -->
<div class="relative">
<button class="flex items-center gap-2 p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">
<div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white font-medium">
{{ user_name | first | upper }}
</div>
</button>
</div>
</div>
</div>
</nav>
<div class="flex h-[calc(100vh-65px)]">
<!-- Sidebar -->
<aside class="w-64 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 p-4 flex flex-col">
<!-- New Button -->
<button onclick="showUploadModal()"
class="w-full mb-4 px-4 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium flex items-center justify-center gap-2 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
New
</button>
<!-- Navigation -->
<nav class="flex-1 space-y-1">
<a href="#" hx-get="/api/files/list" hx-target="#file-list" hx-vals='{"path": ""}'
class="flex items-center gap-3 px-3 py-2 rounded-lg bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-medium">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>
</svg>
My Drive
</a>
<a href="#" hx-get="/api/files/shared" hx-target="#file-list"
class="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
</svg>
Shared with me
</a>
<a href="#" hx-get="/api/files/recent" hx-target="#file-list"
class="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/>
</svg>
Recent
</a>
<a href="#" hx-get="/api/files/favorite" hx-target="#file-list"
class="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/>
</svg>
Starred
</a>
<a href="#" class="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
</svg>
Trash
</a>
</nav>
<!-- Storage Info -->
<div class="mt-auto pt-4 border-t border-gray-200 dark:border-gray-700"
hx-get="/api/files/quota" hx-trigger="load" hx-swap="innerHTML">
<div class="text-sm text-gray-500 dark:text-gray-400">
<div class="flex justify-between mb-1">
<span>Storage</span>
<span>Loading...</span>
</div>
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div class="bg-blue-600 h-2 rounded-full" style="width: 0%"></div>
</div>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 flex flex-col overflow-hidden">
<!-- Breadcrumb & Actions -->
<div class="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-6 py-3 flex items-center justify-between">
<div id="breadcrumb" class="flex items-center gap-2 text-sm">
<a href="#" hx-get="/api/files/list" hx-vals='{"path": ""}' hx-target="#file-list"
class="text-gray-600 dark:text-gray-400 hover:text-blue-600">My Drive</a>
</div>
<div class="flex items-center gap-2">
<!-- Sort -->
<select class="px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm"
hx-get="/api/files/list" hx-target="#file-list" hx-include="[name='path']">
<option value="name">Name</option>
<option value="modified">Modified</option>
<option value="size">Size</option>
<option value="type">Type</option>
</select>
<!-- Actions for selected -->
<div id="selection-actions" class="hidden flex items-center gap-2">
<button onclick="downloadSelected()" class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg" title="Download">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
</button>
<button onclick="shareSelected()" class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg" title="Share">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"/>
</svg>
</button>
<button onclick="deleteSelected()" class="p-2 hover:bg-red-100 dark:hover:bg-red-900/30 text-red-600 rounded-lg" title="Delete">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</div>
</div>
</div>
<!-- File List -->
<div class="flex-1 overflow-auto p-6"
id="file-container"
ondrop="handleDrop(event)"
ondragover="handleDragOver(event)"
ondragleave="handleDragLeave(event)">
<!-- Loading indicator -->
<div id="loading-indicator" class="hidden flex items-center justify-center h-full">
<svg class="animate-spin h-8 w-8 text-blue-600" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<!-- File list populated via HTMX -->
<div id="file-list" class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4"
hx-get="/api/files/list"
hx-trigger="load"
hx-indicator="#loading-indicator">
</div>
</div>
</main>
<!-- Details Panel (hidden by default) -->
<aside id="details-panel" class="hidden w-80 bg-white dark:bg-gray-800 border-l border-gray-200 dark:border-gray-700 p-4 overflow-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold">Details</h3>
<button onclick="hideDetailsPanel()" class="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div id="details-content">
<!-- Populated dynamically -->
</div>
</aside>
</div>
<!-- Upload Modal -->
<div id="upload-modal" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-xl max-w-md w-full mx-4 p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">Upload Files</h3>
<button onclick="hideUploadModal()" class="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<form hx-post="/api/files/upload" hx-encoding="multipart/form-data" hx-target="#file-list" hx-on::after-request="hideUploadModal()">
<input type="hidden" name="path" id="upload-path" value="">
<div class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-xl p-8 text-center hover:border-blue-500 transition-colors">
<input type="file" name="file" id="file-input" class="hidden" multiple onchange="updateFileLabel(this)">
<label for="file-input" class="cursor-pointer">
<svg class="w-12 h-12 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
</svg>
<p class="text-gray-600 dark:text-gray-400">
<span class="text-blue-600 font-medium">Click to upload</span> or drag and drop
</p>
<p id="file-label" class="text-sm text-gray-500 mt-1">Any file up to 100MB</p>
</label>
</div>
<div class="mt-4 flex gap-3">
<button type="button" onclick="hideUploadModal()" class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700">
Cancel
</button>
<button type="submit" class="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
Upload
</button>
</div>
</form>
</div>
</div>
<!-- New Folder Modal -->
<div id="folder-modal" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-xl max-w-md w-full mx-4 p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">New Folder</h3>
<button onclick="hideFolderModal()" class="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<form hx-post="/api/files/create-folder" hx-target="#file-list" hx-on::after-request="hideFolderModal()">
<input type="hidden" name="path" id="folder-path" value="">
<input type="text" name="name" placeholder="Folder name" required
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<div class="mt-4 flex gap-3">
<button type="button" onclick="hideFolderModal()" class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700">
Cancel
</button>
<button type="submit" class="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
Create
</button>
</div>
</form>
</div>
</div>
<!-- Versions Modal -->
<div id="versions-modal" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-xl max-w-lg w-full mx-4 p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">Version History</h3>
<button onclick="hideVersionsModal()" class="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div id="versions-content" class="max-h-96 overflow-auto">
<!-- Populated via HTMX -->
</div>
</div>
</div>
<!-- Context Menu -->
<div id="context-menu" class="hidden context-menu bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 py-1 min-w-48">
<button onclick="openFile()" class="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-3">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
Open
</button>
<button onclick="downloadFile()" class="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-3">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
Download
</button>
<button onclick="shareFile()" class="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-3">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"/>
</svg>
Share
</button>
<hr class="my-1 border-gray-200 dark:border-gray-700">
<button onclick="showVersions()" class="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-3">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Version History
</button>
<button onclick="renameFile()" class="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-3">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
Rename
</button>
<button onclick="copyFile()" class="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-3">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
</svg>
Copy
</button>
<button onclick="moveFile()" class="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-3">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"/>
</svg>
Move
</button>
<hr class="my-1 border-gray-200 dark:border-gray-700">
<button onclick="deleteFile()" class="w-full px-4 py-2 text-left hover:bg-red-50 dark:hover:bg-red-900/30 text-red-600 flex items-center gap-3">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
Delete
</button>
</div>
<script>
let currentPath = '';
let selectedFiles = [];
let contextTarget = null;
// View toggle
function setView(view) {
const fileList = document.getElementById('file-list');
const gridBtn = document.getElementById('view-grid');
const listBtn = document.getElementById('view-list');
if (view === 'grid') {
fileList.className = 'grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4';
gridBtn.className = 'px-3 py-2 bg-blue-600 text-white';
listBtn.className = 'px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700';
} else {
fileList.className = 'space-y-1';
gridBtn.className = 'px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700';
listBtn.className = 'px-3 py-2 bg-blue-600 text-white';
}
localStorage.setItem('driveView', view);
}
// Modals
function showUploadModal() {
document.getElementById('upload-path').value = currentPath;
document.getElementById('upload-modal').classList.remove('hidden');
}
function hideUploadModal() {
document.getElementById('upload-modal').classList.add('hidden');
}
function showFolderModal() {
document.getElementById('folder-path').value = currentPath;
document.getElementById('folder-modal').classList.remove('hidden');
}
function hideFolderModal() {
document.getElementById('folder-modal').classList.add('hidden');
}
function showVersionsModal(path) {
document.getElementById('versions-modal').classList.remove('hidden');
htmx.ajax('GET', `/api/files/versions?path=${encodeURIComponent(path)}`, '#versions-content');
}
function hideVersionsModal() {
document.getElementById('versions-modal').classList.add('hidden');
}
// File label update
function updateFileLabel(input) {
const label = document.getElementById('file-label');
if (input.files.length > 0) {
label.textContent = `${input.files.length} file(s) selected`;
}
}
// Context menu
function showContextMenu(event, element) {
event.preventDefault();
contextTarget = element;
const menu = document.getElementById('context-menu');
menu.style.left = event.pageX + 'px';
menu.style.top = event.pageY + 'px';
menu.classList.remove('hidden');
}
document.addEventListener('click', () => {
document.getElementById('context-menu').classList.add('hidden');
});
// File operations
function showVersions() {
if (contextTarget) {
showVersionsModal(contextTarget.dataset.path);
}
}
function restoreVersion(path, versionId) {
htmx.ajax('POST', '/api/files/restore', {
values: { path: path, version_id: versionId },
target: '#file-list'
}).then(() => {
hideVersionsModal();
});
}
// Drag and drop
function handleDragOver(event) {
event.preventDefault();
event.currentTarget.classList.add('drop-target');
}
function handleDragLeave(event) {
event.currentTarget.classList.remove('drop-target');
}
function handleDrop(event) {
event.preventDefault();
event.currentTarget.classList.remove('drop-target');
const files = event.dataTransfer.files;
if (files.length > 0) {
const formData = new FormData();
formData.append('path', currentPath);
for (let file of files) {
formData.append('file', file);
}
fetch('/api/files/upload', { method: 'POST', body: formData })
.then(() => htmx.trigger('#file-list', 'refresh'));
}
}
// Details panel
function showDetailsPanel(path) {
document.getElementById('details-panel').classList.remove('hidden');
htmx.ajax('GET', `/api/files/details?path=${encodeURIComponent(path)}`, '#details-content');
}
function hideDetailsPanel() {
document.getElementById('details-panel').classList.add('hidden');
}
// Selection handling
function toggleSelect(element) {
element.classList.toggle('selected');
const path = element.dataset.path;
const idx = selectedFiles.indexOf(path);
if (idx > -1) {
selectedFiles.splice(idx, 1);
} else {
selectedFiles.push(path);
}
document.getElementById('selection-actions').classList.toggle('hidden', selectedFiles.length === 0);
}
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Delete' && selectedFiles.length > 0) {
deleteSelected();
}
if (e.ctrlKey && e.key === 'a') {
e.preventDefault();
document.querySelectorAll('.file-item').forEach(el => {
el.classList.add('selected');
selectedFiles.push(el.dataset.path);
});
}
});
// Load saved view preference
document.addEventListener('DOMContentLoaded', () => {
const savedView = localStorage.getItem('driveView') || 'grid';
setView(savedView);
});
// HTMX events
document.body.addEventListener('htmx:afterSwap', (event) => {
if (event.detail.target.id === 'file-list') {
selectedFiles = [];
document.getElementById('selection-actions').classList.add('hidden');
}
});
</script>
</body>
</html>