feat: add HTTP server and refactor initialization

- Added HTTP server with CORS support and various endpoints
- Introduced http_tx/http_rx channels for HTTP server control
- Cleaned up build.rs by removing commented code
- Updated .gitignore to use *.rdb pattern instead of .rdb
- Simplified capabilities.json to empty object
- Improved UI initialization with better error handling
- Reorganized module imports in main.rs
- Added worker count configuration for HTTP server

The changes introduce a new HTTP server capability while cleaning up and improving existing code structure. The HTTP server includes authentication, session management, and websocket support.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-11-15 09:48:46 -03:00
parent 6e78618b2e
commit 4b185f00f9
94 changed files with 12361 additions and 536 deletions

2
.gitignore vendored
View file

@ -8,4 +8,4 @@ botserver-stack
*logfile*
*-log*
docs/book
.rdb
*.rdb

View file

@ -1,5 +1,3 @@
// build.rs
fn main() {
// Tauri build script to generate the context required by `tauri::generate_context!()`
tauri_build::build()
}

View file

@ -1,11 +0,0 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default"
]
}

View file

@ -1,253 +1,3 @@
# General Bots 6 (GB6) Platform
## Vision
GB6 is a billion-scale real-time communication platform integrating advanced bot capabilities, WebRTC multimedia, and enterprise-grade messaging, built with Rust for maximum performance and reliability and BASIC-WebAssembly VM.
## 🌟 Key Features
### Scale & Performance
- Billion+ active users support
- Sub-second message delivery
- 4K video streaming
- 99.99% uptime guarantee
- Zero message loss
- Petabyte-scale storage
### Core Services
- **API Service** (gb-server)
- Axum-based REST & WebSocket
- Multi-tenant request routing
- Authentication & Authorization
- File handling & streaming
- **Media Processing** (gb-media)
- WebRTC integration
- GStreamer transcoding
- Real-time track management
- Professional recording
- **Messaging** (gb-messaging)
- Kafka event processing
- RabbitMQ integration
- WebSocket communication
- Redis PubSub
- **Storage** (gb-storage)
- PostgreSQL with sharding
- Redis caching
- TiKV distributed storage
## 🏗 Architecture
### Multi-Tenant Core
- Organizations
- Instance management
- Resource quotas
- Usage analytics
### Communication Infrastructure
- WebRTC rooms
- Real-time messaging
- Media processing
- Video conferencing
## 🛠 Installation
### Prerequisites
- Rust 1.70+
- Kubernetes cluster
- PostgreSQL 13+
- Redis 6+
- Kafka 3.0+
- GStreamer
# Deploy platform
## Linux && Mac
```
sudo apt update
sudo apt install brave-browser-beta
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
git clone https://alm.pragmatismo.com.br/generalbots/gbserver
apt install -y build-essential \
pkg-config \
libssl-dev \
gcc-multilib \
g++-multilib \
clang \
lld \
binutils-dev \
libudev-dev \
libdbus-1-dev
```
## Build
```
### Build & Run
```bash
# Build all services
cargo build --workspace
# Run tests
cargo test --workspace
# Start API service
cargo run -p gb-server
```
## 📊 Monitoring & Operations
### Health Metrics
- System performance
- Resource utilization
- Error rates
- Latency tracking
### Scaling Operations
- Auto-scaling rules
- Shard management
- Load balancing
- Failover systems
## 🔒 Security
### Authentication & Authorization
- Multi-factor auth
- Role-based access
- Rate limiting
- End-to-end encryption
### Data Protection
- Tenant isolation
- Encryption at rest
- Secure communications
- Audit logging
## 🚀 Development
### Project Structure
```
general-bots/
├── gb-server/ # API service
├── gb-core/ # Core functionality
├── gb-media/ # Media processing
├── gb-messaging/ # Message brokers
├── gb-storage/ # Data storage
├── gb-utils/ # Utilities
└── migrations/ # DB migrations
```
### Configuration
```env
DATABASE_URL=postgresql://user:password@localhost:5432/gbdb
REDIS_URL=redis://localhost:6379
KAFKA_BROKERS=localhost:9092
RABBIT_URL=amqp://guest:guest@localhost:5672
```
## 🌍 Deployment
### Global Infrastructure
- Edge presence
- Regional optimization
- Content delivery
- Traffic management
### Disaster Recovery
- Automated backups
- Multi-region failover
- Data replication
- System redundancy
## 🤝 Contributing
1. Fork repository
2. Create feature branch
3. Implement changes
4. Add tests
5. Submit PR
## 📝 License
Licensed under terms specified in workspace configuration.
## 🆘 Support
### Issues
- Check existing issues
- Provide reproduction steps
- Include relevant logs
- Follow up on discussions
### Documentation
- API references
- Integration guides
- Deployment docs
- Best practices
## 🔮 Roadmap
### Short Term
- Enhanced media processing
- Additional messaging protocols
- Improved scalability
- Extended monitoring
### Long Term
- Advanced analytics
- Global expansion
- Enterprise features
| ✓ | Requirement | Component | Standard | Implementation Steps |
|---|-------------|-----------|-----------|---------------------|
| ✅ | TLS 1.3 Configuration | Nginx | All | Configure modern SSL parameters and ciphers in `/etc/nginx/conf.d/ssl.conf` |
| ✅ | Access Logging | Nginx | All | Enable detailed access logs with privacy fields in `/etc/nginx/nginx.conf` |
| ⬜ | Rate Limiting | Nginx | ISO 27001 | Implement rate limiting rules in location blocks |
| ⬜ | WAF Rules | Nginx | HIPAA | Install and configure ModSecurity with OWASP rules |
| ✅ | Reverse Proxy Security | Nginx | All | Configure security headers (X-Frame-Options, HSTS, CSP) |
| ✅ | MFA Implementation | Zitadel | All | Enable and enforce MFA for all administrative accounts |
| ✅ | RBAC Configuration | Zitadel | All | Set up role-based access control with least privilege |
| ✅ | Password Policy | Zitadel | All | Configure strong password requirements (length, complexity, history) |
| ✅ | OAuth2/OIDC Setup | Zitadel | ISO 27001 | Configure secure OAuth flows and token policies |
| ✅ | Audit Logging | Zitadel | All | Enable comprehensive audit logging for user activities |
| ✅ | Encryption at Rest | MinIO | All | Configure encrypted storage with key management |
| ✅ | Bucket Policies | MinIO | All | Implement strict bucket access policies |
| ✅ | Object Versioning | MinIO | HIPAA | Enable versioning for data recovery capability |
| ✅ | Access Logging | MinIO | All | Enable detailed access logging for object operations |
| ⬜ | Lifecycle Rules | MinIO | LGPD | Configure data retention and deletion policies |
| ✅ | DKIM/SPF/DMARC | Stalwart | All | Configure email authentication mechanisms |
| ✅ | Mail Encryption | Stalwart | All | Enable TLS for mail transport |
| ✅ | Content Filtering | Stalwart | All | Implement content scanning and filtering rules |
| ⬜ | Mail Archiving | Stalwart | HIPAA | Configure compliant email archiving |
| ✅ | Sieve Filtering | Stalwart | All | Implement security-focused mail filtering rules |
| ⬜ | System Hardening | Ubuntu | All | Apply CIS Ubuntu Linux benchmarks |
| ✅ | System Updates | Ubuntu | All | Configure unattended-upgrades for security patches |
| ⬜ | Audit Daemon | Ubuntu | All | Configure auditd for system event logging |
| ✅ | Firewall Rules | Ubuntu | All | Configure UFW with restrictive rules |
| ⬜ | Disk Encryption | Ubuntu | All | Implement LUKS encryption for system disks |
| ⬜ | SELinux/AppArmor | Ubuntu | All | Enable and configure mandatory access control |
| ✅ | Monitoring Setup | All | All | Install and configure Prometheus + Grafana |
| ✅ | Log Aggregation | All | All | Implement centralized logging (e.g., ELK Stack) |
| ⬜ | Backup System | All | All | Configure automated backup system with encryption |
| ✅ | Network Isolation | All | All | Implement proper network segmentation |
| ✅ | Data Classification | All | HIPAA/LGPD | Document data types and handling procedures |
| ✅ | Session Management | Zitadel | All | Configure secure session timeouts and invalidation |
| ✅ | Certificate Management | All | All | Implement automated certificate renewal with Let's Encrypt |
| ✅ | Vulnerability Scanning | All | ISO 27001 | Regular automated scanning with tools like OpenVAS |
| ✅ | Incident Response Plan | All | All | Document and test incident response procedures |
| ✅ | Disaster Recovery | All | HIPAA | Implement and test disaster recovery procedures |
## Documentation Requirements
1. **Security Policies**
- Information Security Policy
- Access Control Policy
@ -284,10 +34,9 @@ Licensed under terms specified in workspace configuration.
- Annual penetration testing
- Bi-annual disaster recovery testing
## Documentation Requirements
# API:
## **File & Document Management**
/files/upload
/files/download
@ -500,4 +249,107 @@ Licensed under terms specified in workspace configuration.
/monitoring/metrics
Built with ❤️ from Brazil, using Rust for maximum performance and reliability.
| ✓ | Requirement | Component | Standard | Implementation Steps |
|---|-------------|-----------|-----------|---------------------|
| ✅ | TLS 1.3 Configuration | Nginx | All | Configure modern SSL parameters and ciphers in `/etc/nginx/conf.d/ssl.conf` |
| ✅ | Access Logging | Nginx | All | Enable detailed access logs with privacy fields in `/etc/nginx/nginx.conf` |
| ⬜ | Rate Limiting | Nginx | ISO 27001 | Implement rate limiting rules in location blocks |
| ⬜ | WAF Rules | Nginx | HIPAA | Install and configure ModSecurity with OWASP rules |
| ✅ | Reverse Proxy Security | Nginx | All | Configure security headers (X-Frame-Options, HSTS, CSP) |
| ✅ | MFA Implementation | Zitadel | All | Enable and enforce MFA for all administrative accounts |
| ✅ | RBAC Configuration | Zitadel | All | Set up role-based access control with least privilege |
| ✅ | Password Policy | Zitadel | All | Configure strong password requirements (length, complexity, history) |
| ✅ | OAuth2/OIDC Setup | Zitadel | ISO 27001 | Configure secure OAuth flows and token policies |
| ✅ | Audit Logging | Zitadel | All | Enable comprehensive audit logging for user activities |
| ✅ | Encryption at Rest | MinIO | All | Configure encrypted storage with key management |
| ✅ | Bucket Policies | MinIO | All | Implement strict bucket access policies |
| ✅ | Object Versioning | MinIO | HIPAA | Enable versioning for data recovery capability |
| ✅ | Access Logging | MinIO | All | Enable detailed access logging for object operations |
| ⬜ | Lifecycle Rules | MinIO | LGPD | Configure data retention and deletion policies |
| ✅ | DKIM/SPF/DMARC | Stalwart | All | Configure email authentication mechanisms |
| ✅ | Mail Encryption | Stalwart | All | Enable TLS for mail transport |
| ✅ | Content Filtering | Stalwart | All | Implement content scanning and filtering rules |
| ⬜ | Mail Archiving | Stalwart | HIPAA | Configure compliant email archiving |
| ✅ | Sieve Filtering | Stalwart | All | Implement security-focused mail filtering rules |
| ⬜ | System Hardening | Ubuntu | All | Apply CIS Ubuntu Linux benchmarks |
| ✅ | System Updates | Ubuntu | All | Configure unattended-upgrades for security patches |
| ⬜ | Audit Daemon | Ubuntu | All | Configure auditd for system event logging |
| ✅ | Firewall Rules | Ubuntu | All | Configure UFW with restrictive rules |
| ⬜ | Disk Encryption | Ubuntu | All | Implement LUKS encryption for system disks |
| ⬜ | SELinux/AppArmor | Ubuntu | All | Enable and configure mandatory access control |
| ✅ | Monitoring Setup | All | All | Install and configure Prometheus + Grafana |
| ✅ | Log Aggregation | All | All | Implement centralized logging (e.g., ELK Stack) |
| ⬜ | Backup System | All | All | Configure automated backup system with encryption |
| ✅ | Network Isolation | All | All | Implement proper network segmentation |
| ✅ | Data Classification | All | HIPAA/LGPD | Document data types and handling procedures |
| ✅ | Session Management | Zitadel | All | Configure secure session timeouts and invalidation |
| ✅ | Certificate Management | All | All | Implement automated certificate renewal with Let's Encrypt |
| ✅ | Vulnerability Scanning | All | ISO 27001 | Regular automated scanning with tools like OpenVAS |
| ✅ | Incident Response Plan | All | All | Document and test incident response procedures |
| ✅ | Disaster Recovery | All | HIPAA | Implement and test disaster recovery procedures |
## Vision
GB6 is a billion-scale real-time communication platform integrating advanced bot capabilities, WebRTC multimedia, and enterprise-grade messaging, built with Rust for maximum performance and reliability and BASIC-WebAssembly VM.
## 🌟 Key Features
### Scale & Performance
- Billion+ active users support
- Sub-second message delivery
- 4K video streaming
- 99.99% uptime guarantee
- Zero message loss
- Petabyte-scale storage
## 📊 Monitoring & Operations
### Health Metrics
- System performance
- Resource utilization
- Error rates
- Latency tracking
### Scaling Operations
- Auto-scaling rules
- Shard management
- Load balancing
- Failover systems
## 🔒 Security
### Authentication & Authorization
- Multi-factor auth
- Role-based access
- Rate limiting
- End-to-end encryption
### Data Protection
- Tenant isolation
- Encryption at rest
- Secure communications
- Audit logging
### Global Infrastructure
- Edge presence
- Regional optimization
- Content delivery
- Traffic management
### Disaster Recovery
- Automated backups
- Multi-region failover
- Data replication
- System redundancy
## 🤝 Contributing
1. Fork repository
2. Create feature branch
3. Implement changes
4. Add tests
5. Submit PR

View file

@ -1 +1 @@
{"default":{"identifier":"default","description":"enables the default permissions","local":true,"windows":["main"],"permissions":["core:default"]}}
{}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

BIN
public/images/badge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
public/images/bg.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

BIN
public/images/emoji1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
public/images/emoji2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
public/images/emoji3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
public/images/emoji4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
public/images/emoji5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
public/images/emoji6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
public/images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
public/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,5 @@
<svg width="21" height="22" viewBox="0 0 21 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.3795 1.30176H5.6812C3.00308 1.30176 0.832031 3.47281 0.832031 6.15093V15.8493C0.832031 18.5274 3.00308 20.6984 5.6812 20.6984H15.3795C18.0577 20.6984 20.2287 18.5274 20.2287 15.8493V6.15093C20.2287 3.47281 18.0577 1.30176 15.3795 1.30176Z" stroke="#7657AA" stroke-width="1.49205" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.4101 10.3892C14.5298 11.1963 14.3919 12.0206 14.0161 12.7449C13.6403 13.4692 13.0457 14.0565 12.3168 14.4234C11.588 14.7902 10.762 14.9179 9.95639 14.7883C9.15079 14.6586 8.40657 14.2783 7.8296 13.7013C7.25262 13.1243 6.87227 12.3801 6.74263 11.5745C6.613 10.7689 6.74069 9.94294 7.10754 9.21409C7.47439 8.48524 8.06172 7.89062 8.78599 7.51481C9.51027 7.139 10.3346 7.00113 11.1417 7.12082C11.9651 7.24291 12.7273 7.62656 13.3158 8.21509C13.9043 8.80363 14.288 9.56585 14.4101 10.3892Z" stroke="#7657AA" stroke-width="1.49205" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.8643 5.66602H15.8747" stroke="#7657AA" stroke-width="2.23808" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
public/images/logo-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
public/images/mercury.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

BIN
public/images/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

4751
public/output.css Normal file

File diff suppressed because it is too large Load diff

46
public/sounds/click.mp3 Normal file
View file

@ -0,0 +1,46 @@
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

46
public/sounds/error.mp3 Normal file
View file

@ -0,0 +1,46 @@
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

46
public/sounds/hover.mp3 Normal file
View file

@ -0,0 +1,46 @@
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

13
public/sounds/manifest.ts Normal file
View file

@ -0,0 +1,13 @@
export const soundAssets = {
send: '/assets/sounds/send.mp3',
receive: '/assets/sounds/receive.mp3',
typing: '/assets/sounds/typing.mp3',
notification: '/assets/sounds/notification.mp3',
click: '/assets/sounds/click.mp3',
hover: '/assets/sounds/hover.mp3',
success: '/assets/sounds/success.mp3',
error: '/assets/sounds/error.mp3'
} as const;
// Type for sound names
export type SoundName = keyof typeof soundAssets;

View file

@ -0,0 +1,46 @@
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

46
public/sounds/receive.mp3 Normal file
View file

@ -0,0 +1,46 @@
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

46
public/sounds/send.mp3 Normal file
View file

@ -0,0 +1,46 @@
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

46
public/sounds/success.mp3 Normal file
View file

@ -0,0 +1,46 @@
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

46
public/sounds/typing.mp3 Normal file
View file

@ -0,0 +1,46 @@
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

2801
public/styles/output.css Normal file

File diff suppressed because it is too large Load diff

66
public/themes/3dbevel.css Normal file
View file

@ -0,0 +1,66 @@
body, .card, .popover, .input, .button, .menu, .dialog {
font-family: 'IBM Plex Mono', 'Courier New', monospace !important;
background: #c0c0c0 !important;
color: #000 !important;
border-radius: 0 !important;
box-shadow: none !important;
}
.card, .popover, .menu, .dialog {
border: 2px solid #fff !important;
border-bottom: 2px solid #404040 !important;
border-right: 2px solid #404040 !important;
padding: 8px !important;
background: #e0e0e0 !important;
}
.button, button, input[type="button"], input[type="submit"] {
background: #e0e0e0 !important;
color: #000 !important;
border: 2px solid #fff !important;
border-bottom: 2px solid #404040 !important;
border-right: 2px solid #404040 !important;
padding: 4px 12px !important;
font-weight: bold !important;
box-shadow: none !important;
outline: none !important;
}
input, textarea, select {
background: #fff !important;
color: #000 !important;
border: 2px solid #fff !important;
border-bottom: 2px solid #404040 !important;
border-right: 2px solid #404040 !important;
font-family: inherit !important;
box-shadow: none !important;
}
.menu {
background: #d0d0d0 !important;
border: 2px solid #fff !important;
border-bottom: 2px solid #404040 !important;
border-right: 2px solid #404040 !important;
}
::-webkit-scrollbar {
width: 16px !important;
background: #c0c0c0 !important;
}
::-webkit-scrollbar-thumb {
background: #404040 !important;
border: 2px solid #fff !important;
border-bottom: 2px solid #404040 !important;
border-right: 2px solid #404040 !important;
}
a {
color: #0000aa !important;
text-decoration: underline !important;
}
hr {
border: none !important;
border-top: 2px solid #404040 !important;
margin: 8px 0 !important;
}

View file

@ -0,0 +1,28 @@
:root {
/* ArcadeFlash Theme */
--background: 0 0% 5%;
--foreground: 0 0% 98%;
--card: 0 0% 8%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 5%;
--popover-foreground: 0 0% 98%;
--primary: 120 100% 50%;
--primary-foreground: 0 0% 5%;
--secondary: 0 0% 15%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 10%;
--muted-foreground: 0 0% 60%;
--accent: 240 100% 50%;
--accent-foreground: 0 0% 98%;
--destructive: 0 100% 50%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 15%;
--input: 0 0% 15%;
--ring: 120 100% 50%;
--radius: 0.5rem;
--chart-1: 120 100% 50%;
--chart-2: 240 100% 50%;
--chart-3: 60 100% 50%;
--chart-4: 0 100% 50%;
--chart-5: 300 100% 50%;
}

View file

@ -0,0 +1,28 @@
:root {
/* CyberPunk Theme */
--background: 240 30% 5%;
--foreground: 60 100% 80%;
--card: 240 30% 8%;
--card-foreground: 60 100% 80%;
--popover: 240 30% 5%;
--popover-foreground: 60 100% 80%;
--primary: 330 100% 60%;
--primary-foreground: 240 30% 5%;
--secondary: 240 30% 15%;
--secondary-foreground: 60 100% 80%;
--muted: 240 30% 10%;
--muted-foreground: 60 100% 60%;
--accent: 180 100% 60%;
--accent-foreground: 240 30% 5%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 240 30% 15%;
--input: 240 30% 15%;
--ring: 330 100% 60%;
--radius: 0.5rem;
--chart-1: 330 100% 60%;
--chart-2: 180 100% 60%;
--chart-3: 60 100% 60%;
--chart-4: 0 100% 60%;
--chart-5: 270 100% 60%;
}

View file

@ -0,0 +1,28 @@
:root {
/* DiscoFever Theme */
--background: 270 20% 10%;
--foreground: 0 0% 98%;
--card: 270 20% 15%;
--card-foreground: 0 0% 98%;
--popover: 270 20% 10%;
--popover-foreground: 0 0% 98%;
--primary: 330 100% 60%;
--primary-foreground: 0 0% 98%;
--secondary: 270 20% 20%;
--secondary-foreground: 0 0% 98%;
--muted: 270 20% 25%;
--muted-foreground: 270 10% 60%;
--accent: 60 100% 60%;
--accent-foreground: 270 20% 10%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 270 20% 20%;
--input: 270 20% 20%;
--ring: 330 100% 60%;
--radius: 0.5rem;
--chart-1: 330 100% 60%;
--chart-2: 60 100% 60%;
--chart-3: 120 100% 60%;
--chart-4: 240 100% 60%;
--chart-5: 0 100% 60%;
}

View file

@ -0,0 +1,28 @@
:root {
/* GrungeEra Theme */
--background: 30 10% 10%;
--foreground: 30 30% 80%;
--card: 30 10% 15%;
--card-foreground: 30 30% 80%;
--popover: 30 10% 10%;
--popover-foreground: 30 30% 80%;
--primary: 10 70% 50%;
--primary-foreground: 30 30% 80%;
--secondary: 30 10% 20%;
--secondary-foreground: 30 30% 80%;
--muted: 30 10% 25%;
--muted-foreground: 30 30% 60%;
--accent: 200 70% 50%;
--accent-foreground: 30 30% 80%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 30 10% 20%;
--input: 30 10% 20%;
--ring: 10 70% 50%;
--radius: 0.5rem;
--chart-1: 10 70% 50%;
--chart-2: 200 70% 50%;
--chart-3: 90 70% 50%;
--chart-4: 300 70% 50%;
--chart-5: 30 70% 50%;
}

28
public/themes/jazzage.css Normal file
View file

@ -0,0 +1,28 @@
:root {
/* JazzAge Theme */
--background: 30 20% 10%;
--foreground: 40 30% 85%;
--card: 30 20% 15%;
--card-foreground: 40 30% 85%;
--popover: 30 20% 10%;
--popover-foreground: 40 30% 85%;
--primary: 20 80% 50%;
--primary-foreground: 40 30% 85%;
--secondary: 30 20% 20%;
--secondary-foreground: 40 30% 85%;
--muted: 30 20% 25%;
--muted-foreground: 40 30% 60%;
--accent: 200 80% 50%;
--accent-foreground: 40 30% 85%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 30 20% 20%;
--input: 30 20% 20%;
--ring: 20 80% 50%;
--radius: 0.5rem;
--chart-1: 20 80% 50%;
--chart-2: 200 80% 50%;
--chart-3: 350 80% 50%;
--chart-4: 140 80% 50%;
--chart-5: 260 80% 50%;
}

View file

@ -0,0 +1,28 @@
:root {
/* MellowGold Theme */
--background: 45 30% 90%;
--foreground: 30 20% 20%;
--card: 45 30% 85%;
--card-foreground: 30 20% 20%;
--popover: 45 30% 90%;
--popover-foreground: 30 20% 20%;
--primary: 35 80% 50%;
--primary-foreground: 45 30% 90%;
--secondary: 45 30% 80%;
--secondary-foreground: 30 20% 20%;
--muted: 45 30% 75%;
--muted-foreground: 30 20% 40%;
--accent: 25 80% 50%;
--accent-foreground: 45 30% 90%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 45 30% 80%;
--input: 45 30% 80%;
--ring: 35 80% 50%;
--radius: 0.5rem;
--chart-1: 35 80% 50%;
--chart-2: 25 80% 50%;
--chart-3: 15 80% 50%;
--chart-4: 5 80% 50%;
--chart-5: 55 80% 50%;
}

View file

@ -0,0 +1,28 @@
:root {
/* MidCenturyMod Theme */
--background: 40 30% 95%;
--foreground: 30 20% 20%;
--card: 40 30% 90%;
--card-foreground: 30 20% 20%;
--popover: 40 30% 95%;
--popover-foreground: 30 20% 20%;
--primary: 180 60% 40%;
--primary-foreground: 40 30% 95%;
--secondary: 40 30% 85%;
--secondary-foreground: 30 20% 20%;
--muted: 40 30% 80%;
--muted-foreground: 30 20% 40%;
--accent: 350 60% 40%;
--accent-foreground: 40 30% 95%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 40 30% 85%;
--input: 40 30% 85%;
--ring: 180 60% 40%;
--radius: 0.5rem;
--chart-1: 180 60% 40%;
--chart-2: 350 60% 40%;
--chart-3: 40 60% 40%;
--chart-4: 220 60% 40%;
--chart-5: 300 60% 40%;
}

27
public/themes/orange.css Normal file
View file

@ -0,0 +1,27 @@
:root {
--background: 0 0% 100%; /* White */
--foreground: 0 0% 13%; /* #212121 - near black */
--card: 0 0% 98%; /* #faf9f8 - light gray */
--card-foreground: 0 0% 13%; /* #212121 */
--popover: 0 0% 100%; /* White */
--popover-foreground: 0 0% 13%; /* #212121 */
--primary: 24 90% 54%; /* #d83b01 - Office orange */
--primary-foreground: 0 0% 100%; /* White */
--secondary: 210 36% 96%; /* #f3f2f1 - light blue-gray */
--secondary-foreground: 0 0% 13%; /* #212121 */
--muted: 0 0% 90%; /* #e1dfdd - muted gray */
--muted-foreground: 0 0% 40%; /* #666666 */
--accent: 207 90% 54%; /* #0078d4 - Office blue */
--accent-foreground: 0 0% 100%; /* White */
--destructive: 0 85% 60%; /* #e81123 - Office red */
--destructive-foreground: 0 0% 100%; /* White */
--border: 0 0% 85%; /* #d2d0ce - light border */
--input: 0 0% 100%; /* White */
--ring: 207 90% 54%; /* #0078d4 */
--radius: 0.25rem; /* Slightly less rounded */
--chart-1: 24 90% 54%; /* Office orange */
--chart-2: 207 90% 54%; /* Office blue */
--chart-3: 120 60% 40%; /* Office green */
--chart-4: 340 82% 52%; /* Office magenta */
--chart-5: 44 100% 50%; /* Office yellow */
}

View file

@ -0,0 +1,28 @@
:root {
/* PolaroidMemories Theme */
--background: 50 30% 95%;
--foreground: 30 20% 20%;
--card: 50 30% 90%;
--card-foreground: 30 20% 20%;
--popover: 50 30% 95%;
--popover-foreground: 30 20% 20%;
--primary: 200 80% 50%;
--primary-foreground: 50 30% 95%;
--secondary: 50 30% 85%;
--secondary-foreground: 30 20% 20%;
--muted: 50 30% 80%;
--muted-foreground: 30 20% 40%;
--accent: 350 80% 50%;
--accent-foreground: 50 30% 95%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 50 30% 85%;
--input: 50 30% 85%;
--ring: 200 80% 50%;
--radius: 0.5rem;
--chart-1: 200 80% 50%;
--chart-2: 350 80% 50%;
--chart-3: 50 80% 50%;
--chart-4: 140 80% 50%;
--chart-5: 260 80% 50%;
}

View file

@ -0,0 +1,28 @@
:root {
/* RetroWave Theme */
--background: 240 21% 15%;
--foreground: 0 0% 98%;
--card: 240 21% 18%;
--card-foreground: 0 0% 98%;
--popover: 240 21% 15%;
--popover-foreground: 0 0% 98%;
--primary: 334 89% 62%;
--primary-foreground: 0 0% 100%;
--secondary: 240 21% 25%;
--secondary-foreground: 0 0% 98%;
--muted: 240 21% 20%;
--muted-foreground: 240 5% 65%;
--accent: 41 99% 60%;
--accent-foreground: 240 21% 15%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 240 21% 25%;
--input: 240 21% 25%;
--ring: 334 89% 62%;
--radius: 0.5rem;
--chart-1: 334 89% 62%;
--chart-2: 41 99% 60%;
--chart-3: 190 90% 50%;
--chart-4: 280 89% 65%;
--chart-5: 80 75% 55%;
}

View file

@ -0,0 +1,28 @@
:root {
/* SaturdayCartoons Theme */
--background: 220 50% 95%;
--foreground: 220 50% 20%;
--card: 220 50% 90%;
--card-foreground: 220 50% 20%;
--popover: 220 50% 95%;
--popover-foreground: 220 50% 20%;
--primary: 30 100% 55%;
--primary-foreground: 220 50% 95%;
--secondary: 220 50% 85%;
--secondary-foreground: 220 50% 20%;
--muted: 220 50% 80%;
--muted-foreground: 220 50% 40%;
--accent: 120 100% 55%;
--accent-foreground: 220 50% 95%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 220 50% 85%;
--input: 220 50% 85%;
--ring: 30 100% 55%;
--radius: 0.5rem;
--chart-1: 30 100% 55%;
--chart-2: 120 100% 55%;
--chart-3: 240 100% 55%;
--chart-4: 330 100% 55%;
--chart-5: 60 100% 55%;
}

View file

@ -0,0 +1,28 @@
:root {
/* SeasidePostcard Theme */
--background: 200 50% 95%;
--foreground: 200 50% 20%;
--card: 200 50% 90%;
--card-foreground: 200 50% 20%;
--popover: 200 50% 95%;
--popover-foreground: 200 50% 20%;
--primary: 30 100% 55%;
--primary-foreground: 200 50% 95%;
--secondary: 200 50% 85%;
--secondary-foreground: 200 50% 20%;
--muted: 200 50% 80%;
--muted-foreground: 200 50% 40%;
--accent: 350 100% 55%;
--accent-foreground: 200 50% 95%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 200 50% 85%;
--input: 200 50% 85%;
--ring: 30 100% 55%;
--radius: 0.5rem;
--chart-1: 30 100% 55%;
--chart-2: 350 100% 55%;
--chart-3: 200 100% 55%;
--chart-4: 140 100% 55%;
--chart-5: 260 100% 55%;
}

View file

@ -0,0 +1,28 @@
:root {
/* Typewriter Theme */
--background: 0 0% 95%;
--foreground: 0 0% 10%;
--card: 0 0% 90%;
--card-foreground: 0 0% 10%;
--popover: 0 0% 95%;
--popover-foreground: 0 0% 10%;
--primary: 0 0% 20%;
--primary-foreground: 0 0% 95%;
--secondary: 0 0% 85%;
--secondary-foreground: 0 0% 10%;
--muted: 0 0% 80%;
--muted-foreground: 0 0% 40%;
--accent: 0 0% 70%;
--accent-foreground: 0 0% 10%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 85%;
--input: 0 0% 85%;
--ring: 0 0% 20%;
--radius: 0.5rem;
--chart-1: 0 0% 20%;
--chart-2: 0 0% 40%;
--chart-3: 0 0% 60%;
--chart-4: 0 0% 30%;
--chart-5: 0 0% 50%;
}

View file

@ -0,0 +1,28 @@
:root {
/* VaporDream Theme */
--background: 260 20% 10%;
--foreground: 0 0% 98%;
--card: 260 20% 13%;
--card-foreground: 0 0% 98%;
--popover: 260 20% 10%;
--popover-foreground: 0 0% 98%;
--primary: 300 100% 70%;
--primary-foreground: 260 20% 10%;
--secondary: 260 20% 20%;
--secondary-foreground: 0 0% 98%;
--muted: 260 20% 15%;
--muted-foreground: 260 10% 60%;
--accent: 200 100% 70%;
--accent-foreground: 260 20% 10%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 260 20% 20%;
--input: 260 20% 20%;
--ring: 300 100% 70%;
--radius: 0.5rem;
--chart-1: 300 100% 70%;
--chart-2: 200 100% 70%;
--chart-3: 50 100% 60%;
--chart-4: 330 100% 70%;
--chart-5: 150 100% 60%;
}

71
public/themes/xeroxui.css Normal file
View file

@ -0,0 +1,71 @@
:root {
/* Windows 3.1 White & Blue Theme */
--background: 0 0% 100%; /* Pure white */
--foreground: 0 0% 0%; /* Black text */
--card: 0 0% 98%; /* Slightly off-white for cards */
--card-foreground: 0 0% 0%; /* Black text */
--popover: 0 0% 100%; /* White */
--popover-foreground: 0 0% 0%; /* Black */
--primary: 240 100% 27%; /* Windows blue */
--primary-foreground: 0 0% 100%; /* White text on blue */
--secondary: 0 0% 90%; /* Light gray for secondary */
--secondary-foreground: 0 0% 0%; /* Black text */
--muted: 0 0% 85%; /* Muted gray */
--muted-foreground: 240 10% 40%; /* Muted blue-gray */
--accent: 60 100% 50%; /* Classic yellow accent */
--accent-foreground: 240 100% 27%; /* Blue */
--destructive: 0 100% 50%; /* Red for destructive */
--destructive-foreground: 0 0% 100%; /* White */
--border: 240 100% 27%; /* Blue borders */
--input: 0 0% 100%; /* White input */
--ring: 240 100% 27%; /* Blue ring/focus */
--radius: 0.125rem; /* Small radius, almost square */
--chart-1: 240 100% 27%; /* Blue */
--chart-2: 0 0% 60%; /* Gray */
--chart-3: 60 100% 50%; /* Yellow */
--chart-4: 0 100% 50%; /* Red */
--chart-5: 120 100% 25%; /* Green */
--border-light: 0 0% 100%; /* White for top/left border */
--border-dark: 240 100% 20%; /* Dark blue for bottom/right border */
}
/* Windows 3.11 style border */
.win311-border {
border-top: 2px solid hsl(var(--border-light));
border-left: 2px solid hsl(var(--border-light));
border-bottom: 2px solid hsl(var(--border-dark));
border-right: 2px solid hsl(var(--border-dark));
background: hsl(var(--background));
}
/* Titles */
.win311-title {
color: hsl(var(--primary));
border-bottom: 2px solid hsl(var(--primary));
font-weight: bold;
padding: 0.25em 0.5em;
background: hsl(var(--background));
}
/* General text */
body, .filemanager, .filemanager * {
color: hsl(var(--foreground));
background: hsl(var(--background));
}
button, .win311-button {
font-family: inherit;
font-size: 1em;
padding: 0.25em 1.5em;
background: #c0c0c0; /* classic light gray */
color: #000;
border-top: 2px solid #fff; /* light bevel */
border-left: 2px solid #fff; /* light bevel */
border-bottom: 2px solid #808080;/* dark bevel */
border-right: 2px solid #808080; /* dark bevel */
border-radius: 0;
box-shadow: inset 1px 1px 0 #fff, inset -1px -1px 0 #808080 !important;
outline: none !important;
cursor: pointer !important;
transition: none !important;
}

228
public/themes/xtreegold.css Normal file
View file

@ -0,0 +1,228 @@
:root {
/* XTree Gold DOS File Manager Theme - Authentic 1980s Interface */
/* Core XTree Gold Palette - Exact Match */
--background: 240 100% 16%; /* Classic XTree blue background */
--foreground: 60 100% 88%; /* Bright yellow text */
/* Card Elements - File Panels */
--card: 240 100% 16%; /* Same blue as main background */
--card-foreground: 60 100% 88%; /* Bright yellow panel text */
/* Popover Elements - Context Menus */
--popover: 240 100% 12%; /* Slightly darker blue for menus */
--popover-foreground: 60 100% 90%; /* Bright yellow menu text */
/* Primary - XTree Gold Highlight (Cyan Selection) */
--primary: 180 100% 70%; /* Bright cyan for selections */
--primary-foreground: 240 100% 10%; /* Dark blue text on cyan */
/* Secondary - Directory Highlights */
--secondary: 180 100% 50%; /* Pure cyan for directories */
--secondary-foreground: 240 100% 10%; /* Dark blue on cyan */
/* Muted - Status Areas */
--muted: 240 100% 14%; /* Slightly darker blue */
--muted-foreground: 60 80% 75%; /* Dimmed yellow */
/* Accent - Function Keys & Highlights */
--accent: 60 100% 50%; /* Pure yellow for F-keys */
--accent-foreground: 240 100% 10%; /* Dark blue on yellow */
/* Destructive - Delete/Error */
--destructive: 0 100% 60%; /* Bright red for warnings */
--destructive-foreground: 60 90% 95%; /* Light yellow on red */
/* Interactive Elements */
--border: 60 100% 70%; /* Yellow border lines */
--input: 240 100% 14%; /* Dark blue input fields */
--ring: 180 100% 70%; /* Cyan focus ring */
/* Border Radius - Sharp DOS aesthetic */
--radius: 0rem; /* No rounding - pure DOS */
/* Chart Colors - Authentic DOS 16-color palette */
--chart-1: 180 100% 70%; /* Bright cyan */
--chart-2: 60 100% 50%; /* Yellow */
--chart-3: 120 100% 50%; /* Green */
--chart-4: 300 100% 50%; /* Magenta */
--chart-5: 0 100% 60%; /* Red */
/* Authentic XTree Gold Colors */
--xtree-blue: 240 100% 16%; /* Main background blue */
--xtree-yellow: 60 100% 88%; /* Text yellow */
--xtree-cyan: 180 100% 70%; /* Selection cyan */
--xtree-white: 0 0% 100%; /* Pure white */
--xtree-green: 120 100% 50%; /* DOS green */
--xtree-magenta: 300 100% 50%; /* DOS magenta */
--xtree-red: 0 100% 60%; /* DOS red */
/* File Type Colors - Authentic XTree */
--executable-color: 0 0% 100%; /* White for executables */
--directory-color: 180 100% 70%; /* Cyan for directories */
--archive-color: 300 100% 50%; /* Magenta for archives */
--text-color: 60 100% 88%; /* Yellow for text */
--system-color: 0 100% 60%; /* Red for system files */
/* Menu Bar Colors */
--menu-bar: 240 100% 8%; /* Dark blue menu bar */
--menu-text: 60 100% 88%; /* Yellow menu text */
--menu-highlight: 180 100% 50%; /* Cyan menu highlight */
}
/* Authentic XTree Gold Enhancement Classes */
.xtree-main-panel {
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
font-family: 'Perfect DOS VGA 437', 'Courier New', monospace;
font-size: 16px;
line-height: 1;
border: none;
}
.xtree-menu-bar {
background: hsl(var(--menu-bar));
color: hsl(var(--menu-text));
padding: 0;
height: 20px;
display: flex;
align-items: center;
font-weight: normal;
}
.xtree-menu-item {
padding: 0 8px;
color: hsl(var(--xtree-yellow));
background: transparent;
}
.xtree-menu-item:hover,
.xtree-menu-item.active {
background: hsl(var(--xtree-cyan));
color: hsl(240 100% 10%);
}
.xtree-dual-pane {
display: flex;
height: calc(100vh - 60px);
}
.xtree-left-pane,
.xtree-right-pane {
flex: 1;
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
padding: 0;
margin: 0;
}
.xtree-directory-tree {
color: hsl(var(--directory-color));
background: hsl(var(--xtree-blue));
padding: 4px;
}
.xtree-file-list {
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
font-family: 'Perfect DOS VGA 437', 'Courier New', monospace;
font-size: 16px;
line-height: 20px;
padding: 4px;
}
.xtree-file-selected {
background: hsl(var(--xtree-cyan));
color: hsl(240 100% 10%);
}
.xtree-directory {
color: hsl(var(--directory-color));
}
.xtree-executable {
color: hsl(var(--executable-color));
}
.xtree-archive {
color: hsl(var(--archive-color));
}
.xtree-text-file {
color: hsl(var(--text-color));
}
.xtree-system-file {
color: hsl(var(--system-color));
}
.xtree-status-line {
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
height: 20px;
padding: 0 8px;
display: flex;
align-items: center;
font-size: 16px;
}
.xtree-function-bar {
background: hsl(var(--menu-bar));
color: hsl(var(--xtree-yellow));
height: 20px;
display: flex;
padding: 0;
font-size: 14px;
}
.xtree-function-key {
padding: 0 4px;
color: hsl(var(--xtree-yellow));
border-right: 1px solid hsl(var(--xtree-yellow));
}
.xtree-function-key:last-child {
border-right: none;
}
.xtree-path-bar {
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
padding: 2px 8px;
border-bottom: 1px solid hsl(var(--xtree-yellow));
}
.xtree-disk-info {
background: hsl(var(--xtree-blue));
color: hsl(var(--xtree-yellow));
padding: 4px 8px;
text-align: right;
font-size: 14px;
}
/* Authentic DOS Box Drawing Characters */
.xtree-box-char {
font-family: 'Perfect DOS VGA 437', 'Courier New', monospace;
line-height: 1;
letter-spacing: 0;
}
/* Classic Text Mode Cursor */
.xtree-cursor {
background: hsl(var(--xtree-yellow));
color: hsl(var(--xtree-blue));
animation: blink 1s infinite;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
/* Authentic DOS Window Styling */
.xtree-window {
border: 2px outset hsl(var(--xtree-blue));
background: hsl(var(--xtree-blue));
box-shadow: none;
border-radius: 0;
}

28
public/themes/y2kglow.css Normal file
View file

@ -0,0 +1,28 @@
:root {
/* Y2KGlow Theme */
--background: 240 10% 10%;
--foreground: 0 0% 98%;
--card: 240 10% 13%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 10%;
--popover-foreground: 0 0% 98%;
--primary: 190 90% 50%;
--primary-foreground: 240 10% 10%;
--secondary: 240 10% 20%;
--secondary-foreground: 0 0% 98%;
--muted: 240 10% 15%;
--muted-foreground: 240 5% 60%;
--accent: 280 89% 65%;
--accent-foreground: 240 10% 10%;
--destructive: 0 85% 60%;
--destructive-foreground: 0 0% 98%;
--border: 240 10% 20%;
--input: 240 10% 20%;
--ring: 190 90% 50%;
--radius: 0.5rem;
--chart-1: 190 90% 50%;
--chart-2: 280 89% 65%;
--chart-3: 80 75% 55%;
--chart-4: 334 89% 62%;
--chart-5: 41 99% 60%;
}

View file

@ -10,7 +10,7 @@ mod auth;
mod automation;
mod basic;
mod bootstrap;
mod bot;
mod bot;
mod channels;
mod config;
mod context;
@ -75,6 +75,8 @@ async fn main() -> std::io::Result<()> {
dotenv().ok();
let (progress_tx, progress_rx) = tokio::sync::mpsc::unbounded_channel::<BootstrapProgress>();
let (state_tx, state_rx) = tokio::sync::mpsc::channel::<Arc<AppState>>(1);
let (http_tx, http_rx) = tokio::sync::oneshot::channel();
let ui_handle = if !no_ui {
let progress_rx = Arc::new(tokio::sync::Mutex::new(progress_rx));
let state_rx = Arc::new(tokio::sync::Mutex::new(state_rx));
@ -89,17 +91,18 @@ async fn main() -> std::io::Result<()> {
.expect("Failed to create UI runtime");
rt.block_on(async {
tokio::select! {
result = async {
let mut rx = state_rx.lock().await;
rx.recv().await
} => {
if let Some(app_state) = result {
ui.set_app_state(app_state);
}
}
_ = tokio::time::sleep(tokio::time::Duration::from_secs(300)) => {
eprintln!("UI initialization timeout");
}
result = async {
let mut rx = state_rx.lock().await;
rx.recv().await
} => {
if let Some(app_state) = result {
ui.set_app_state(app_state);
}
}
_ = http_rx => {}
_ = tokio::time::sleep(tokio::time::Duration::from_secs(300)) => {
eprintln!("UI initialization timeout");
}
}
});
if let Err(e) = ui.start_ui() {
@ -261,6 +264,65 @@ async fn main() -> std::io::Result<()> {
let worker_count = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let http_handle = {
let app_state = app_state.clone();
let config = config.clone();
let worker_count = worker_count;
std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().expect("Failed to create HTTP runtime");
rt.block_on(async {
let server = HttpServer::new(move || {
let cors = Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header()
.max_age(3600);
let app_state_clone = app_state.clone();
let mut app = App::new()
.wrap(cors)
.wrap(Logger::default())
.wrap(Logger::new("HTTP REQUEST: %a %{User-Agent}i"))
.app_data(web::Data::from(app_state_clone))
.service(auth_handler)
.service(create_session)
.service(get_session_history)
.service(get_sessions)
.service(index)
.service(start_session)
.service(upload_file)
.service(voice_start)
.service(voice_stop)
.service(websocket_handler)
.service(crate::bot::create_bot_handler)
.service(crate::bot::mount_bot_handler)
.service(crate::bot::handle_user_input_handler)
.service(crate::bot::get_user_sessions_handler)
.service(crate::bot::get_conversation_history_handler)
.service(crate::bot::send_warning_handler);
#[cfg(feature = "email")]
{
app = app
.service(get_latest_email_from)
.service(get_emails)
.service(list_emails)
.service(send_email)
.service(save_draft)
.service(save_click);
}
app = app.service(static_files);
app = app.service(bot_index);
app
})
.workers(worker_count)
.bind((config.server.host.clone(), config.server.port))?
.run();
let _ = http_tx.send(());
server.await
})
})
};
let bot_orchestrator = BotOrchestrator::new(app_state.clone());
tokio::spawn(async move {
if let Err(e) = bot_orchestrator.mount_all_bots().await {
@ -318,55 +380,6 @@ async fn main() -> std::io::Result<()> {
return Ok(());
}
// Normal server start continues here``
let server_result = HttpServer::new(move || {
let cors = Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header()
.max_age(3600);
let app_state_clone = app_state.clone();
let mut app = App::new()
.wrap(cors)
.wrap(Logger::default())
.wrap(Logger::new("HTTP REQUEST: %a %{User-Agent}i"))
.app_data(web::Data::from(app_state_clone))
.service(auth_handler)
.service(create_session)
.service(get_session_history)
.service(get_sessions)
.service(index)
.service(start_session)
.service(upload_file)
.service(voice_start)
.service(voice_stop)
.service(websocket_handler)
.service(crate::bot::create_bot_handler)
.service(crate::bot::mount_bot_handler)
.service(crate::bot::handle_user_input_handler)
.service(crate::bot::get_user_sessions_handler)
.service(crate::bot::get_conversation_history_handler)
.service(crate::bot::send_warning_handler);
#[cfg(feature = "email")]
{
app = app
.service(get_latest_email_from)
.service(get_emails)
.service(list_emails)
.service(send_email)
.service(save_draft)
.service(save_click);
}
app = app.service(static_files);
app = app.service(bot_index);
app
})
.workers(worker_count)
.bind((config.server.host.clone(), config.server.port))?
.run()
.await;
if let Some(handle) = ui_handle {
handle.join().ok();
}
server_result
http_handle.join().ok();
Ok(())
}

View file

@ -0,0 +1 @@
Prompts come from: https://github.com/0xeb/TheBigPromptLibrary

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,20 @@
use actix_web::{HttpRequest, HttpResponse, Result};
use log::{debug, error, warn};
use std::fs;
#[actix_web::get("/auth")]
async fn auth() -> Result<HttpResponse> {
match fs::read_to_string("web/desktop/auth/index.html") {
Ok(html) => Ok(HttpResponse::Ok().content_type("text/html").body(html)),
Err(e) => {
error!("Failed to load auth page: {}", e);
Ok(HttpResponse::InternalServerError().body("Failed to load auth page"))
}
}
}
#[actix_web::get("/")]
async fn index() -> Result<HttpResponse> {
match fs::read_to_string("web/html/index.html") {
match fs::read_to_string("web/desktop/auth/index.html") {
Ok(html) => Ok(HttpResponse::Ok().content_type("text/html").body(html)),
Err(e) => {
error!("Failed to load index page: {}", e);

View file

@ -4,7 +4,7 @@
"version": "6.0.8",
"identifier": "br.com.pragmatismo",
"build": {
"frontendDist": "./web/html"
"frontendDist": "./web/desktop"
},
"app": {
"security": {

74
web/desktop/auth/app.js Normal file
View file

@ -0,0 +1,74 @@
document.addEventListener('alpine:init', () => {
Alpine.data('auth', () => ({
email: '',
password: '',
rememberMe: false,
isLoading: false,
error: '',
async socialLogin(provider) {
this.isLoading = true;
this.error = '';
try {
// In a real implementation, this would redirect to the auth endpoint
const authUrl = `${this.getAuthEndpoint()}/oauth/v2/authorize?` +
`client_id=${this.getClientId()}&` +
`redirect_uri=${encodeURIComponent(window.location.origin)}&` +
`response_type=code&` +
`scope=openid profile email&` +
`provider=${provider}`;
window.location.href = authUrl;
} catch (err) {
this.error = 'Failed to initiate login';
console.error('Login error:', err);
} finally {
this.isLoading = false;
}
},
async emailLogin() {
this.isLoading = true;
this.error = '';
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: this.email,
password: this.password,
rememberMe: this.rememberMe
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Login failed');
}
const data = await response.json();
localStorage.setItem('authToken', data.token);
window.location.href = '/tables.html';
} catch (err) {
this.error = err.message || 'Login failed. Please check your credentials.';
console.error('Login error:', err);
} finally {
this.isLoading = false;
}
},
getAuthEndpoint() {
// In a real app, this would come from config
return 'https://auth.example.com';
},
getClientId() {
// In a real app, this would come from config
return 'general-bots-client';
}
}));
});

View file

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>General Bots - Authentication</title>
<link rel="stylesheet" href="styles.css">
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script src="app.js" defer></script>
</head>
<body>
<div class="auth-container" x-data="auth">
<div class="auth-left-panel">
<div class="auth-logo">
<h1>Welcome to General Bots</h1>
</div>
<div class="auth-quote">
<p>"Errar é Humano."</p>
<p>General Bots</p>
</div>
</div>
<div class="auth-form-container">
<div class="auth-form-header">
<h2>Sign in to your account</h2>
<p>Choose your preferred login method</p>
</div>
<div x-show="error" class="auth-error" x-text="error"></div>
<div class="auth-social-buttons">
<button class="auth-social-button google" @click="socialLogin('google')">
<span class="auth-social-icon">G</span>
Continue with Google
</button>
<button class="auth-social-button microsoft" @click="socialLogin('microsoft')">
<span class="auth-social-icon">M</span>
Continue with Microsoft
</button>
<button class="auth-social-button pragmatismo" @click="socialLogin('pragmatismo')">
<span class="auth-social-icon">P</span>
Continue with Pragmatismo
</button>
</div>
<div class="auth-divider">
<span>OR</span>
</div>
<form @submit.prevent="emailLogin" class="auth-form">
<div class="auth-form-group">
<label for="email">Email</label>
<input id="email" type="email" x-model="email" placeholder="your@email.com" required>
</div>
<div class="auth-form-group">
<label for="password">Password</label>
<input id="password" type="password" x-model="password" placeholder="••••••••" required>
</div>
<div class="auth-form-options">
<div class="auth-remember-me">
<input type="checkbox" id="remember" x-model="rememberMe">
<label for="remember">Remember me</label>
</div>
<a href="#" class="auth-forgot-password">Forgot password?</a>
</div>
<button type="submit" class="auth-submit-button" :disabled="isLoading">
<span x-text="isLoading ? 'Signing in...' : 'Sign in with Email'"></span>
</button>
</form>
<div class="auth-signup-link">
Don't have an account? <a href="#">Sign up</a>
</div>
</div>
</div>
</body>
</html>

249
web/desktop/auth/styles.css Normal file
View file

@ -0,0 +1,249 @@
:root {
--background: #1a1a2e;
--foreground: #ffffff;
--primary: #4f46e5;
--primary-foreground: #ffffff;
--secondary: #374151;
--secondary-foreground: #ffffff;
--muted: #4b5563;
--muted-foreground: #9ca3af;
--accent: #7c3aed;
--destructive: #ef4444;
--border: #374151;
--input: #1f2937;
--radius: 0.5rem;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', sans-serif;
background-color: var(--background);
color: var(--foreground);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.auth-container {
display: flex;
width: 100%;
max-width: 1200px;
background-color: var(--secondary);
border-radius: var(--radius);
overflow: hidden;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.auth-left-panel {
flex: 1;
padding: 4rem;
background: linear-gradient(135deg, var(--primary), var(--accent));
color: var(--primary-foreground);
display: flex;
flex-direction: column;
justify-content: space-between;
}
.auth-logo h1 {
font-size: 2rem;
margin-bottom: 1rem;
}
.auth-quote {
font-style: italic;
margin-top: auto;
}
.auth-quote p:last-child {
text-align: right;
margin-top: 0.5rem;
}
.auth-form-container {
flex: 1;
padding: 4rem;
max-width: 500px;
}
.auth-form-header {
margin-bottom: 2rem;
text-align: center;
}
.auth-form-header h2 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.auth-form-header p {
color: var(--muted-foreground);
}
.auth-error {
background-color: var(--destructive);
color: var(--primary-foreground);
padding: 0.75rem;
border-radius: var(--radius);
margin-bottom: 1rem;
text-align: center;
}
.auth-social-buttons {
display: grid;
grid-template-columns: 1fr;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.auth-social-button {
display: flex;
align-items: center;
justify-content: center;
padding: 0.75rem;
border-radius: var(--radius);
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: 1px solid var(--border);
background-color: var(--input);
color: var(--foreground);
}
.auth-social-button:hover {
background-color: var(--muted);
}
.auth-social-icon {
width: 1.25rem;
height: 1.25rem;
margin-right: 0.5rem;
font-weight: bold;
}
.auth-divider {
display: flex;
align-items: center;
margin: 1.5rem 0;
color: var(--muted-foreground);
}
.auth-divider::before,
.auth-divider::after {
content: "";
flex: 1;
border-bottom: 1px solid var(--border);
}
.auth-divider span {
padding: 0 1rem;
}
.auth-form {
margin-top: 1.5rem;
}
.auth-form-group {
margin-bottom: 1rem;
}
.auth-form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.auth-form-group input {
width: 100%;
padding: 0.75rem;
border-radius: var(--radius);
border: 1px solid var(--border);
background-color: var(--input);
color: var(--foreground);
}
.auth-form-group input:focus {
outline: none;
border-color: var(--primary);
}
.auth-form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin: 1rem 0;
}
.auth-remember-me {
display: flex;
align-items: center;
}
.auth-remember-me input {
margin-right: 0.5rem;
}
.auth-forgot-password {
color: var(--primary);
text-decoration: none;
}
.auth-forgot-password:hover {
text-decoration: underline;
}
.auth-submit-button {
width: 100%;
padding: 0.75rem;
border-radius: var(--radius);
background-color: var(--primary);
color: var(--primary-foreground);
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.auth-submit-button:hover {
background-color: var(--accent);
}
.auth-submit-button:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.auth-signup-link {
text-align: center;
margin: 1.5rem 0;
color: var(--muted-foreground);
}
.auth-signup-link a {
color: var(--primary);
text-decoration: none;
}
.auth-signup-link a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.auth-container {
flex-direction: column;
}
.auth-left-panel {
padding: 2rem;
}
.auth-form-container {
padding: 2rem;
max-width: 100%;
}
}

195
web/desktop/css/chat.css Normal file
View file

@ -0,0 +1,195 @@
.chat-container {
margin-top: 60px; /* Account for navbar height */
height: calc(100vh - 60px);
display: flex;
flex-direction: column;
}
#messages {
flex: 1;
overflow-y: auto;
padding: 20px 20px 140px;
max-width: 680px;
margin: 0 auto;
width: 100%;
position: relative;
z-index: 1;
}
.chat-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: var(--bg);
border-top: 1px solid var(--border);
padding: 12px;
z-index: 100;
transition: all 0.3s;
backdrop-filter: blur(20px);
}
/* Message styles */
.message-container {
margin-bottom: 24px;
opacity: 0;
transform: translateY(10px);
}
.user-message {
display: flex;
justify-content: flex-end;
margin-bottom: 8px;
}
.user-message-content {
background: var(--fg);
color: var(--bg);
border-radius: 18px;
padding: 12px 18px;
max-width: 80%;
font-size: 14px;
line-height: 1.5;
box-shadow: 0 2px 8px var(--shadow);
position: relative;
overflow: hidden;
}
.user-message-content::before {
content: '';
position: absolute;
inset: 0;
background: var(--gradient-2);
opacity: 0.3;
pointer-events: none;
}
.assistant-message {
display: flex;
gap: 8px;
align-items: flex-start;
}
.assistant-avatar {
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--logo-url) center/contain no-repeat;
flex-shrink: 0;
margin-top: 2px;
filter: var(--logo-filter, none);
}
.assistant-message-content {
flex: 1;
font-size: 14px;
line-height: 1.7;
background: var(--glass);
border-radius: 18px;
padding: 12px 18px;
border: 1px solid var(--border);
box-shadow: 0 2px 8px var(--shadow);
position: relative;
overflow: hidden;
}
.assistant-message-content::before {
content: '';
position: absolute;
inset: 0;
background: var(--gradient-1);
opacity: 0.5;
pointer-events: none;
}
/* Input and suggestions */
.suggestions-container {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 8px;
justify-content: center;
max-width: 680px;
margin: 0 auto 8px;
}
.suggestion-button {
padding: 6px 12px;
border-radius: 12px;
cursor: pointer;
font-size: 11px;
font-weight: 400;
transition: all 0.2s;
background: var(--glass);
border: 1px solid var(--border);
color: var(--fg);
}
.suggestion-button:hover {
background: var(--fg);
color: var(--bg);
transform: scale(1.05);
}
.input-container {
display: flex;
gap: 6px;
max-width: 680px;
margin: 0 auto;
align-items: center;
}
#messageInput {
flex: 1;
border-radius: 20px;
padding: 10px 16px;
font-size: 14px;
font-family: "Inter", sans-serif;
outline: none;
transition: all 0.3s;
background: var(--glass);
border: 1px solid var(--border);
color: var(--fg);
backdrop-filter: blur(10px);
}
#messageInput:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(0,102,255,0.1);
}
#messageInput::placeholder {
opacity: 0.3;
}
#sendBtn, #voiceBtn {
width: 36px;
height: 36px;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
border: none;
background: var(--fg);
color: var(--bg);
font-size: 16px;
flex-shrink: 0;
}
#voiceBtn.recording {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1) }
50% { opacity: 0.6; transform: scale(1.1) }
}
/* Responsive adjustments */
@media (max-width: 768px) {
#messages {
padding: 20px 16px 140px;
}
}

114
web/desktop/css/theme.css Normal file
View file

@ -0,0 +1,114 @@
:root {
/* Main theme */
--background: #ffffff;
--foreground: #000000;
--card: #f8f9fa;
--popover: #ffffff;
--primary: #2563eb;
--secondary: #f1f5f9;
--muted: #64748b;
--accent: #f59e0b;
--destructive: #ef4444;
--border: #e2e8f0;
--input: #e2e8f0;
--ring: #93c5fd;
--radius: 0.5rem;
--chart-1: #3b82f6;
--chart-2: #10b981;
--chart-3: #f59e0b;
--chart-4: #ef4444;
--chart-5: #8b5cf6;
/* File manager theme */
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-tertiary: #0f3460;
--text-primary: #e94560;
--text-secondary: #00d9ff;
--filemanager-border: #533483;
}
.navbar {
background: var(--background);
padding: 1rem;
border-bottom: 1px solid var(--border);
}
.mobile-menu-btn {
display: none;
}
.nav-links {
display: flex;
gap: 1rem;
}
.nav-links a {
color: var(--foreground);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: var(--radius);
}
.nav-links a:hover {
background: var(--secondary);
}
.footer {
background: var(--background);
padding: 1rem;
border-top: 1px solid var(--border);
display: flex;
gap: 2rem;
justify-content: center;
}
.shortcut-group {
display: flex;
gap: 1rem;
}
.shortcut-btn {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.5rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
cursor: pointer;
}
.shortcut-btn .key {
font-weight: bold;
color: var(--primary);
}
@media (max-width: 768px) {
.mobile-menu-btn {
display: block;
}
.nav-links {
display: none;
flex-direction: column;
position: absolute;
background: var(--background);
width: 100%;
left: 0;
padding: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.nav-links.hidden {
display: none;
}
.nav-links:not(.hidden) {
display: flex;
}
.shortcut-group {
flex-wrap: wrap;
}
}

View file

@ -0,0 +1,585 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XTree Gold File Manager</title>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>
[x-cloak] { display: none !important; }
/* XTree Gold inspired theme */
:root {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-tertiary: #0f3460;
--text-primary: #e94560;
--text-secondary: #00d9ff;
--border: #533483;
}
body {
font-family: 'Courier New', monospace;
background: var(--bg-primary);
color: var(--text-secondary);
}
.panel {
border: 2px solid var(--border);
background: var(--bg-secondary);
}
.tree-line {
color: var(--border);
}
.selected {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.shortcut-key {
display: inline-block;
min-width: 2rem;
padding: 0.25rem 0.5rem;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 0.25rem;
text-align: center;
font-weight: bold;
}
.file-icon {
display: inline-block;
width: 1.5rem;
text-align: center;
}
.folder-icon::before { content: '📁'; }
.file-icon.pdf::before { content: '📄'; }
.file-icon.xlsx::before { content: '📊'; }
.file-icon.json::before { content: '{}'; }
.file-icon.md::before { content: '📝'; }
.file-icon.jpg::before, .file-icon.jpeg::before, .file-icon.png::before { content: '🖼️'; }
.file-icon.mp4::before { content: '🎬'; }
.file-icon.mp3::before { content: '🎵'; }
.file-icon.default::before { content: '📋'; }
</style>
</head>
<body class="h-screen flex flex-col overflow-hidden" x-data="fileManager()" x-cloak>
<!-- Main Container -->
<div class="flex-1 flex overflow-hidden">
<!-- Left Sidebar - Folder Tree -->
<div class="panel w-64 flex flex-col overflow-hidden" :class="{ 'w-16': collapsed }">
<!-- Navigation Links -->
<div class="p-2 space-y-1">
<template x-for="link in navLinks" :key="link.path">
<button
@click="selectPath(link.path)"
:class="currentPath === link.path ? 'selected' : ''"
class="w-full px-3 py-2 text-left hover:bg-gray-700 rounded flex items-center gap-2"
>
<span x-text="link.icon" class="text-xl"></span>
<span x-show="!collapsed" x-text="link.title"></span>
</button>
</template>
</div>
<div class="border-t border-gray-600 my-2"></div>
<!-- Folder Tree -->
<div class="flex-1 overflow-auto p-2" x-show="!collapsed">
<template x-for="item in rootFolders" :key="item.id">
<div>
<button
@click="toggleFolder(item.path); selectPath(item.path)"
:class="currentPath === item.path ? 'selected' : ''"
class="w-full px-2 py-1 text-left hover:bg-gray-700 rounded flex items-center gap-1 text-sm"
>
<span x-show="item.is_dir" x-text="expanded[item.path] ? '▼' : '▶'" class="w-4"></span>
<span class="folder-icon"></span>
<span x-text="item.name"></span>
<span x-show="item.starred" class="ml-auto text-yellow-400"></span>
</button>
<div x-show="expanded[item.path]" class="ml-4">
<template x-for="child in getChildren(item.path)" :key="child.id">
<button
@click="selectPath(child.path)"
:class="currentPath === child.path ? 'selected' : ''"
class="w-full px-2 py-1 text-left hover:bg-gray-700 rounded flex items-center gap-1 text-sm"
>
<span :class="child.is_dir ? 'folder-icon' : 'file-icon ' + (child.type || 'default')"></span>
<span x-text="child.name"></span>
</button>
</template>
</div>
</div>
</template>
</div>
<!-- Collapse Toggle -->
<button
@click="collapsed = !collapsed"
class="p-2 border-t border-gray-600 hover:bg-gray-700 text-center"
>
<span x-text="collapsed ? '▶' : '◀'"></span>
</button>
</div>
<!-- Middle Panel - File List -->
<div class="panel flex-1 flex flex-col overflow-hidden mx-2">
<!-- Header -->
<div class="p-4 border-b border-gray-600">
<h1 class="text-2xl font-bold mb-2" x-text="currentItem?.name || 'My Drive'"></h1>
<!-- Search and Filter -->
<div class="flex gap-2">
<input
type="text"
x-model="searchTerm"
placeholder="Search files (Ctrl+F)"
class="flex-1 px-3 py-2 bg-gray-800 border border-gray-600 rounded focus:outline-none focus:border-blue-500"
/>
<select
x-model="filterType"
class="px-3 py-2 bg-gray-800 border border-gray-600 rounded focus:outline-none"
>
<option value="all">All items</option>
<option value="folders">Folders</option>
<option value="files">Files</option>
<option value="starred">Starred</option>
</select>
</div>
</div>
<!-- File List -->
<div class="flex-1 overflow-auto p-2">
<template x-for="file in filteredFiles" :key="file.id">
<button
@click="selectFile(file)"
@dblclick="openFile(file)"
@contextmenu.prevent="showContextMenu($event, file)"
:class="selectedFile?.id === file.id ? 'selected' : ''"
class="w-full p-3 text-left hover:bg-gray-700 rounded border-b border-gray-700 flex items-center gap-3"
>
<span :class="file.is_dir ? 'folder-icon' : 'file-icon ' + (file.type || 'default')"></span>
<div class="flex-1">
<div class="flex items-center gap-2">
<span x-text="file.name" class="font-semibold"></span>
<span x-show="file.starred" class="text-yellow-400 text-sm"></span>
<span x-show="file.shared" class="text-blue-400 text-sm">👥</span>
</div>
<div class="text-xs text-gray-400">
<span x-text="file.is_dir ? 'Folder' : formatFileSize(file.size)"></span>
</div>
</div>
<div class="text-xs text-gray-400" x-text="formatDate(file.modified)"></div>
</button>
</template>
<div x-show="filteredFiles.length === 0" class="text-center text-gray-500 py-8">
No files found
</div>
</div>
</div>
<!-- Right Panel - File Details -->
<div class="panel w-80 flex flex-col overflow-hidden">
<div class="p-2 border-b border-gray-600 flex gap-2">
<button @click="downloadFile()" :disabled="!selectedFile" class="px-3 py-1 bg-blue-600 hover:bg-blue-700 rounded disabled:opacity-50">
⬇ Download
</button>
<button @click="shareFile()" :disabled="!selectedFile" class="px-3 py-1 bg-green-600 hover:bg-green-700 rounded disabled:opacity-50">
🔗 Share
</button>
<button @click="toggleStar()" :disabled="!selectedFile" class="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 rounded disabled:opacity-50">
★ Star
</button>
</div>
<div class="flex-1 overflow-auto p-4">
<template x-if="selectedFile">
<div>
<div class="flex items-start gap-4 mb-4">
<div class="p-3 bg-gray-700 rounded">
<span :class="selectedFile.is_dir ? 'folder-icon' : 'file-icon ' + (selectedFile.type || 'default')" class="text-3xl"></span>
</div>
<div>
<h3 class="font-bold text-lg" x-text="selectedFile.name"></h3>
<p class="text-sm text-gray-400" x-text="selectedFile.is_dir ? 'Folder' : (selectedFile.type?.toUpperCase() || 'File') + ' • ' + formatFileSize(selectedFile.size)"></p>
</div>
</div>
<div class="space-y-3 text-sm">
<div>
<div class="font-semibold mb-1">Location</div>
<div class="text-gray-400" x-text="'/' + (selectedFile.path || '')"></div>
</div>
<div>
<div class="font-semibold mb-1">Modified</div>
<div class="text-gray-400" x-text="formatDateTime(selectedFile.modified)"></div>
</div>
<div x-show="!selectedFile.is_dir">
<div class="font-semibold mb-1">Size</div>
<div class="text-gray-400" x-text="formatFileSize(selectedFile.size)"></div>
</div>
</div>
</div>
</template>
<template x-if="!selectedFile">
<div class="text-center text-gray-500 py-8">
<div class="text-4xl mb-4">📄</div>
<div class="text-lg font-semibold">No file selected</div>
<div class="text-sm">Select a file to view details</div>
</div>
</template>
</div>
</div>
</div>
<!-- Footer - Status Bar with Keyboard Shortcuts -->
<div class="panel p-2 border-t-2 border-gray-600">
<div class="grid grid-cols-2 gap-2 text-xs">
<!-- Row 1 -->
<div class="flex flex-wrap gap-1">
<template x-for="shortcut in shortcuts[0]" :key="shortcut.key">
<button
@click="shortcut.action()"
class="shortcut-key hover:bg-gray-600"
:title="'Ctrl+' + shortcut.key"
>
<span x-text="shortcut.key"></span>
<span class="text-xs ml-1" x-text="shortcut.label"></span>
</button>
</template>
</div>
<!-- Row 2 -->
<div class="flex flex-wrap gap-1">
<template x-for="shortcut in shortcuts[1]" :key="shortcut.key">
<button
@click="shortcut.action()"
class="shortcut-key hover:bg-gray-600"
:title="'Ctrl+' + shortcut.key"
>
<span x-text="shortcut.key"></span>
<span class="text-xs ml-1" x-text="shortcut.label"></span>
</button>
</template>
</div>
</div>
</div>
<!-- Context Menu -->
<div
x-show="contextMenu.show"
@click.away="contextMenu.show = false"
:style="`top: ${contextMenu.y}px; left: ${contextMenu.x}px`"
class="fixed bg-gray-800 border border-gray-600 rounded shadow-lg z-50 py-1 min-w-48"
>
<template x-for="item in contextMenuItems" :key="item.label">
<button
@click="handleContextAction(item.action)"
class="w-full px-4 py-2 text-left hover:bg-gray-700 flex items-center gap-2"
>
<span x-text="item.icon"></span>
<span x-text="item.label"></span>
</button>
</template>
</div>
<script>
function fileManager() {
return {
collapsed: false,
currentPath: '',
searchTerm: '',
filterType: 'all',
selectedFile: null,
expanded: { '': true, 'projects': true },
contextMenu: { show: false, x: 0, y: 0, file: null },
navLinks: [
{ title: 'My Drive', path: '', icon: '🏠' },
{ title: 'Shared', path: 'shared', icon: '👥' },
{ title: 'Starred', path: 'starred', icon: '⭐' },
{ title: 'Recent', path: 'recent', icon: '🕐' },
{ title: 'Trash', path: 'trash', icon: '🗑️' },
],
fileSystem: {
"": {
id: "root", name: "My Drive", path: "", is_dir: true,
children: ["projects", "documents", "media", "shared"]
},
"projects": {
id: "projects", name: "Projects", path: "projects", is_dir: true,
modified: "2025-01-15T10:30:00Z", starred: true, shared: false,
children: ["web-apps", "mobile-apps", "ai-research"]
},
"projects/web-apps": {
id: "web-apps", name: "Web Applications", path: "projects/web-apps", is_dir: true,
modified: "2025-01-14T16:45:00Z", starred: false, shared: true,
children: ["package.json", "README.md"]
},
"projects/web-apps/package.json": {
id: "package-json", name: "package.json", path: "projects/web-apps/package.json",
is_dir: false, size: 2048, type: "json", modified: "2025-01-13T14:20:00Z"
},
"projects/web-apps/README.md": {
id: "readme-md", name: "README.md", path: "projects/web-apps/README.md",
is_dir: false, size: 5120, type: "md", modified: "2025-01-12T09:30:00Z", shared: true
},
"documents": {
id: "documents", name: "Documents", path: "documents", is_dir: true,
modified: "2025-01-14T12:00:00Z",
children: ["Q1-Strategy.pdf", "Budget-2025.xlsx"]
},
"documents/Q1-Strategy.pdf": {
id: "q1-strategy", name: "Q1 Strategy.pdf", path: "documents/Q1-Strategy.pdf",
is_dir: false, size: 1048576, type: "pdf", modified: "2025-01-10T15:30:00Z", starred: true, shared: true
},
"documents/Budget-2025.xlsx": {
id: "budget-xlsx", name: "Budget-2025.xlsx", path: "documents/Budget-2025.xlsx",
is_dir: false, size: 524288, type: "xlsx", modified: "2025-01-09T11:00:00Z"
},
"media": {
id: "media", name: "Media", path: "media", is_dir: true,
modified: "2025-01-13T18:45:00Z",
children: ["vacation-2024.jpg"]
},
"media/vacation-2024.jpg": {
id: "vacation-photo", name: "vacation-2024.jpg", path: "media/vacation-2024.jpg",
is_dir: false, size: 3145728, type: "jpg", modified: "2024-12-25T20:00:00Z", starred: true
},
"shared": {
id: "shared", name: "Shared", path: "shared", is_dir: true,
modified: "2025-01-12T11:20:00Z", shared: true,
children: []
}
},
shortcuts: [
[
{ key: 'Q', label: 'Rename', action: () => this.renameFile() },
{ key: 'W', label: 'View', action: () => this.viewFile() },
{ key: 'E', label: 'Edit', action: () => this.editFile() },
{ key: 'R', label: 'Move', action: () => this.moveFile() },
{ key: 'T', label: 'MkDir', action: () => this.makeDirectory() },
{ key: 'Y', label: 'Delete', action: () => this.deleteFile() },
{ key: 'U', label: 'Copy', action: () => this.copyFile() },
{ key: 'I', label: 'Cut', action: () => this.cutFile() },
{ key: 'O', label: 'Paste', action: () => this.pasteFile() },
{ key: 'P', label: 'Duplicate', action: () => this.duplicateFile() },
],
[
{ key: 'A', label: 'Select', action: () => this.toggleSelect() },
{ key: 'S', label: 'Select All', action: () => this.selectAll() },
{ key: 'D', label: 'Deselect', action: () => this.deselectAll() },
{ key: 'G', label: 'Details', action: () => this.showDetails() },
{ key: 'H', label: 'History', action: () => this.showHistory() },
{ key: 'J', label: 'Share', action: () => this.shareFile() },
{ key: 'K', label: 'Star', action: () => this.toggleStar() },
{ key: 'L', label: 'Download', action: () => this.downloadFile() },
{ key: 'Z', label: 'Upload', action: () => this.uploadFile() },
{ key: 'X', label: 'Refresh', action: () => this.refresh() },
]
],
contextMenuItems: [
{ icon: '👁️', label: 'Open', action: 'open' },
{ icon: '⬇', label: 'Download', action: 'download' },
{ icon: '🔗', label: 'Share', action: 'share' },
{ icon: '⭐', label: 'Star/Unstar', action: 'star' },
{ icon: '📋', label: 'Copy', action: 'copy' },
{ icon: '✂️', label: 'Cut', action: 'cut' },
{ icon: '✏️', label: 'Rename', action: 'rename' },
{ icon: '🗑️', label: 'Delete', action: 'delete' },
],
get currentItem() {
return this.fileSystem[this.currentPath];
},
get rootFolders() {
const root = this.fileSystem[''];
if (!root || !root.children) return [];
return root.children.map(name => this.fileSystem[name]).filter(Boolean);
},
get filteredFiles() {
const current = this.currentItem;
if (!current || !current.is_dir || !current.children) return [];
let files = current.children
.map(childName => {
const path = this.currentPath ? `${this.currentPath}/${childName}` : childName;
return this.fileSystem[path];
})
.filter(Boolean);
if (this.searchTerm) {
files = files.filter(f =>
f.name.toLowerCase().includes(this.searchTerm.toLowerCase())
);
}
if (this.filterType !== 'all') {
if (this.filterType === 'folders') files = files.filter(f => f.is_dir);
else if (this.filterType === 'files') files = files.filter(f => !f.is_dir);
else if (this.filterType === 'starred') files = files.filter(f => f.starred);
}
return files.sort((a, b) => {
if (a.is_dir && !b.is_dir) return -1;
if (!a.is_dir && b.is_dir) return 1;
return a.name.localeCompare(b.name);
});
},
getChildren(path) {
const item = this.fileSystem[path];
if (!item || !item.children) return [];
return item.children.map(name => {
const childPath = path ? `${path}/${name}` : name;
return this.fileSystem[childPath];
}).filter(Boolean);
},
selectPath(path) {
this.currentPath = path;
this.selectedFile = null;
},
selectFile(file) {
this.selectedFile = file;
},
openFile(file) {
if (file.is_dir) {
this.currentPath = file.path;
this.expanded[file.path] = true;
} else {
console.log('Opening file:', file.name);
}
},
toggleFolder(path) {
this.expanded[path] = !this.expanded[path];
},
showContextMenu(event, file) {
this.contextMenu = {
show: true,
x: event.clientX,
y: event.clientY,
file: file
};
this.selectedFile = file;
},
handleContextAction(action) {
console.log('Action:', action, 'File:', this.contextMenu.file);
this.contextMenu.show = false;
switch(action) {
case 'open': this.openFile(this.contextMenu.file); break;
case 'download': this.downloadFile(); break;
case 'share': this.shareFile(); break;
case 'star': this.toggleStar(); break;
case 'copy': this.copyFile(); break;
case 'cut': this.cutFile(); break;
case 'rename': this.renameFile(); break;
case 'delete': this.deleteFile(); break;
}
},
formatFileSize(bytes) {
if (!bytes) return '';
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
},
formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
const now = new Date();
const diff = now - date;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days === 0) return 'Today';
if (days === 1) return 'Yesterday';
if (days < 7) return `${days}d ago`;
return date.toLocaleDateString();
},
formatDateTime(dateString) {
if (!dateString) return '';
return new Date(dateString).toLocaleString();
},
// Action methods
renameFile() { console.log('Rename:', this.selectedFile?.name); },
viewFile() { console.log('View:', this.selectedFile?.name); },
editFile() { console.log('Edit:', this.selectedFile?.name); },
moveFile() { console.log('Move:', this.selectedFile?.name); },
makeDirectory() { console.log('Make Directory'); },
deleteFile() { console.log('Delete:', this.selectedFile?.name); },
copyFile() { console.log('Copy:', this.selectedFile?.name); },
cutFile() { console.log('Cut:', this.selectedFile?.name); },
pasteFile() { console.log('Paste'); },
duplicateFile() { console.log('Duplicate:', this.selectedFile?.name); },
toggleSelect() { console.log('Toggle Select'); },
selectAll() { console.log('Select All'); },
deselectAll() { this.selectedFile = null; },
showDetails() { console.log('Show Details'); },
showHistory() { console.log('Show History'); },
shareFile() { console.log('Share:', this.selectedFile?.name); },
toggleStar() {
if (this.selectedFile) {
this.selectedFile.starred = !this.selectedFile.starred;
}
},
downloadFile() { console.log('Download:', this.selectedFile?.name); },
uploadFile() { console.log('Upload'); },
refresh() { console.log('Refresh'); },
init() {
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey || e.metaKey) {
const key = e.key.toUpperCase();
// Find and execute shortcut
for (const row of this.shortcuts) {
const shortcut = row.find(s => s.key === key);
if (shortcut) {
e.preventDefault();
shortcut.action();
break;
}
}
// Special shortcuts
if (key === 'F') {
e.preventDefault();
document.querySelector('input[placeholder*="Search"]')?.focus();
}
} else if (e.key === 'Delete' && this.selectedFile) {
e.preventDefault();
this.deleteFile();
} else if (e.key === 'F2' && this.selectedFile) {
e.preventDefault();
this.renameFile();
}
});
}
};
}
</script>
</body>
</html>

47
web/desktop/index.html Normal file
View file

@ -0,0 +1,47 @@
<!doctype html>
<html lang="pt-br">
<head>
<meta charset="utf-8"/>
<title>General Bots</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<link rel="stylesheet" href="shared/styles.css" type="text/css">
<link rel="stylesheet" href="css/chat.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="js/mock-data.js"></script>
<script src="js/auth.js"></script>
</head>
<body>
<!-- Include shared navbar -->
<div id="navbar-container"></div>
<!-- Main chat content -->
<div class="chat-container">
<div class="connection-status connecting" id="connectionStatus"></div>
<div class="flash-overlay" id="flashOverlay"></div>
<main id="messages"></main>
<footer class="chat-footer">
<div class="suggestions-container" id="suggestions"></div>
<div class="input-container">
<input id="messageInput" type="text" placeholder="Message..." autofocus/>
<button id="voiceBtn" title="Voice">🎤</button>
<button id="sendBtn" title="Send"></button>
</div>
</footer>
</div>
<!-- Load navbar template -->
<script>
fetch('shared/navbar.html')
.then(response => response.text())
.then(html => {
document.getElementById('navbar-container').innerHTML = html;
// Set active nav link
document.querySelector(`.nav-link[data-target="chat"]`).classList.add('active');
});
</script>
</body>
</html>

61
web/desktop/js/auth.js Normal file
View file

@ -0,0 +1,61 @@
// Handle authentication state
let currentUser = null;
let currentSession = null;
// Initialize auth with mock data
function initializeAuth() {
if (window.location.pathname.includes('auth')) {
return; // Don't initialize on auth pages
}
// Check for existing session
const sessionId = localStorage.getItem('sessionId');
if (sessionId) {
currentSession = mockSessions.find(s => s.id === sessionId) || mockSessions[0];
} else {
currentSession = mockSessions[0];
localStorage.setItem('sessionId', currentSession.id);
}
// Set current user
currentUser = mockUsers[0];
updateUserUI();
}
// Update UI based on auth state
function updateUserUI() {
const userAvatar = document.getElementById('userAvatar');
if (userAvatar && currentUser) {
userAvatar.textContent = currentUser.avatar;
}
}
// Handle login
function handleLogin(email, password) {
// In a real app, this would call an API
currentUser = mockUsers.find(u => u.email === email) || mockUsers[0];
currentSession = mockSessions[0];
localStorage.setItem('sessionId', currentSession.id);
updateUserUI();
window.location.href = '/desktop/index.html';
}
// Handle logout
function handleLogout() {
localStorage.removeItem('sessionId');
window.location.href = '/desktop/auth/login.html';
}
// Check auth state for protected routes
function checkAuth() {
if (!currentUser && !window.location.pathname.includes('auth')) {
window.location.href = '/desktop/auth/login.html';
}
}
// Initialize on page load
if (document.readyState === 'complete') {
initializeAuth();
} else {
window.addEventListener('load', initializeAuth);
}

View file

@ -0,0 +1,46 @@
const mockUsers = [
{
id: 'user1',
name: 'John Doe',
email: 'john@example.com',
avatar: '👨'
},
{
id: 'user2',
name: 'Jane Smith',
email: 'jane@example.com',
avatar: '👩'
}
];
const mockBots = [
{
id: 'default_bot',
name: 'General Bot',
description: 'Main assistant bot'
}
];
const mockSessions = [
{
id: 'session1',
title: 'First Chat',
created_at: new Date().toISOString()
},
{
id: 'session2',
title: 'Project Discussion',
created_at: new Date(Date.now() - 86400000).toISOString()
}
];
const mockAuthResponse = {
user_id: mockUsers[0].id,
session_id: mockSessions[0].id
};
const mockSuggestions = [
{ text: "What can you do?", context: "capabilities" },
{ text: "Show my files", context: "drive" },
{ text: "Create a task", context: "tasks" }
];

29
web/desktop/mail/data.js Normal file
View file

@ -0,0 +1,29 @@
export const mails = [
{
id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
name: "William Smith",
email: "williamsmith@example.com",
subject: "Meeting Tomorrow",
text: "Hi, let's have a meeting tomorrow to discuss the project...",
date: "2023-10-22T09:00:00",
read: true,
labels: ["meeting", "work", "important"],
},
// Additional emails would go here
];
export const accounts = [
{
label: "Alicia Koch",
email: "alicia@example.com",
icon: "📧",
}
];
export const contacts = [
{
name: "Emma Johnson",
email: "emma.johnson@example.com",
},
// Additional contacts would go here
];

View file

@ -0,0 +1,94 @@
<!DOCTYPE html>
<html>
<head>
<title>Email Client</title>
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script src="./data.js"></script>
<script src="./store.js"></script>
<style>
[x-cloak] { display: none !important; }
:root {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--text-primary: #e94560;
--text-secondary: #00d9ff;
}
body {
font-family: 'Segoe UI', sans-serif;
margin: 0;
padding: 0;
background: var(--bg-primary);
color: white;
}
.email-container {
display: flex;
height: 100vh;
}
.sidebar {
width: 250px;
background: var(--bg-secondary);
padding: 1rem;
}
.email-list {
flex: 1;
overflow-y: auto;
}
.email-content {
flex: 2;
padding: 1rem;
}
.email-item {
padding: 1rem;
border-bottom: 1px solid #333;
cursor: pointer;
}
.email-item:hover {
background: rgba(255,255,255,0.1);
}
.email-item.unread {
font-weight: bold;
}
</style>
</head>
<body x-data="{
mails,
accounts,
contacts,
...mailStore
}" x-cloak>
<div class="email-container">
<div class="sidebar">
<h2>Accounts</h2>
<div x-text="accounts[0].label"></div>
<h2>Folders</h2>
<div>Inbox</div>
<div>Sent</div>
<div>Drafts</div>
</div>
<div class="email-list">
<template x-for="mail in mails" :key="mail.id">
<div
class="email-item"
:class="{ 'unread': !mail.read }"
@click="setSelected(mail.id)"
>
<div x-text="mail.name"></div>
<div x-text="mail.subject"></div>
<div x-text="mail.date"></div>
</div>
</template>
</div>
<div class="email-content">
<template x-if="selected">
<div>
<h2 x-text="mails.find(m => m.id === selected).subject"></h2>
<div x-text="mails.find(m => m.id === selected).text"></div>
</div>
</template>
</div>
</div>
</body>
</html>

10
web/desktop/mail/store.js Normal file
View file

@ -0,0 +1,10 @@
export function createMailStore() {
return {
selected: null,
setSelected(id) {
this.selected = id;
}
};
}
export const mailStore = createMailStore();

View file

@ -0,0 +1,27 @@
<div x-data="{
shortcuts: [
[
{ key: 'Q', label: 'Rename', action: () => console.log('Rename') },
{ key: 'W', label: 'View', action: () => console.log('View') },
{ key: 'E', label: 'Edit', action: () => console.log('Edit') },
{ key: 'I', label: 'Cut', action: () => console.log('Cut') },
{ key: 'O', label: 'Paste', action: () => console.log('Paste') },
{ key: 'P', label: 'Duplicate', action: () => console.log('Duplicate') }
],
[
{ key: 'K', label: 'Star', action: () => console.log('Star') },
{ key: 'L', label: 'Download', action: () => console.log('Download') },
{ key: 'Z', label: 'Upload', action: () => console.log('Upload') },
{ key: 'X', label: 'Refresh', action: () => console.log('Refresh') }
]
]
}" class="footer">
<div class="shortcut-group" x-for="group in shortcuts">
<template x-for="shortcut in group">
<button @click="shortcut.action()" class="shortcut-btn">
<span x-text="shortcut.key" class="key"></span>
<span x-text="shortcut.label" class="label"></span>
</button>
</template>
</div>
</div>

View file

@ -0,0 +1,30 @@
<nav class="navbar">
<div class="navbar-brand">
<img src="/icons/general-bots.svg" alt="Logo" class="logo">
<span>General Bots</span>
</div>
<div class="nav-links">
<a href="./index.html" class="nav-link active" data-target="chat">
<i class="icon">💬</i> Chat
</a>
<a href="./drive/index.html" class="nav-link" data-target="drive">
<i class="icon">📁</i> Drive
</a>
<a href="./tables/index.html" class="nav-link" data-target="tables">
<i class="icon">📊</i> Tables
</a>
<a href="./tasks/index.html" class="nav-link" data-target="tasks">
<i class="icon"></i> Tasks
</a>
<a href="./mail/index.html" class="nav-link" data-target="mail">
<i class="icon">✉️</i> Mail
</a>
</div>
<div class="nav-user">
<div class="user-avatar" id="userAvatar">👤</div>
<div class="user-menu">
<a href="../auth/login.html" class="user-menu-item">Sign In</a>
<a href="../auth/register.html" class="user-menu-item">Register</a>
</div>
</div>
</nav>

View file

@ -0,0 +1,79 @@
<div x-data="{
leftWidth: '30%',
rightWidth: '70%',
isDragging: false,
startDrag() {
this.isDragging = true;
document.body.style.cursor = 'col-resize';
document.addEventListener('mousemove', this.drag.bind(this));
document.addEventListener('mouseup', this.stopDrag.bind(this));
},
drag(e) {
if (!this.isDragging) return;
const container = this.$el.parentElement;
const containerRect = container.getBoundingClientRect();
const newLeftWidth = ((e.clientX - containerRect.left) / containerRect.width) * 100;
if (newLeftWidth > 20 && newLeftWidth < 80) {
this.leftWidth = `${newLeftWidth}%`;
this.rightWidth = `${100 - newLeftWidth}%`;
}
},
stopDrag() {
this.isDragging = false;
document.body.style.cursor = '';
document.removeEventListener('mousemove', this.drag);
document.removeEventListener('mouseup', this.stopDrag);
}
}" class="resizable-container">
<div class="resizable-panel left" :style="{ width: leftWidth }">
<slot name="left"></slot>
</div>
<div class="resizable-handle" @mousedown="startDrag"></div>
<div class="resizable-panel right" :style="{ width: rightWidth }">
<slot name="right"></slot>
</div>
</div>
<style>
.resizable-container {
display: flex;
height: 100%;
width: 100%;
}
.resizable-panel {
height: 100%;
overflow: auto;
}
.resizable-handle {
width: 8px;
background: var(--border);
cursor: col-resize;
transition: background 0.2s;
}
.resizable-handle:hover {
background: var(--primary);
}
@media (max-width: 768px) {
.resizable-container {
flex-direction: column;
}
.resizable-panel {
width: 100% !important;
height: auto;
}
.resizable-handle {
width: 100%;
height: 8px;
}
}
</style>

View file

@ -0,0 +1,112 @@
:root {
--navbar-height: 60px;
--primary-color: #0066ff;
--text-color: #333;
--bg-color: #fff;
--border-color: #e0e0e0;
--hover-bg: #f5f5f5;
}
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
height: var(--navbar-height);
padding: 0 20px;
background: var(--bg-color);
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
}
.navbar-brand {
display: flex;
align-items: center;
gap: 10px;
font-weight: bold;
color: var(--text-color);
}
.logo {
height: 30px;
width: 30px;
}
.nav-links {
display: flex;
gap: 20px;
}
.nav-link {
display: flex;
align-items: center;
gap: 5px;
text-decoration: none;
color: var(--text-color);
padding: 5px 10px;
border-radius: 4px;
transition: all 0.2s;
}
.nav-link:hover {
background: var(--hover-bg);
}
.nav-link.active {
color: var(--primary-color);
font-weight: 500;
}
.nav-user {
position: relative;
cursor: pointer;
}
.user-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--hover-bg);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
.user-menu {
position: absolute;
right: 0;
top: 100%;
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 5px 0;
min-width: 150px;
display: none;
}
.user-menu-item {
display: block;
padding: 8px 15px;
color: var(--text-color);
text-decoration: none;
}
.user-menu-item:hover {
background: var(--hover-bg);
}
.nav-user:hover .user-menu {
display: block;
}
[data-theme="dark"] {
--text-color: #fff;
--bg-color: #1a1a1a;
--border-color: #333;
--hover-bg: #333;
}

View file

@ -4,28 +4,26 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tables - Excel Clone</title>
<link rel="stylesheet" href="./styles/theme.css">
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="./store.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
.app-container {
display: flex;
flex-direction: column;
height: 100vh;
background: var(--background);
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f0f0f0;
.content-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.app-container {
height: 100vh;
display: flex;
flex-direction: column;
background: white;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
color: white;
padding: 15px 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
@ -41,194 +39,34 @@
opacity: 0.9;
}
.toolbar {
.spreadsheet-content {
display: flex;
gap: 10px;
padding: 10px;
background: #fafafa;
border-bottom: 1px solid #ddd;
flex-wrap: wrap;
}
.toolbar button {
padding: 8px 15px;
border: 1px solid #ccc;
background: white;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.toolbar button:hover {
background: #f0f0f0;
border-color: #999;
}
.toolbar button:active {
transform: scale(0.98);
}
.formula-bar {
display: flex;
align-items: center;
padding: 8px 10px;
background: white;
border-bottom: 2px solid #ddd;
gap: 10px;
}
.cell-ref {
font-weight: bold;
min-width: 60px;
padding: 6px 10px;
background: #f0f0f0;
border-radius: 4px;
font-family: monospace;
}
.formula-input {
flex: 1;
padding: 6px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: monospace;
font-size: 14px;
}
.spreadsheet-container {
flex: 1;
overflow: auto;
position: relative;
background: #fff;
}
.spreadsheet {
display: inline-block;
border-collapse: collapse;
background: white;
}
.spreadsheet th,
.spreadsheet td {
border: 1px solid #d0d0d0;
min-width: 100px;
height: 25px;
padding: 4px 8px;
text-align: left;
position: relative;
}
.spreadsheet th {
background: #f0f0f0;
font-weight: 600;
text-align: center;
position: sticky;
z-index: 10;
font-size: 12px;
}
.spreadsheet thead th {
top: 0;
}
.spreadsheet tbody th {
left: 0;
min-width: 50px;
background: #f0f0f0;
}
.spreadsheet td {
cursor: cell;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.spreadsheet td.selected {
outline: 2px solid #667eea;
outline-offset: -2px;
background: #e8eaf6;
}
.spreadsheet td.editing {
padding: 0;
}
.cell-editor {
width: 100%;
flex-direction: column;
height: 100%;
border: none;
padding: 4px 8px;
font-family: inherit;
font-size: inherit;
outline: 2px solid #667eea;
}
.status-bar {
display: flex;
justify-content: space-between;
padding: 6px 15px;
background: #f8f8f8;
border-top: 1px solid #ddd;
font-size: 12px;
color: #666;
}
.status-bar .stats {
display: flex;
gap: 20px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.header {
animation: fadeIn 0.5s ease;
}
/* Existing spreadsheet styles remain unchanged */
</style>
</head>
<body>
<div class="app-container">
<div class="header">
<h1>📊 Tables</h1>
<div class="subtitle">Excel Clone - Celebrating Lotus 1-2-3 Legacy 🎉</div>
<div class="app-container" x-data>
<div class="navbar-container">
<div x-html="(await fetch('./components/navbar.html')).text()"></div>
</div>
<div class="toolbar">
<button onclick="app.bold()">📝 Bold</button>
<button onclick="app.addRow()"> Add Row</button>
<button onclick="app.addColumn()"> Add Column</button>
<button onclick="app.deleteRow()"> Delete Row</button>
<button onclick="app.deleteColumn()"> Delete Column</button>
<button onclick="app.sort()">🔽 Sort A-Z</button>
<button onclick="app.sum()">Σ Sum</button>
<button onclick="app.average()">📊 Average</button>
<button onclick="app.clearCell()">🗑️ Clear</button>
<button onclick="app.exportData()">💾 Export</button>
</div>
<div class="formula-bar">
<div class="cell-ref" id="cellRef">A1</div>
<input type="text" class="formula-input" id="formulaInput" placeholder="Enter value or formula (=SUM, =AVERAGE, etc.)">
</div>
<div class="spreadsheet-container" id="spreadsheetContainer">
<table class="spreadsheet" id="spreadsheet">
<thead id="tableHead"></thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<div class="status-bar">
<div class="stats">
<span>Rows: <strong id="rowCount">0</strong></span>
<span>Cols: <strong id="colCount">0</strong></span>
<span>Selected: <strong id="selectedCell">None</strong></span>
<div class="content-container">
<div class="header">
<h1>📊 Tables</h1>
<div class="subtitle">Excel Clone - Celebrating Lotus 1-2-3 Legacy 🎉</div>
</div>
<div>Ready | Lotus 1-2-3 Mode Active ✨</div>
<div class="resizable-container">
<div class="resizable-panel left" style="width: 30%">
<!-- Left panel content -->
</div>
<div class="resizable-handle"></div>
<div class="resizable-panel right" style="width: 70%">
<div class="spreadsheet-content">
</div>
</div>

View file

@ -732,14 +732,14 @@ connectionStatus.className=`connection-status ${s}`;
}
function getWebSocketUrl(){
const p=window.location.protocol==="https:"?"wss:":"ws:",s=currentSessionId||crypto.randomUUID(),u=currentUserId||crypto.randomUUID();
return`${p}//${window.location.host}/ws?session_id=${s}&user_id=${u}`;
const p="ws:",s=currentSessionId||crypto.randomUUID(),u=currentUserId||crypto.randomUUID();
return`${p}//localhost:8080/ws?session_id=${s}&user_id=${u}`;
}
async function initializeAuth(){
try{
updateConnectionStatus("connecting");
const p=window.location.pathname.split('/').filter(s=>s),b=p.length>0?p[0]:'default',r=await fetch(`/api/auth?bot_name=${encodeURIComponent(b)}`),a=await r.json();
const p=window.location.pathname.split('/').filter(s=>s),b=p.length>0?p[0]:'default',r=await fetch(`http://localhost:8080/api/auth?bot_name=${encodeURIComponent(b)}`),a=await r.json();
currentUserId=a.user_id;
currentSessionId=a.session_id;
connectWebSocket();
@ -753,7 +753,7 @@ setTimeout(initializeAuth,3000);
async function loadSessions(){
try{
const r=await fetch("/api/sessions"),s=await r.json(),h=document.getElementById("history");
const r=await fetch("http://localhost:8080/api/sessions"),s=await r.json(),h=document.getElementById("history");
h.innerHTML="";
s.forEach(session=>{
const item=document.createElement('div');
@ -769,7 +769,7 @@ console.error("Failed to load sessions:",e);
async function createNewSession(){
try{
const r=await fetch("/api/sessions",{method:"POST"}),s=await r.json();
const r=await fetch("http://localhost:8080/api/sessions",{method:"POST"}),s=await r.json();
currentSessionId=s.session_id;
hasReceivedInitialMessage=false;
connectWebSocket();
@ -802,7 +802,7 @@ sidebar.classList.remove('open');
async function loadSessionHistory(s){
try{
const r=await fetch("/api/sessions/"+s),h=await r.json(),m=document.getElementById("messages");
const r=await fetch("http://localhost:8080/api/sessions/"+s),h=await r.json(),m=document.getElementById("messages");
m.innerHTML="";
if(h.length===0){
updateContextUsage(0);
@ -1156,7 +1156,7 @@ await stopVoiceSession();
async function startVoiceSession(){
if(!currentSessionId)return;
try{
const r=await fetch("th/voice/start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:currentSessionId,user_id:currentUserId})}),d=await r.json();
const r=await fetch("http://localhost:8080/api/voice/start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:currentSessionId,user_id:currentUserId})}),d=await r.json();
if(d.token){
await connectToVoiceRoom(d.token);
startVoiceRecording();
@ -1170,7 +1170,7 @@ showWarning("Falha ao iniciar modo de voz");
async function stopVoiceSession(){
if(!currentSessionId)return;
try{
await fetch("/api/voice/stop",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:currentSessionId})});
await fetch("http://localhost:8080/api/voice/stop",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:currentSessionId})});
if(voiceRoom){
voiceRoom.disconnect();
voiceRoom=null;
@ -1185,7 +1185,7 @@ console.error("Failed to stop voice session:",e);
async function connectToVoiceRoom(t){
try{
const r=new LiveKitClient.Room(),p=window.location.protocol==="https:"?"wss:":"ws:",u=`${p}//${window.location.host}/voice`;
const r=new LiveKitClient.Room(),p="ws:",u=`${p}//localhost:8080/voice`;
await r.connect(u,t);
voiceRoom=r;