Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b9faac8d16 | |||
| 80d7716e65 |
11
HISTORY.md
11
HISTORY.md
@@ -5,10 +5,21 @@ Changelog
|
||||
(unreleased)
|
||||
------------
|
||||
|
||||
Fix
|
||||
~~~
|
||||
- Add Telegram helper functions, refs NOISSUE. [Simon Diesenreiter]
|
||||
|
||||
|
||||
0.6.3 (2026-04-10)
|
||||
------------------
|
||||
|
||||
Fix
|
||||
~~~
|
||||
- N8n workflow generation, refs NOISSUE. [Simon Diesenreiter]
|
||||
|
||||
Other
|
||||
~~~~~
|
||||
|
||||
|
||||
0.6.2 (2026-04-10)
|
||||
------------------
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.3
|
||||
0.6.4
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Telegram bot integration for n8n webhook."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@@ -13,6 +11,62 @@ class TelegramHandler:
|
||||
self.webhook_url = webhook_url
|
||||
self.api_url = "https://api.telegram.org/bot"
|
||||
|
||||
def build_prompt_guide_message(self, backend_url: str | None = None) -> str:
|
||||
"""Build a Telegram message explaining the expected prompt format."""
|
||||
lines = [
|
||||
"AI Software Factory is listening in this chat.",
|
||||
"",
|
||||
"Send prompts in this format:",
|
||||
"Name: Inventory Portal",
|
||||
"Description: Internal tool for stock management and purchase tracking",
|
||||
"Features:",
|
||||
"- role-based login",
|
||||
"- inventory dashboard",
|
||||
"- purchase order workflow",
|
||||
"Tech Stack:",
|
||||
"- fastapi",
|
||||
"- postgresql",
|
||||
"- nicegui",
|
||||
]
|
||||
if backend_url:
|
||||
lines.extend(["", f"Backend target: {backend_url}"])
|
||||
return "\n".join(lines)
|
||||
|
||||
async def send_message(self, bot_token: str, chat_id: str | int, text: str) -> dict:
|
||||
"""Send a direct Telegram message using the configured bot."""
|
||||
if not bot_token:
|
||||
return {"status": "error", "message": "Telegram bot token is not configured"}
|
||||
if chat_id in (None, ""):
|
||||
return {"status": "error", "message": "Telegram chat id is not configured"}
|
||||
|
||||
api_endpoint = f"{self.api_url}{bot_token}/sendMessage"
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
api_endpoint,
|
||||
json={
|
||||
"chat_id": str(chat_id),
|
||||
"text": text,
|
||||
},
|
||||
) as resp:
|
||||
payload = await resp.json()
|
||||
if 200 <= resp.status < 300 and payload.get("ok"):
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Telegram prompt guide sent successfully",
|
||||
"payload": payload,
|
||||
}
|
||||
description = payload.get("description") or payload.get("message") or str(payload)
|
||||
return {
|
||||
"status": "error",
|
||||
"message": f"Telegram API returned {resp.status}: {description}",
|
||||
"payload": payload,
|
||||
}
|
||||
except Exception as exc:
|
||||
return {"status": "error", "message": str(exc)}
|
||||
|
||||
async def handle_message(self, message_data: dict) -> dict:
|
||||
"""Handle incoming Telegram message."""
|
||||
text = message_data.get("text", "")
|
||||
|
||||
@@ -5,16 +5,18 @@ from __future__ import annotations
|
||||
from contextlib import closing
|
||||
from html import escape
|
||||
|
||||
from nicegui import ui
|
||||
from nicegui import app, ui
|
||||
|
||||
try:
|
||||
from .agents.database_manager import DatabaseManager
|
||||
from .agents.n8n_setup import N8NSetupAgent
|
||||
from .agents.telegram import TelegramHandler
|
||||
from .config import settings
|
||||
from .database import get_database_runtime_summary, get_db_sync, init_db
|
||||
except ImportError:
|
||||
from agents.database_manager import DatabaseManager
|
||||
from agents.n8n_setup import N8NSetupAgent
|
||||
from agents.telegram import TelegramHandler
|
||||
from config import settings
|
||||
from database import get_database_runtime_summary, get_db_sync, init_db
|
||||
|
||||
@@ -165,6 +167,7 @@ def _render_health_panels() -> None:
|
||||
('API URL', n8n_health.get('api_url') or 'Not configured'),
|
||||
('Auth Configured', 'yes' if n8n_health.get('auth_configured') else 'no'),
|
||||
('Checked Via', n8n_health.get('checked_via') or 'none'),
|
||||
('Telegram Chat ID', settings.telegram_chat_id or 'Not configured'),
|
||||
]
|
||||
if n8n_health.get('workflow_count') is not None:
|
||||
rows.append(('Workflow Count', str(n8n_health['workflow_count'])))
|
||||
@@ -204,6 +207,15 @@ def create_health_page() -> None:
|
||||
def create_dashboard():
|
||||
"""Create the main NiceGUI dashboard."""
|
||||
_add_dashboard_styles()
|
||||
active_tab_key = 'dashboard.active_tab'
|
||||
|
||||
def _selected_tab_name() -> str:
|
||||
"""Return the persisted active dashboard tab."""
|
||||
return app.storage.user.get(active_tab_key, 'overview')
|
||||
|
||||
def _store_selected_tab(event) -> None:
|
||||
"""Persist the active dashboard tab across refreshes."""
|
||||
app.storage.user[active_tab_key] = event.value or 'overview'
|
||||
|
||||
async def setup_n8n_workflow_action() -> None:
|
||||
api_url = _resolve_n8n_api_url()
|
||||
@@ -232,6 +244,34 @@ def create_dashboard():
|
||||
ui.notify(result.get('message', 'n8n setup finished'), color='positive' if result.get('status') == 'success' else 'negative')
|
||||
dashboard_body.refresh()
|
||||
|
||||
async def send_telegram_prompt_guide_action() -> None:
|
||||
if not settings.telegram_bot_token:
|
||||
ui.notify('Configure TELEGRAM_BOT_TOKEN first', color='negative')
|
||||
return
|
||||
if not settings.telegram_chat_id:
|
||||
ui.notify('Configure TELEGRAM_CHAT_ID to message the prompt channel', color='negative')
|
||||
return
|
||||
|
||||
handler = TelegramHandler(settings.n8n_webhook_url or _resolve_n8n_api_url())
|
||||
message = handler.build_prompt_guide_message(settings.backend_public_url)
|
||||
result = await handler.send_message(
|
||||
bot_token=settings.telegram_bot_token,
|
||||
chat_id=settings.telegram_chat_id,
|
||||
text=message,
|
||||
)
|
||||
|
||||
db = get_db_sync()
|
||||
if db is not None:
|
||||
with closing(db):
|
||||
DatabaseManager(db).log_system_event(
|
||||
component='telegram',
|
||||
level='INFO' if result.get('status') == 'success' else 'ERROR',
|
||||
message=result.get('message', str(result)),
|
||||
)
|
||||
|
||||
ui.notify(result.get('message', 'Telegram message sent'), color='positive' if result.get('status') == 'success' else 'negative')
|
||||
dashboard_body.refresh()
|
||||
|
||||
def init_db_action() -> None:
|
||||
result = init_db()
|
||||
ui.notify(result.get('message', 'Database initialized'), color='positive' if result.get('status') == 'success' else 'negative')
|
||||
@@ -270,6 +310,7 @@ def create_dashboard():
|
||||
ui.button('Refresh', on_click=dashboard_body.refresh).props('outline')
|
||||
ui.button('Initialize DB', on_click=init_db_action).props('unelevated color=dark')
|
||||
ui.button('Provision n8n Workflow', on_click=setup_n8n_workflow_action).props('unelevated color=accent')
|
||||
ui.button('Message Prompt Channel', on_click=send_telegram_prompt_guide_action).props('outline color=secondary')
|
||||
|
||||
with ui.grid(columns=4).classes('w-full gap-4'):
|
||||
metrics = [
|
||||
@@ -284,15 +325,16 @@ def create_dashboard():
|
||||
ui.label(str(value)).style('font-size: 2.1rem; font-weight: 800; margin-top: 6px;')
|
||||
ui.label(subtitle).style('font-size: 0.9rem; opacity: 0.78; margin-top: 8px;')
|
||||
|
||||
with ui.tabs().classes('w-full') as tabs:
|
||||
overview_tab = ui.tab('Overview')
|
||||
projects_tab = ui.tab('Projects')
|
||||
trace_tab = ui.tab('Prompt Trace')
|
||||
system_tab = ui.tab('System')
|
||||
health_tab = ui.tab('Health')
|
||||
selected_tab = _selected_tab_name()
|
||||
with ui.tabs(value=selected_tab, on_change=_store_selected_tab).classes('w-full') as tabs:
|
||||
ui.tab('Overview').props('name=overview')
|
||||
ui.tab('Projects').props('name=projects')
|
||||
ui.tab('Prompt Trace').props('name=trace')
|
||||
ui.tab('System').props('name=system')
|
||||
ui.tab('Health').props('name=health')
|
||||
|
||||
with ui.tab_panels(tabs, value=overview_tab).classes('w-full'):
|
||||
with ui.tab_panel(overview_tab):
|
||||
with ui.tab_panels(tabs, value=selected_tab).classes('w-full'):
|
||||
with ui.tab_panel('overview'):
|
||||
with ui.grid(columns=2).classes('w-full gap-4'):
|
||||
with ui.card().classes('factory-panel q-pa-lg'):
|
||||
ui.label('Project Pipeline').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
|
||||
@@ -322,7 +364,7 @@ def create_dashboard():
|
||||
ui.label(label).classes('factory-muted')
|
||||
ui.label(value).style('font-weight: 600; color: #3a281a;')
|
||||
|
||||
with ui.tab_panel(projects_tab):
|
||||
with ui.tab_panel('projects'):
|
||||
if not projects:
|
||||
with ui.card().classes('factory-panel q-pa-lg'):
|
||||
ui.label('No project data available yet.').classes('factory-muted')
|
||||
@@ -376,7 +418,7 @@ def create_dashboard():
|
||||
else:
|
||||
ui.label('No audit events yet.').classes('factory-muted')
|
||||
|
||||
with ui.tab_panel(trace_tab):
|
||||
with ui.tab_panel('trace'):
|
||||
with ui.card().classes('factory-panel q-pa-lg'):
|
||||
ui.label('Prompt to Code Correlation').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
|
||||
ui.label('Each prompt entry is linked to the generated files recorded after that prompt for the same project.').classes('factory-muted')
|
||||
@@ -397,7 +439,7 @@ def create_dashboard():
|
||||
else:
|
||||
ui.label('No prompt traces recorded yet.').classes('factory-muted')
|
||||
|
||||
with ui.tab_panel(system_tab):
|
||||
with ui.tab_panel('system'):
|
||||
with ui.grid(columns=2).classes('w-full gap-4'):
|
||||
with ui.card().classes('factory-panel q-pa-lg'):
|
||||
ui.label('System Logs').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
|
||||
@@ -423,11 +465,19 @@ def create_dashboard():
|
||||
for endpoint in endpoints:
|
||||
ui.label(endpoint).classes('factory-code q-mt-sm')
|
||||
|
||||
with ui.tab_panel(health_tab):
|
||||
with ui.tab_panel('health'):
|
||||
with ui.card().classes('factory-panel q-pa-lg q-mb-md'):
|
||||
ui.label('Health and Diagnostics').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
|
||||
ui.label('Use this page to verify runtime configuration, n8n API connectivity, and likely causes of provisioning failures.').classes('factory-muted')
|
||||
ui.link('Open dedicated health page', '/health-ui')
|
||||
with ui.card().classes('factory-panel q-pa-lg q-mb-md'):
|
||||
ui.label('Telegram Prompt Channel').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
|
||||
ui.label('Send a guide message into the same Telegram chat/channel where the bot is expected to receive prompts.').classes('factory-muted')
|
||||
with ui.row().classes('justify-between w-full q-mt-sm'):
|
||||
ui.label('Configured Chat ID').classes('factory-muted')
|
||||
ui.label(settings.telegram_chat_id or 'Not configured').style('font-weight: 600; color: #3a281a;')
|
||||
with ui.row().classes('items-center gap-2 q-mt-md'):
|
||||
ui.button('Send Prompt Guide', on_click=send_telegram_prompt_guide_action).props('unelevated color=secondary')
|
||||
_render_health_panels()
|
||||
|
||||
dashboard_body()
|
||||
|
||||
Reference in New Issue
Block a user