lollmsbot 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lollmsbot/__init__.py +1 -0
- lollmsbot/agent.py +1682 -0
- lollmsbot/channels/__init__.py +22 -0
- lollmsbot/channels/discord.py +408 -0
- lollmsbot/channels/http_api.py +449 -0
- lollmsbot/channels/telegram.py +272 -0
- lollmsbot/cli.py +217 -0
- lollmsbot/config.py +90 -0
- lollmsbot/gateway.py +606 -0
- lollmsbot/guardian.py +692 -0
- lollmsbot/heartbeat.py +826 -0
- lollmsbot/lollms_client.py +37 -0
- lollmsbot/skills.py +1483 -0
- lollmsbot/soul.py +482 -0
- lollmsbot/storage/__init__.py +245 -0
- lollmsbot/storage/sqlite_store.py +332 -0
- lollmsbot/tools/__init__.py +151 -0
- lollmsbot/tools/calendar.py +717 -0
- lollmsbot/tools/filesystem.py +663 -0
- lollmsbot/tools/http.py +498 -0
- lollmsbot/tools/shell.py +519 -0
- lollmsbot/ui/__init__.py +11 -0
- lollmsbot/ui/__main__.py +121 -0
- lollmsbot/ui/app.py +1122 -0
- lollmsbot/ui/routes.py +39 -0
- lollmsbot/wizard.py +1493 -0
- lollmsbot-0.0.1.dist-info/METADATA +25 -0
- lollmsbot-0.0.1.dist-info/RECORD +32 -0
- lollmsbot-0.0.1.dist-info/WHEEL +5 -0
- lollmsbot-0.0.1.dist-info/entry_points.txt +2 -0
- lollmsbot-0.0.1.dist-info/licenses/LICENSE +201 -0
- lollmsbot-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Telegram channel implementation for LollmsBot.
|
|
3
|
+
|
|
4
|
+
Uses shared Agent for all business logic.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Awaitable, Callable, List, Optional, Set
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from telegram import Update
|
|
12
|
+
from telegram.ext import (
|
|
13
|
+
Application,
|
|
14
|
+
ApplicationBuilder,
|
|
15
|
+
CommandHandler,
|
|
16
|
+
ContextTypes,
|
|
17
|
+
MessageHandler,
|
|
18
|
+
filters,
|
|
19
|
+
)
|
|
20
|
+
TELEGRAM_AVAILABLE = True
|
|
21
|
+
except ImportError as e:
|
|
22
|
+
TELEGRAM_AVAILABLE = False
|
|
23
|
+
TELEGRAM_IMPORT_ERROR = str(e)
|
|
24
|
+
# Create dummy classes for type checking
|
|
25
|
+
class Update: pass
|
|
26
|
+
class Application: pass
|
|
27
|
+
class ApplicationBuilder: pass
|
|
28
|
+
class CommandHandler: pass
|
|
29
|
+
class ContextTypes:
|
|
30
|
+
DEFAULT_TYPE = Any
|
|
31
|
+
class MessageHandler: pass
|
|
32
|
+
class filters:
|
|
33
|
+
TEXT = None
|
|
34
|
+
COMMAND = None
|
|
35
|
+
|
|
36
|
+
from lollmsbot.agent import Agent, PermissionLevel
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TelegramChannel:
|
|
43
|
+
"""Telegram messaging channel using shared Agent.
|
|
44
|
+
|
|
45
|
+
All business logic is delegated to the Agent. This class handles
|
|
46
|
+
only Telegram-specific protocol concerns.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
agent: Agent,
|
|
52
|
+
bot_token: str,
|
|
53
|
+
allowed_users: Optional[List[int]] = None,
|
|
54
|
+
blocked_users: Optional[List[int]] = None,
|
|
55
|
+
):
|
|
56
|
+
if not TELEGRAM_AVAILABLE:
|
|
57
|
+
raise ImportError(
|
|
58
|
+
f"Telegram support requires 'python-telegram-bot'. "
|
|
59
|
+
f"Install with: pip install 'python-telegram-bot>=20.0' "
|
|
60
|
+
f"Original error: {TELEGRAM_IMPORT_ERROR}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
self.agent = agent
|
|
64
|
+
self.bot_token = bot_token
|
|
65
|
+
self.allowed_users: Optional[Set[int]] = set(allowed_users) if allowed_users else None
|
|
66
|
+
self.blocked_users: Set[int] = set(blocked_users) if blocked_users else set()
|
|
67
|
+
self.application: Optional[Application] = None
|
|
68
|
+
self._is_running = False
|
|
69
|
+
|
|
70
|
+
def _can_interact(self, user_id: int) -> tuple[bool, str]:
|
|
71
|
+
"""Check if user can interact with bot."""
|
|
72
|
+
if user_id in self.blocked_users:
|
|
73
|
+
return False, "user blocked"
|
|
74
|
+
|
|
75
|
+
if self.allowed_users is not None:
|
|
76
|
+
if user_id not in self.allowed_users:
|
|
77
|
+
return False, "not in allowed users"
|
|
78
|
+
|
|
79
|
+
return True, ""
|
|
80
|
+
|
|
81
|
+
def _get_user_id(self, tg_user_id: int) -> str:
|
|
82
|
+
"""Generate consistent user ID for Agent."""
|
|
83
|
+
return f"telegram:{tg_user_id}"
|
|
84
|
+
|
|
85
|
+
async def start(self) -> None:
|
|
86
|
+
"""Start the Telegram bot."""
|
|
87
|
+
if not TELEGRAM_AVAILABLE:
|
|
88
|
+
raise ImportError("python-telegram-bot is not installed")
|
|
89
|
+
|
|
90
|
+
if self._is_running:
|
|
91
|
+
logger.warning("Telegram channel is already running")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
self.application = (
|
|
96
|
+
ApplicationBuilder()
|
|
97
|
+
.token(self.bot_token)
|
|
98
|
+
.build()
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Add handlers
|
|
102
|
+
self.application.add_handler(
|
|
103
|
+
CommandHandler("start", self._handle_start_command)
|
|
104
|
+
)
|
|
105
|
+
self.application.add_handler(
|
|
106
|
+
CommandHandler("help", self._handle_help_command)
|
|
107
|
+
)
|
|
108
|
+
self.application.add_handler(
|
|
109
|
+
MessageHandler(filters.TEXT & ~filters.COMMAND, self._handle_message)
|
|
110
|
+
)
|
|
111
|
+
self.application.add_error_handler(self._handle_error)
|
|
112
|
+
|
|
113
|
+
# Start
|
|
114
|
+
await self.application.initialize()
|
|
115
|
+
await self.application.start()
|
|
116
|
+
await self.application.updater.start_polling(drop_pending_updates=True)
|
|
117
|
+
|
|
118
|
+
self._is_running = True
|
|
119
|
+
logger.info("Telegram channel started successfully")
|
|
120
|
+
|
|
121
|
+
except Exception as exc:
|
|
122
|
+
logger.error(f"Failed to start Telegram channel: {exc}")
|
|
123
|
+
self._is_running = False
|
|
124
|
+
raise
|
|
125
|
+
|
|
126
|
+
async def stop(self) -> None:
|
|
127
|
+
"""Stop the Telegram bot."""
|
|
128
|
+
if not self._is_running:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
if self.application:
|
|
133
|
+
await self.application.updater.stop()
|
|
134
|
+
await self.application.stop()
|
|
135
|
+
await self.application.shutdown()
|
|
136
|
+
self.application = None
|
|
137
|
+
|
|
138
|
+
self._is_running = False
|
|
139
|
+
logger.info("Telegram channel stopped successfully")
|
|
140
|
+
|
|
141
|
+
except Exception as exc:
|
|
142
|
+
logger.error(f"Error stopping Telegram channel: {exc}")
|
|
143
|
+
raise
|
|
144
|
+
|
|
145
|
+
async def _handle_start_command(
|
|
146
|
+
self,
|
|
147
|
+
update: Update,
|
|
148
|
+
context: ContextTypes.DEFAULT_TYPE,
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Handle /start command."""
|
|
151
|
+
if not update.effective_chat or not update.message:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
user_id = update.effective_user.id if update.effective_user else 0
|
|
155
|
+
|
|
156
|
+
# Check permissions
|
|
157
|
+
can_interact, reason = self._can_interact(user_id)
|
|
158
|
+
if not can_interact:
|
|
159
|
+
await update.message.reply_text("⛔ You don't have permission to use this bot.")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
# Get or create user permissions in agent
|
|
163
|
+
agent_user_id = self._get_user_id(user_id)
|
|
164
|
+
|
|
165
|
+
welcome_text = (
|
|
166
|
+
f"👋 Hello! I'm {self.agent.name}.\n\n"
|
|
167
|
+
f"Send me any message and I'll respond using my AI backend.\n\n"
|
|
168
|
+
f"Your ID: `{user_id}`\n"
|
|
169
|
+
f"Use /help for more information."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
await update.message.reply_text(welcome_text, parse_mode="Markdown")
|
|
173
|
+
|
|
174
|
+
async def _handle_help_command(
|
|
175
|
+
self,
|
|
176
|
+
update: Update,
|
|
177
|
+
context: ContextTypes.DEFAULT_TYPE,
|
|
178
|
+
) -> None:
|
|
179
|
+
"""Handle /help command."""
|
|
180
|
+
help_text = (
|
|
181
|
+
f"*{self.agent.name} - Available Commands*\n\n"
|
|
182
|
+
"/start - Start the bot\n"
|
|
183
|
+
"/help - Show this help message\n\n"
|
|
184
|
+
"Just send any message to chat with me!"
|
|
185
|
+
)
|
|
186
|
+
await update.message.reply_text(help_text, parse_mode="Markdown")
|
|
187
|
+
|
|
188
|
+
async def _handle_message(
|
|
189
|
+
self,
|
|
190
|
+
update: Update,
|
|
191
|
+
context: ContextTypes.DEFAULT_TYPE,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Handle incoming text messages."""
|
|
194
|
+
if not update.effective_chat or not update.message or not update.message.text:
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
user_id = update.effective_user.id if update.effective_user else 0
|
|
198
|
+
chat_id = update.effective_chat.id
|
|
199
|
+
|
|
200
|
+
# Check permissions
|
|
201
|
+
can_interact, reason = self._can_interact(user_id)
|
|
202
|
+
if not can_interact:
|
|
203
|
+
logger.warning(f"Message from unauthorized user {user_id}: {reason}")
|
|
204
|
+
await update.message.reply_text("⛔ Access denied.")
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
# Build context
|
|
208
|
+
agent_user_id = self._get_user_id(user_id)
|
|
209
|
+
context_data = {
|
|
210
|
+
"channel": "telegram",
|
|
211
|
+
"telegram_user_id": user_id,
|
|
212
|
+
"telegram_username": update.effective_user.username if update.effective_user else None,
|
|
213
|
+
"telegram_chat_id": chat_id,
|
|
214
|
+
"is_dm": update.effective_chat.type == "private",
|
|
215
|
+
"chat_type": update.effective_chat.type,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
message_text = update.message.text
|
|
219
|
+
|
|
220
|
+
logger.info(f"Processing message from Telegram user {user_id}: {message_text[:50]}...")
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
# Use Agent for processing
|
|
224
|
+
result = await self.agent.chat(
|
|
225
|
+
user_id=agent_user_id,
|
|
226
|
+
message=message_text,
|
|
227
|
+
context=context_data,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
if result.get("permission_denied"):
|
|
231
|
+
await update.message.reply_text("⛔ You don't have permission to use this bot.")
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
if not result.get("success"):
|
|
235
|
+
error_msg = result.get("error", "Unknown error")
|
|
236
|
+
await update.message.reply_text(f"❌ Error: {error_msg[:400]}")
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
response = result.get("response", "No response")
|
|
240
|
+
# Telegram has 4096 char limit
|
|
241
|
+
if len(response) > 4000:
|
|
242
|
+
response = response[:4000] + "\n... (truncated)"
|
|
243
|
+
|
|
244
|
+
await update.message.reply_text(response)
|
|
245
|
+
|
|
246
|
+
except Exception as exc:
|
|
247
|
+
logger.error(f"Error processing Telegram message: {exc}")
|
|
248
|
+
try:
|
|
249
|
+
await update.message.reply_text("❌ An error occurred. Please try again.")
|
|
250
|
+
except Exception:
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
async def _handle_error(
|
|
254
|
+
self,
|
|
255
|
+
update: Optional[Update],
|
|
256
|
+
context: ContextTypes.DEFAULT_TYPE,
|
|
257
|
+
) -> None:
|
|
258
|
+
"""Handle errors."""
|
|
259
|
+
logger.error(f"Telegram bot error: {context.error}")
|
|
260
|
+
|
|
261
|
+
if update and update.effective_message:
|
|
262
|
+
try:
|
|
263
|
+
await update.effective_message.reply_text(
|
|
264
|
+
"❌ An unexpected error occurred."
|
|
265
|
+
)
|
|
266
|
+
except Exception:
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
def __repr__(self) -> str:
|
|
270
|
+
status = "running" if self._is_running else "stopped"
|
|
271
|
+
restricted = f", restricted={len(self.allowed_users)} users" if self.allowed_users else ""
|
|
272
|
+
return f"TelegramChannel({status}{restricted})"
|
lollmsbot/cli.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
lollmsBot CLI - Gateway + Wizard + UI
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.text import Text
|
|
16
|
+
from rich import box
|
|
17
|
+
console = Console()
|
|
18
|
+
except ImportError:
|
|
19
|
+
print("Install dev deps: pip install -e .[dev]")
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def print_ui_banner() -> None:
|
|
24
|
+
"""Print beautiful UI launch banner."""
|
|
25
|
+
console.print()
|
|
26
|
+
|
|
27
|
+
# Create ASCII art style banner
|
|
28
|
+
banner = Text()
|
|
29
|
+
banner.append("╭─────────╮\n", style="blue")
|
|
30
|
+
banner.append("│ 🤖 │ ", style="blue")
|
|
31
|
+
banner.append("LollmsBot", style="bold cyan")
|
|
32
|
+
banner.append(" Web UI\n", style="bold blue")
|
|
33
|
+
banner.append("╰─────────╯\n", style="blue")
|
|
34
|
+
|
|
35
|
+
panel = Panel(
|
|
36
|
+
banner,
|
|
37
|
+
box=box.DOUBLE_EDGE,
|
|
38
|
+
border_style="bright_cyan",
|
|
39
|
+
title="[bold]Starting Interface[/bold]",
|
|
40
|
+
subtitle="[dim]Real-time AI Chat[/dim]"
|
|
41
|
+
)
|
|
42
|
+
console.print(panel)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def print_gateway_banner(host: str, port: int, ui_enabled: bool) -> None:
|
|
46
|
+
"""Print gateway startup banner with status."""
|
|
47
|
+
|
|
48
|
+
# For display purposes, use localhost if host is 0.0.0.0 or empty
|
|
49
|
+
# Browsers can't connect to 0.0.0.0, they need localhost/127.0.0.1
|
|
50
|
+
display_host = "localhost" if host in ("0.0.0.0", "") else host
|
|
51
|
+
|
|
52
|
+
# Status indicators
|
|
53
|
+
status_table = Table(
|
|
54
|
+
show_header=False,
|
|
55
|
+
box=box.SIMPLE,
|
|
56
|
+
border_style="blue",
|
|
57
|
+
padding=(0, 2)
|
|
58
|
+
)
|
|
59
|
+
status_table.add_column("Service", style="cyan")
|
|
60
|
+
status_table.add_column("Status", style="green")
|
|
61
|
+
status_table.add_column("URL", style="dim")
|
|
62
|
+
|
|
63
|
+
status_table.add_row(
|
|
64
|
+
"🔌 Gateway API",
|
|
65
|
+
"✅ Active",
|
|
66
|
+
f"http://{display_host}:{port}"
|
|
67
|
+
)
|
|
68
|
+
status_table.add_row(
|
|
69
|
+
"📚 API Docs",
|
|
70
|
+
"✅ Available",
|
|
71
|
+
f"http://{display_host}:{port}/docs"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if ui_enabled:
|
|
75
|
+
status_table.add_row(
|
|
76
|
+
"🌐 Web UI",
|
|
77
|
+
"✅ Mounted",
|
|
78
|
+
f"http://{display_host}:{port}/ui"
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
status_table.add_row(
|
|
82
|
+
"🌐 Web UI",
|
|
83
|
+
"⭕ Disabled",
|
|
84
|
+
"Use --ui to enable"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
panel = Panel(
|
|
88
|
+
status_table,
|
|
89
|
+
box=box.ROUNDED,
|
|
90
|
+
border_style="bright_green" if ui_enabled else "yellow",
|
|
91
|
+
title="[bold bright_green]🚀 Gateway Starting[/bold bright_green]",
|
|
92
|
+
subtitle=f"[dim]LoLLMS Agentic Bot | Host: {host}[/dim]"
|
|
93
|
+
)
|
|
94
|
+
console.print()
|
|
95
|
+
console.print(panel)
|
|
96
|
+
console.print()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main(argv: List[str] | None = None) -> None:
|
|
100
|
+
parser = argparse.ArgumentParser(
|
|
101
|
+
prog="lollmsbot",
|
|
102
|
+
description="Agentic LoLLMS Assistant (Clawdbot-style)",
|
|
103
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
104
|
+
epilog="""
|
|
105
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
106
|
+
│ Examples: │
|
|
107
|
+
│ lollmsbot wizard # Interactive setup │
|
|
108
|
+
│ lollmsbot gateway # Run API server │
|
|
109
|
+
│ lollmsbot gateway --ui # API + Web UI together │
|
|
110
|
+
│ lollmsbot ui # Web UI only (standalone) │
|
|
111
|
+
│ lollmsbot ui --port 3000 # UI on custom port │
|
|
112
|
+
└─────────────────────────────────────────────────────────────┘
|
|
113
|
+
"""
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument("--version", action="version", version="lollmsBot 0.1.0")
|
|
116
|
+
|
|
117
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
118
|
+
|
|
119
|
+
# Gateway command
|
|
120
|
+
gateway_parser = subparsers.add_parser(
|
|
121
|
+
"gateway",
|
|
122
|
+
help="Run API gateway server",
|
|
123
|
+
description="Start the main API gateway with optional channels and UI"
|
|
124
|
+
)
|
|
125
|
+
gateway_parser.add_argument("--host", type=str, default="0.0.0.0", help="Bind address (default: 0.0.0.0)")
|
|
126
|
+
gateway_parser.add_argument("--port", type=int, default=8800, help="Port number (default: 8800)")
|
|
127
|
+
gateway_parser.add_argument("--ui", action="store_true", help="Also start web UI at /ui")
|
|
128
|
+
|
|
129
|
+
# UI command (standalone)
|
|
130
|
+
ui_parser = subparsers.add_parser(
|
|
131
|
+
"ui",
|
|
132
|
+
help="Run web UI only (standalone mode)",
|
|
133
|
+
description="Start just the web interface without the full gateway"
|
|
134
|
+
)
|
|
135
|
+
ui_parser.add_argument("--host", type=str, default="127.0.0.1", help="Bind address (default: 127.0.0.1)")
|
|
136
|
+
ui_parser.add_argument("--port", type=int, default=8080, help="Port number (default: 8080)")
|
|
137
|
+
ui_parser.add_argument("--quiet", "-q", action="store_true", help="Minimal console output")
|
|
138
|
+
|
|
139
|
+
# Wizard command
|
|
140
|
+
wizard_parser = subparsers.add_parser(
|
|
141
|
+
"wizard",
|
|
142
|
+
help="Interactive setup wizard",
|
|
143
|
+
description="Configure LoLLMS connection and bot settings interactively"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
args = parser.parse_args(argv)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
if args.command == "gateway":
|
|
150
|
+
import uvicorn
|
|
151
|
+
from lollmsbot.config import GatewaySettings
|
|
152
|
+
from lollmsbot import gateway
|
|
153
|
+
|
|
154
|
+
settings = GatewaySettings.from_env()
|
|
155
|
+
host = args.host or settings.host
|
|
156
|
+
port = args.port or settings.port
|
|
157
|
+
|
|
158
|
+
# Print startup banner
|
|
159
|
+
print_gateway_banner(host, port, args.ui)
|
|
160
|
+
|
|
161
|
+
# Enable UI if requested
|
|
162
|
+
if args.ui:
|
|
163
|
+
# Use localhost for UI server internally, gateway will mount it
|
|
164
|
+
gateway.enable_ui(host="127.0.0.1", port=8080)
|
|
165
|
+
|
|
166
|
+
# Run server
|
|
167
|
+
uvicorn.run(
|
|
168
|
+
"lollmsbot.gateway:app",
|
|
169
|
+
host=host,
|
|
170
|
+
port=port,
|
|
171
|
+
reload=args.host == "127.0.0.1" and not args.ui,
|
|
172
|
+
log_level="info",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
elif args.command == "ui":
|
|
176
|
+
# Run standalone UI with full rich output
|
|
177
|
+
from lollmsbot.ui.app import WebUI
|
|
178
|
+
import uvicorn
|
|
179
|
+
|
|
180
|
+
print_ui_banner()
|
|
181
|
+
|
|
182
|
+
ui = WebUI(verbose=not args.quiet)
|
|
183
|
+
ui.print_server_ready(args.host, args.port)
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
uvicorn.run(
|
|
187
|
+
ui.app,
|
|
188
|
+
host=args.host,
|
|
189
|
+
port=args.port,
|
|
190
|
+
log_level="warning" if args.quiet else "info",
|
|
191
|
+
)
|
|
192
|
+
except KeyboardInterrupt:
|
|
193
|
+
ui._print_shutdown_message()
|
|
194
|
+
|
|
195
|
+
elif args.command == "wizard":
|
|
196
|
+
from lollmsbot import wizard
|
|
197
|
+
wizard.run_wizard()
|
|
198
|
+
|
|
199
|
+
else:
|
|
200
|
+
parser.print_help()
|
|
201
|
+
console.print("\n[bold cyan]💡 Need help? Try: lollmsbot wizard[/]")
|
|
202
|
+
|
|
203
|
+
except KeyboardInterrupt:
|
|
204
|
+
console.print("\n[yellow]👋 Goodbye![/]")
|
|
205
|
+
sys.exit(130)
|
|
206
|
+
except ImportError as e:
|
|
207
|
+
console.print(f"[red]❌ Missing dependency: {e}[/]")
|
|
208
|
+
console.print("[cyan]💡 Run: pip install -e .[dev][/]")
|
|
209
|
+
sys.exit(1)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
console.print(f"[red]💥 Error: {e}[/]")
|
|
212
|
+
console.print_exception(show_locals=True)
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
main()
|
lollmsbot/config.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
load_dotenv()
|
|
11
|
+
|
|
12
|
+
console = None # Forward ref
|
|
13
|
+
|
|
14
|
+
def _get_bool(name: str, default: bool = False) -> bool:
|
|
15
|
+
val = os.getenv(name)
|
|
16
|
+
if val is None:
|
|
17
|
+
return default
|
|
18
|
+
return val.lower() in ("1", "true", "yes", "on")
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class BotConfig:
|
|
22
|
+
"""Bot behavior configuration settings."""
|
|
23
|
+
name: str = field(default="LollmsBot")
|
|
24
|
+
max_history: int = field(default=10)
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_env(cls) -> "BotConfig":
|
|
28
|
+
"""Load from environment variables."""
|
|
29
|
+
return cls(
|
|
30
|
+
name=os.getenv("LOLLMSBOT_NAME", "LollmsBot"),
|
|
31
|
+
max_history=int(os.getenv("LOLLMSBOT_MAX_HISTORY", "10")),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class LollmsSettings:
|
|
36
|
+
"""LoLLMS connection settings."""
|
|
37
|
+
host_address: str = field(default="http://localhost:9600")
|
|
38
|
+
api_key: Optional[str] = field(default=None)
|
|
39
|
+
verify_ssl: bool = field(default=True)
|
|
40
|
+
binding_name: Optional[str] = field(default=None)
|
|
41
|
+
model_name: Optional[str] = field(default=None)
|
|
42
|
+
context_size: Optional[int] = field(default=None)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_env(cls) -> "LollmsSettings":
|
|
46
|
+
"""Load from environment variables."""
|
|
47
|
+
global console
|
|
48
|
+
return cls(
|
|
49
|
+
host_address=os.getenv("LOLLMS_HOST_ADDRESS", "http://localhost:9600"),
|
|
50
|
+
api_key=os.getenv("LOLLMS_API_KEY"),
|
|
51
|
+
verify_ssl=_get_bool("LOLLMS_VERIFY_SSL", True),
|
|
52
|
+
binding_name=os.getenv("LOLLMS_BINDING_NAME"),
|
|
53
|
+
model_name=os.getenv("LOLLMS_MODEL_NAME"),
|
|
54
|
+
context_size=int(os.getenv("LOLLMS_CONTEXT_SIZE", "32000")) or None,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_wizard(cls) -> "LollmsSettings":
|
|
59
|
+
"""Load from wizard config."""
|
|
60
|
+
wizard_path = Path.home() / ".lollmsbot" / "config.json"
|
|
61
|
+
if not wizard_path.exists():
|
|
62
|
+
return cls.from_env()
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
wizard_data = json.loads(wizard_path.read_text())
|
|
66
|
+
lollms_data = wizard_data.get("lollms", {})
|
|
67
|
+
if lollms_data.get("host_address"):
|
|
68
|
+
console.print("[green]📡 Using wizard config![/]" if console else "Using wizard config")
|
|
69
|
+
return cls(
|
|
70
|
+
host_address=lollms_data.get("host_address", "http://localhost:9600"),
|
|
71
|
+
api_key=lollms_data.get("api_key"),
|
|
72
|
+
verify_ssl=_get_bool(str(lollms_data.get("verify_ssl", True))),
|
|
73
|
+
binding_name=lollms_data.get("binding_name"),
|
|
74
|
+
)
|
|
75
|
+
except:
|
|
76
|
+
pass
|
|
77
|
+
return cls.from_env()
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class GatewaySettings:
|
|
81
|
+
"""Gateway server settings."""
|
|
82
|
+
host: str = field(default="localhost")
|
|
83
|
+
port: int = field(default=8800)
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def from_env(cls) -> "GatewaySettings":
|
|
87
|
+
return cls(
|
|
88
|
+
host=os.getenv("LOLLMSBOT_HOST", "localhost"),
|
|
89
|
+
port=int(os.getenv("LOLLMSBOT_PORT", "8800")),
|
|
90
|
+
)
|