Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 032139c14f | |||
| 194d5658a6 |
11
HISTORY.md
11
HISTORY.md
@@ -5,10 +5,21 @@ Changelog
|
||||
(unreleased)
|
||||
------------
|
||||
|
||||
Fix
|
||||
~~~
|
||||
- Better n8n workflow, refs NOISSUE. [Simon Diesenreiter]
|
||||
|
||||
|
||||
0.6.4 (2026-04-10)
|
||||
------------------
|
||||
|
||||
Fix
|
||||
~~~
|
||||
- Add Telegram helper functions, refs NOISSUE. [Simon Diesenreiter]
|
||||
|
||||
Other
|
||||
~~~~~
|
||||
|
||||
|
||||
0.6.3 (2026-04-10)
|
||||
------------------
|
||||
|
||||
10
README.md
10
README.md
@@ -86,11 +86,14 @@ docker-compose up -d
|
||||
1. **Send a request via Telegram:**
|
||||
|
||||
```
|
||||
Name: My Awesome App
|
||||
Description: A web application for managing tasks
|
||||
Features: user authentication, task CRUD, notifications
|
||||
Build an internal task management app for our operations team.
|
||||
It should support user authentication, task CRUD, notifications, and reporting.
|
||||
Prefer FastAPI with PostgreSQL and a simple web dashboard.
|
||||
```
|
||||
|
||||
The backend now interprets free-form Telegram text with Ollama before generation.
|
||||
If `TELEGRAM_CHAT_ID` is set, the Telegram-trigger workflow only reacts to messages from that specific chat.
|
||||
|
||||
2. **Monitor progress via Web UI:**
|
||||
|
||||
Open `http://yourserver:8000/` to see the dashboard and `http://yourserver:8000/api` for API metadata
|
||||
@@ -109,6 +112,7 @@ If you deploy the container with PostgreSQL environment variables set, the servi
|
||||
| `/api` | GET | API information |
|
||||
| `/health` | GET | Health check |
|
||||
| `/generate` | POST | Generate new software |
|
||||
| `/generate/text` | POST | Interpret free-form text and generate software |
|
||||
| `/status/{project_id}` | GET | Get project status |
|
||||
| `/projects` | GET | List all projects |
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.4
|
||||
0.6.5
|
||||
|
||||
@@ -275,9 +275,10 @@ class N8NSetupAgent:
|
||||
return value
|
||||
return []
|
||||
|
||||
def build_telegram_workflow(self, webhook_path: str, backend_url: str) -> dict:
|
||||
def build_telegram_workflow(self, webhook_path: str, backend_url: str, allowed_chat_id: str | None = None) -> dict:
|
||||
"""Build the Telegram-to-backend workflow definition."""
|
||||
normalized_path = webhook_path.strip().strip("/") or "telegram"
|
||||
allowed_chat = json.dumps(str(allowed_chat_id)) if allowed_chat_id else "''"
|
||||
return {
|
||||
"name": "Telegram to AI Software Factory",
|
||||
"settings": {"executionOrder": "v1"},
|
||||
@@ -297,13 +298,13 @@ class N8NSetupAgent:
|
||||
},
|
||||
{
|
||||
"id": "parse-node",
|
||||
"name": "Prepare Software Request",
|
||||
"name": "Prepare Freeform Request",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [-200, 120],
|
||||
"parameters": {
|
||||
"language": "javaScript",
|
||||
"jsCode": "const body = $json.body ?? $json;\nconst message = body.message ?? body;\nconst text = String(message.text ?? '').trim();\nconst lines = text.split(/\\r?\\n/);\nconst request = { name: null, description: '', features: [], tech_stack: [] };\nlet nameIndex = -1;\nlet featuresIndex = -1;\nlet techIndex = -1;\nfor (let i = 0; i < lines.length; i += 1) {\n const line = lines[i].trim();\n if (line.toLowerCase().startsWith('name:')) { request.name = line.split(':', 2)[1]?.trim() || null; nameIndex = i; }\n if (line.toLowerCase().startsWith('features:') && featuresIndex === -1) { featuresIndex = i; }\n if (line.toLowerCase().startsWith('tech stack:') && techIndex === -1) { techIndex = i; }\n}\nif (nameIndex >= 0) {\n const descriptionEnd = featuresIndex >= 0 ? featuresIndex : (techIndex >= 0 ? techIndex : lines.length);\n request.description = lines.slice(nameIndex + 1, descriptionEnd).join('\\n').replace(/^description:\\s*/i, '').trim();\n}\nfunction collectList(startIndex, fieldName) {\n if (startIndex < 0) return;\n const firstLine = lines[startIndex].split(':').slice(1).join(':').trim();\n if (firstLine && !firstLine.startsWith('-') && !firstLine.startsWith('*')) {\n request[fieldName].push(...firstLine.split(',').map(item => item.trim()).filter(Boolean));\n }\n for (const rawLine of lines.slice(startIndex + 1)) {\n const line = rawLine.trim();\n if (!line) continue;\n if (/^[A-Za-z ]+:/.test(line)) break;\n if (line.startsWith('-') || line.startsWith('*')) {\n const value = line.slice(1).trim();\n if (value) request[fieldName].push(value);\n }\n }\n}\ncollectList(featuresIndex, 'features');\ncollectList(techIndex, 'tech_stack');\nif (!request.name || request.features.length === 0) { throw new Error('Could not parse software request from Telegram message'); }\nreturn [{ json: { ...request, _source: { raw_text: text, chat_id: message.chat?.id ?? null } } }];",
|
||||
"jsCode": f"const allowedChatId = {allowed_chat};\nconst body = $json.body ?? $json;\nconst message = body.message ?? body;\nconst text = String(message.text ?? '').trim();\nconst chatId = String(message.chat?.id ?? '');\nif (allowedChatId && chatId !== allowedChatId) {{\n return [{{ json: {{ ignored: true, message: `Ignoring message from chat ${{chatId}}`, prompt_text: text, source: 'telegram', chat_id: chatId, chat_type: message.chat?.type ?? null }} }}];\n}}\nreturn [{{ json: {{ prompt_text: text, source: 'telegram', chat_id: chatId, chat_type: message.chat?.type ?? null }} }}];",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -334,8 +335,8 @@ class N8NSetupAgent:
|
||||
},
|
||||
],
|
||||
"connections": {
|
||||
"Telegram Webhook": {"main": [[{"node": "Prepare Software Request", "type": "main", "index": 0}]]},
|
||||
"Prepare Software Request": {"main": [[{"node": "AI Software Factory API", "type": "main", "index": 0}]]},
|
||||
"Telegram Webhook": {"main": [[{"node": "Prepare Freeform Request", "type": "main", "index": 0}]]},
|
||||
"Prepare Freeform Request": {"main": [[{"node": "AI Software Factory API", "type": "main", "index": 0}]]},
|
||||
"AI Software Factory API": {"main": [[{"node": "Respond to Telegram Webhook", "type": "main", "index": 0}]]},
|
||||
},
|
||||
}
|
||||
@@ -344,8 +345,10 @@ class N8NSetupAgent:
|
||||
self,
|
||||
backend_url: str,
|
||||
credential_name: str,
|
||||
allowed_chat_id: str | None = None,
|
||||
) -> dict:
|
||||
"""Build a production Telegram Trigger based workflow."""
|
||||
allowed_chat = json.dumps(str(allowed_chat_id)) if allowed_chat_id else "''"
|
||||
return {
|
||||
"name": "Telegram to AI Software Factory",
|
||||
"settings": {"executionOrder": "v1"},
|
||||
@@ -360,14 +363,14 @@ class N8NSetupAgent:
|
||||
"credentials": {"telegramApi": {"name": credential_name}},
|
||||
},
|
||||
{
|
||||
"id": "parse-node",
|
||||
"name": "Prepare Software Request",
|
||||
"id": "filter-node",
|
||||
"name": "Prepare Freeform Request",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [-180, 120],
|
||||
"parameters": {
|
||||
"language": "javaScript",
|
||||
"jsCode": "const message = $json.message ?? $json;\nconst text = String(message.text ?? '').trim();\nconst lines = text.split(/\\r?\\n/);\nconst request = { name: null, description: '', features: [], tech_stack: [], _source: { raw_text: text, chat_id: message.chat?.id ?? null } };\nlet nameIndex = -1;\nlet featuresIndex = -1;\nlet techIndex = -1;\nfor (let i = 0; i < lines.length; i += 1) {\n const line = lines[i].trim();\n if (line.toLowerCase().startsWith('name:')) { request.name = line.split(':', 2)[1]?.trim() || null; nameIndex = i; }\n if (line.toLowerCase().startsWith('features:') && featuresIndex === -1) { featuresIndex = i; }\n if (line.toLowerCase().startsWith('tech stack:') && techIndex === -1) { techIndex = i; }\n}\nif (nameIndex >= 0) {\n const descriptionEnd = featuresIndex >= 0 ? featuresIndex : (techIndex >= 0 ? techIndex : lines.length);\n request.description = lines.slice(nameIndex + 1, descriptionEnd).join('\\n').replace(/^description:\\s*/i, '').trim();\n}\nfunction collectList(startIndex, fieldName) {\n if (startIndex < 0) return;\n const firstLine = lines[startIndex].split(':').slice(1).join(':').trim();\n if (firstLine && !firstLine.startsWith('-') && !firstLine.startsWith('*')) {\n request[fieldName].push(...firstLine.split(',').map(item => item.trim()).filter(Boolean));\n }\n for (const rawLine of lines.slice(startIndex + 1)) {\n const line = rawLine.trim();\n if (!line) continue;\n if (/^[A-Za-z ]+:/.test(line)) break;\n if (line.startsWith('-') || line.startsWith('*')) {\n const value = line.slice(1).trim();\n if (value) request[fieldName].push(value);\n }\n }\n}\ncollectList(featuresIndex, 'features');\ncollectList(techIndex, 'tech_stack');\nif (!request.name || request.features.length === 0) { throw new Error('Could not parse software request from Telegram message'); }\nreturn [{ json: request }];",
|
||||
"jsCode": f"const allowedChatId = {allowed_chat};\nconst message = $json.message ?? $json;\nconst text = String(message.text ?? '').trim();\nconst chatId = String(message.chat?.id ?? '');\nif (!text) return [];\nif (allowedChatId && chatId !== allowedChatId) return [];\nreturn [{{ json: {{ prompt_text: text, source: 'telegram', chat_id: chatId, chat_type: message.chat?.type ?? null }} }}];",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -401,8 +404,8 @@ class N8NSetupAgent:
|
||||
},
|
||||
],
|
||||
"connections": {
|
||||
"Telegram Trigger": {"main": [[{"node": "Prepare Software Request", "type": "main", "index": 0}]]},
|
||||
"Prepare Software Request": {"main": [[{"node": "AI Software Factory API", "type": "main", "index": 0}]]},
|
||||
"Telegram Trigger": {"main": [[{"node": "Prepare Freeform Request", "type": "main", "index": 0}]]},
|
||||
"Prepare Freeform Request": {"main": [[{"node": "AI Software Factory API", "type": "main", "index": 0}]]},
|
||||
"AI Software Factory API": {"main": [[{"node": "Send Telegram Update", "type": "main", "index": 0}]]},
|
||||
},
|
||||
}
|
||||
@@ -456,7 +459,7 @@ class N8NSetupAgent:
|
||||
"""
|
||||
return await self.setup(
|
||||
webhook_path=webhook_path,
|
||||
backend_url=f"{settings.backend_public_url}/generate",
|
||||
backend_url=f"{settings.backend_public_url}/generate/text",
|
||||
force_update=False,
|
||||
)
|
||||
|
||||
@@ -493,7 +496,7 @@ class N8NSetupAgent:
|
||||
"suggestion": health.get("suggestion"),
|
||||
}
|
||||
|
||||
effective_backend_url = backend_url or f"{settings.backend_public_url}/generate"
|
||||
effective_backend_url = backend_url or f"{settings.backend_public_url}/generate/text"
|
||||
effective_bot_token = telegram_bot_token or settings.telegram_bot_token
|
||||
effective_credential_name = telegram_credential_name or settings.n8n_telegram_credential_name
|
||||
trigger_mode = use_telegram_trigger if use_telegram_trigger is not None else bool(effective_bot_token)
|
||||
@@ -505,11 +508,13 @@ class N8NSetupAgent:
|
||||
workflow = self.build_telegram_trigger_workflow(
|
||||
backend_url=effective_backend_url,
|
||||
credential_name=effective_credential_name,
|
||||
allowed_chat_id=settings.telegram_chat_id,
|
||||
)
|
||||
else:
|
||||
workflow = self.build_telegram_workflow(
|
||||
webhook_path=webhook_path,
|
||||
backend_url=effective_backend_url,
|
||||
allowed_chat_id=settings.telegram_chat_id,
|
||||
)
|
||||
|
||||
existing = await self.get_workflow(workflow["name"])
|
||||
|
||||
105
ai_software_factory/agents/request_interpreter.py
Normal file
105
ai_software_factory/agents/request_interpreter.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""Interpret free-form software requests into structured generation input."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
try:
|
||||
from ..config import settings
|
||||
except ImportError:
|
||||
from config import settings
|
||||
|
||||
|
||||
class RequestInterpreter:
|
||||
"""Use Ollama to turn free-form text into a structured software request."""
|
||||
|
||||
def __init__(self, ollama_url: str | None = None, model: str | None = None):
|
||||
self.ollama_url = (ollama_url or settings.ollama_url).rstrip('/')
|
||||
self.model = model or settings.OLLAMA_MODEL
|
||||
|
||||
async def interpret(self, prompt_text: str) -> dict:
|
||||
"""Interpret free-form text into the request shape expected by the orchestrator."""
|
||||
normalized = prompt_text.strip()
|
||||
if not normalized:
|
||||
raise ValueError('Prompt text cannot be empty')
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f'{self.ollama_url}/api/chat',
|
||||
json={
|
||||
'model': self.model,
|
||||
'stream': False,
|
||||
'format': 'json',
|
||||
'messages': [
|
||||
{
|
||||
'role': 'system',
|
||||
'content': (
|
||||
'You extract structured software requests. '
|
||||
'Return only JSON with keys name, description, features, tech_stack. '
|
||||
'name and description must be concise strings. '
|
||||
'features and tech_stack must be arrays of strings. '
|
||||
'Infer missing details from the user request instead of leaving arrays empty when possible.'
|
||||
),
|
||||
},
|
||||
{'role': 'user', 'content': normalized},
|
||||
],
|
||||
},
|
||||
) as resp:
|
||||
payload = await resp.json()
|
||||
if 200 <= resp.status < 300:
|
||||
content = payload.get('message', {}).get('content', '')
|
||||
if content:
|
||||
return self._normalize_interpreted_request(json.loads(content), normalized)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return self._heuristic_fallback(normalized)
|
||||
|
||||
def _normalize_interpreted_request(self, interpreted: dict, original_prompt: str) -> dict:
|
||||
"""Normalize LLM output into the required request shape."""
|
||||
name = str(interpreted.get('name') or '').strip() or self._derive_name(original_prompt)
|
||||
description = str(interpreted.get('description') or '').strip() or original_prompt[:255]
|
||||
features = self._normalize_list(interpreted.get('features'))
|
||||
tech_stack = self._normalize_list(interpreted.get('tech_stack'))
|
||||
if not features:
|
||||
features = ['core workflow based on free-form request']
|
||||
return {
|
||||
'name': name[:255],
|
||||
'description': description[:255],
|
||||
'features': features,
|
||||
'tech_stack': tech_stack,
|
||||
}
|
||||
|
||||
def _normalize_list(self, value) -> list[str]:
|
||||
if isinstance(value, list):
|
||||
return [str(item).strip() for item in value if str(item).strip()]
|
||||
if isinstance(value, str) and value.strip():
|
||||
return [item.strip() for item in value.split(',') if item.strip()]
|
||||
return []
|
||||
|
||||
def _derive_name(self, prompt_text: str) -> str:
|
||||
"""Derive a stable project name when the LLM does not provide one."""
|
||||
first_line = prompt_text.splitlines()[0].strip()
|
||||
cleaned = re.sub(r'[^A-Za-z0-9 ]+', ' ', first_line)
|
||||
words = [word.capitalize() for word in cleaned.split()[:4]]
|
||||
return ' '.join(words) or 'Generated Project'
|
||||
|
||||
def _heuristic_fallback(self, prompt_text: str) -> dict:
|
||||
"""Fallback request extraction when Ollama is unavailable."""
|
||||
lowered = prompt_text.lower()
|
||||
tech_candidates = [
|
||||
'python', 'fastapi', 'django', 'flask', 'postgresql', 'sqlite', 'react', 'vue', 'nicegui', 'docker'
|
||||
]
|
||||
tech_stack = [candidate for candidate in tech_candidates if candidate in lowered]
|
||||
sentences = [part.strip() for part in re.split(r'[\n\.]+', prompt_text) if part.strip()]
|
||||
features = sentences[:3] or ['Implement the user request from free-form text']
|
||||
return {
|
||||
'name': self._derive_name(prompt_text),
|
||||
'description': sentences[0][:255] if sentences else prompt_text[:255],
|
||||
'features': features,
|
||||
'tech_stack': tech_stack,
|
||||
}
|
||||
@@ -16,17 +16,14 @@ class TelegramHandler:
|
||||
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",
|
||||
"You can send free-form software requests in normal language.",
|
||||
"",
|
||||
"Example:",
|
||||
"Build an internal inventory portal for our warehouse team.",
|
||||
"It should support role-based login, stock dashboards, and purchase orders.",
|
||||
"Prefer FastAPI, PostgreSQL, and a simple web UI.",
|
||||
"",
|
||||
"The backend will interpret the request and turn it into a structured project plan.",
|
||||
]
|
||||
if backend_url:
|
||||
lines.extend(["", f"Backend target: {backend_url}"])
|
||||
|
||||
@@ -226,7 +226,7 @@ def create_dashboard():
|
||||
agent = N8NSetupAgent(api_url=api_url, webhook_token=settings.n8n_api_key)
|
||||
result = await agent.setup(
|
||||
webhook_path='telegram',
|
||||
backend_url=f'{settings.backend_public_url}/generate',
|
||||
backend_url=f'{settings.backend_public_url}/generate/text',
|
||||
force_update=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ try:
|
||||
from . import __version__, frontend
|
||||
from . import database as database_module
|
||||
from .agents.database_manager import DatabaseManager
|
||||
from .agents.request_interpreter import RequestInterpreter
|
||||
from .agents.orchestrator import AgentOrchestrator
|
||||
from .agents.n8n_setup import N8NSetupAgent
|
||||
from .agents.ui_manager import UIManager
|
||||
@@ -36,6 +37,7 @@ except ImportError:
|
||||
import frontend
|
||||
import database as database_module
|
||||
from agents.database_manager import DatabaseManager
|
||||
from agents.request_interpreter import RequestInterpreter
|
||||
from agents.orchestrator import AgentOrchestrator
|
||||
from agents.n8n_setup import N8NSetupAgent
|
||||
from agents.ui_manager import UIManager
|
||||
@@ -79,6 +81,15 @@ class N8NSetupRequest(BaseModel):
|
||||
force_update: bool = False
|
||||
|
||||
|
||||
class FreeformSoftwareRequest(BaseModel):
|
||||
"""Request body for free-form software generation."""
|
||||
|
||||
prompt_text: str = Field(min_length=1)
|
||||
source: str = 'telegram'
|
||||
chat_id: str | None = None
|
||||
chat_type: str | None = None
|
||||
|
||||
|
||||
def _build_project_id(name: str) -> str:
|
||||
"""Create a stable project id from the requested name."""
|
||||
slug = PROJECT_ID_PATTERN.sub("-", name.strip().lower()).strip("-") or "project"
|
||||
@@ -144,6 +155,49 @@ def _compose_prompt_text(request: SoftwareRequest) -> str:
|
||||
)
|
||||
|
||||
|
||||
async def _run_generation(
|
||||
request: SoftwareRequest,
|
||||
db: Session,
|
||||
prompt_text: str | None = None,
|
||||
prompt_actor: str = 'api',
|
||||
) -> dict:
|
||||
"""Run the shared generation pipeline for a structured request."""
|
||||
database_module.init_db()
|
||||
|
||||
project_id = _build_project_id(request.name)
|
||||
resolved_prompt_text = prompt_text or _compose_prompt_text(request)
|
||||
orchestrator = AgentOrchestrator(
|
||||
project_id=project_id,
|
||||
project_name=request.name,
|
||||
description=request.description,
|
||||
features=request.features,
|
||||
tech_stack=request.tech_stack,
|
||||
db=db,
|
||||
prompt_text=resolved_prompt_text,
|
||||
prompt_actor=prompt_actor,
|
||||
)
|
||||
result = await orchestrator.run()
|
||||
|
||||
manager = DatabaseManager(db)
|
||||
manager.log_system_event(
|
||||
component='api',
|
||||
level='INFO' if result['status'] == 'completed' else 'ERROR',
|
||||
message=f"Generated project {project_id} with {len(result.get('changed_files', []))} artifact(s)",
|
||||
)
|
||||
|
||||
history = manager.get_project_by_id(project_id)
|
||||
project_logs = manager.get_project_logs(history.id)
|
||||
response_data = _serialize_project(history)
|
||||
response_data['logs'] = [_serialize_project_log(log) for log in project_logs]
|
||||
response_data['ui_data'] = result.get('ui_data')
|
||||
response_data['features'] = request.features
|
||||
response_data['tech_stack'] = request.tech_stack
|
||||
response_data['project_root'] = result.get('project_root', str(_project_root(project_id)))
|
||||
response_data['changed_files'] = result.get('changed_files', [])
|
||||
response_data['repository'] = result.get('repository')
|
||||
return {'status': result['status'], 'data': response_data}
|
||||
|
||||
|
||||
def _project_root(project_id: str) -> Path:
|
||||
"""Resolve the filesystem location for a generated project."""
|
||||
return database_module.settings.projects_root / project_id
|
||||
@@ -172,6 +226,7 @@ def read_api_info():
|
||||
'/api',
|
||||
'/health',
|
||||
'/generate',
|
||||
'/generate/text',
|
||||
'/projects',
|
||||
'/status/{project_id}',
|
||||
'/audit/projects',
|
||||
@@ -202,40 +257,43 @@ def health_check():
|
||||
@app.post('/generate')
|
||||
async def generate_software(request: SoftwareRequest, db: DbSession):
|
||||
"""Create and record a software-generation request."""
|
||||
database_module.init_db()
|
||||
return await _run_generation(request, db)
|
||||
|
||||
project_id = _build_project_id(request.name)
|
||||
prompt_text = _compose_prompt_text(request)
|
||||
orchestrator = AgentOrchestrator(
|
||||
project_id=project_id,
|
||||
project_name=request.name,
|
||||
description=request.description,
|
||||
features=request.features,
|
||||
tech_stack=request.tech_stack,
|
||||
db=db,
|
||||
prompt_text=prompt_text,
|
||||
|
||||
@app.post('/generate/text')
|
||||
async def generate_software_from_text(request: FreeformSoftwareRequest, db: DbSession):
|
||||
"""Interpret a free-form request and run generation."""
|
||||
if (
|
||||
request.source == 'telegram'
|
||||
and database_module.settings.telegram_chat_id
|
||||
and request.chat_id
|
||||
and str(request.chat_id) != str(database_module.settings.telegram_chat_id)
|
||||
):
|
||||
return {
|
||||
'status': 'ignored',
|
||||
'message': f"Ignoring Telegram message from chat {request.chat_id}",
|
||||
'source': {
|
||||
'type': request.source,
|
||||
'chat_id': request.chat_id,
|
||||
'chat_type': request.chat_type,
|
||||
},
|
||||
}
|
||||
|
||||
interpreted = await RequestInterpreter().interpret(request.prompt_text)
|
||||
structured_request = SoftwareRequest(**interpreted)
|
||||
response = await _run_generation(
|
||||
structured_request,
|
||||
db,
|
||||
prompt_text=request.prompt_text,
|
||||
prompt_actor=request.source,
|
||||
)
|
||||
result = await orchestrator.run()
|
||||
|
||||
manager = DatabaseManager(db)
|
||||
manager.log_system_event(
|
||||
component='api',
|
||||
level='INFO' if result['status'] == 'completed' else 'ERROR',
|
||||
message=f"Generated project {project_id} with {len(result.get('changed_files', []))} artifact(s)",
|
||||
)
|
||||
|
||||
history = manager.get_project_by_id(project_id)
|
||||
project_logs = manager.get_project_logs(history.id)
|
||||
response_data = _serialize_project(history)
|
||||
response_data['logs'] = [_serialize_project_log(log) for log in project_logs]
|
||||
response_data['ui_data'] = result.get('ui_data')
|
||||
response_data['features'] = request.features
|
||||
response_data['tech_stack'] = request.tech_stack
|
||||
response_data['project_root'] = result.get('project_root', str(_project_root(project_id)))
|
||||
response_data['changed_files'] = result.get('changed_files', [])
|
||||
response_data['repository'] = result.get('repository')
|
||||
|
||||
return {'status': result['status'], 'data': response_data}
|
||||
response['interpreted_request'] = interpreted
|
||||
response['source'] = {
|
||||
'type': request.source,
|
||||
'chat_id': request.chat_id,
|
||||
'chat_type': request.chat_type,
|
||||
}
|
||||
return response
|
||||
|
||||
|
||||
@app.get('/projects')
|
||||
@@ -348,7 +406,7 @@ async def setup_n8n_workflow(request: N8NSetupRequest, db: DbSession):
|
||||
)
|
||||
result = await agent.setup(
|
||||
webhook_path=request.webhook_path,
|
||||
backend_url=request.backend_url or f"{database_module.settings.backend_public_url}/generate",
|
||||
backend_url=request.backend_url or f"{database_module.settings.backend_public_url}/generate/text",
|
||||
force_update=request.force_update,
|
||||
telegram_bot_token=database_module.settings.telegram_bot_token,
|
||||
telegram_credential_name=database_module.settings.n8n_telegram_credential_name,
|
||||
|
||||
Reference in New Issue
Block a user