From af4247e6574f3f6fa92781a387803d1f8af01bf5 Mon Sep 17 00:00:00 2001 From: Simon Diesenreiter Date: Fri, 10 Apr 2026 20:27:08 +0200 Subject: [PATCH] feat(dashboard): expose repository urls refs NOISSUE --- .../agents/database_manager.py | 30 ++++++++++++++ ai_software_factory/dashboard_ui.py | 40 ++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/ai_software_factory/agents/database_manager.py b/ai_software_factory/agents/database_manager.py index 9385e18..88b062a 100644 --- a/ai_software_factory/agents/database_manager.py +++ b/ai_software_factory/agents/database_manager.py @@ -4,6 +4,7 @@ from sqlalchemy.orm import Session from sqlalchemy import text try: + from ..config import settings from ..models import ( AuditTrail, ProjectHistory, @@ -17,6 +18,7 @@ try: UserAction, ) except ImportError: + from config import settings from models import ( AuditTrail, ProjectHistory, @@ -307,6 +309,31 @@ class DatabaseManager: self.db.refresh(pr) return pr + def _get_latest_ui_snapshot_data(self, history_id: int) -> dict: + """Return the latest stored UI snapshot payload for a project.""" + snapshot = self.db.query(UISnapshot).filter( + UISnapshot.history_id == history_id + ).order_by(UISnapshot.created_at.desc(), UISnapshot.id.desc()).first() + if not snapshot: + return {} + return self._normalize_metadata(snapshot.snapshot_data) + + def _get_project_repository(self, history: ProjectHistory) -> dict | None: + """Resolve repository metadata for a project.""" + snapshot_data = self._get_latest_ui_snapshot_data(history.id) + repository = snapshot_data.get("repository") + if isinstance(repository, dict) and any(repository.values()): + return repository + + if settings.gitea_owner and settings.gitea_repo and settings.gitea_url: + return { + "owner": settings.gitea_owner, + "name": settings.gitea_repo, + "url": f"{settings.gitea_url.rstrip('/')}/{settings.gitea_owner}/{settings.gitea_repo}", + "mode": "shared", + } + return None + def get_project_by_id(self, project_id: str) -> ProjectHistory | None: """Get project by ID.""" return self.db.query(ProjectHistory).filter(ProjectHistory.project_id == project_id).first() @@ -708,6 +735,7 @@ class DatabaseManager: prompts = self.get_prompt_events(project_id=project_id) code_changes = self.get_code_changes(project_id=project_id) correlations = self.get_prompt_change_correlations(project_id=project_id) + repository = self._get_project_repository(history) return { "project": { @@ -720,6 +748,7 @@ class DatabaseManager: "message": history.message, "error_message": history.error_message, "current_step": history.current_step, + "repository": repository, "completed_at": history.completed_at.isoformat() if history.completed_at else None, "created_at": history.started_at.isoformat() if history.started_at else None }, @@ -759,6 +788,7 @@ class DatabaseManager: "prompts": prompts, "code_changes": code_changes, "prompt_change_correlations": correlations, + "repository": repository, } def get_prompt_events(self, project_id: str | None = None, limit: int = 100) -> list[dict]: diff --git a/ai_software_factory/dashboard_ui.py b/ai_software_factory/dashboard_ui.py index 72b0210..930228d 100644 --- a/ai_software_factory/dashboard_ui.py +++ b/ai_software_factory/dashboard_ui.py @@ -27,6 +27,30 @@ def _resolve_n8n_api_url() -> str: return '' +def _render_repository_block(repository: dict | None) -> None: + """Render repository details and URL when available.""" + if not repository: + ui.label('Repository URL not available yet.').classes('factory-muted') + return + + owner = repository.get('owner') or 'unknown-owner' + name = repository.get('name') or 'unknown-repo' + mode = repository.get('mode') or 'project' + status = repository.get('status') + repo_url = repository.get('url') + + with ui.column().classes('gap-1'): + with ui.row().classes('items-center gap-2'): + ui.label(f'{owner}/{name}').style('font-weight: 700; color: #2f241d;') + ui.label(mode).classes('factory-chip') + if status: + ui.label(status).classes('factory-chip') + if repo_url: + ui.link(repo_url, repo_url, new_tab=True).classes('factory-code') + else: + ui.label('Repository URL not available yet.').classes('factory-muted') + + def _load_dashboard_snapshot() -> dict: """Load dashboard data from the database.""" db = get_db_sync() @@ -101,6 +125,14 @@ def create_dashboard(): projects = snapshot['projects'] correlations = snapshot['correlations'] system_logs = snapshot['system_logs'] + project_repository_map = { + project_bundle['project']['project_id']: { + 'project_name': project_bundle['project']['project_name'], + 'repository': project_bundle.get('repository') or project_bundle['project'].get('repository'), + } + for project_bundle in projects + if project_bundle.get('project') + } with ui.column().classes('factory-shell w-full gap-4 q-pa-lg'): with ui.card().classes('factory-panel w-full q-pa-lg'): @@ -171,6 +203,10 @@ def create_dashboard(): project = project_bundle['project'] with ui.expansion(f"{project['project_name']} ยท {project['status']}", icon='folder').classes('factory-panel w-full q-mb-md'): with ui.grid(columns=2).classes('w-full gap-4 q-pa-md'): + with ui.card().classes('q-pa-md'): + ui.label('Repository').style('font-weight: 700; color: #3a281a;') + _render_repository_block(project_bundle.get('repository') or project.get('repository')) + with ui.card().classes('q-pa-md'): ui.label('Prompt').style('font-weight: 700; color: #3a281a;') prompts = project_bundle.get('prompts', []) @@ -219,8 +255,10 @@ def create_dashboard(): ui.label('Each prompt entry is linked to the generated files recorded after that prompt for the same project.').classes('factory-muted') if correlations: for correlation in correlations: + correlation_project = project_repository_map.get(correlation['project_id'], {}) with ui.card().classes('q-pa-md q-mt-md'): - ui.label(correlation['project_id']).style('font-size: 1rem; font-weight: 700; color: #2f241d;') + ui.label(correlation_project.get('project_name') or correlation['project_id']).style('font-size: 1rem; font-weight: 700; color: #2f241d;') + _render_repository_block(correlation_project.get('repository')) ui.label(correlation['prompt_text']).classes('factory-code q-mt-sm') if correlation['changes']: for change in correlation['changes']: