mirror of
https://github.com/Pythagora-io/gpt-pilot.git
synced 2026-01-08 12:53:50 -05:00
Add wizard agent and project type str to Project
This commit is contained in:
136
config-extension.json
Normal file
136
config-extension.json
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"llm": {
|
||||
"openai": {
|
||||
"base_url": null,
|
||||
"api_key": null,
|
||||
"connect_timeout": 60.0,
|
||||
"read_timeout": 60.0,
|
||||
"extra": null
|
||||
},
|
||||
"anthropic": {
|
||||
"base_url": null,
|
||||
"api_key": null,
|
||||
"connect_timeout": 60.0,
|
||||
"read_timeout": 60.0,
|
||||
"extra": null
|
||||
}
|
||||
},
|
||||
"agent": {
|
||||
"default": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-2024-05-13",
|
||||
"temperature": 0.5
|
||||
},
|
||||
"BugHunter.check_logs": {
|
||||
"provider": "openai",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"temperature": 0.5
|
||||
},
|
||||
"CodeMonkey": {
|
||||
"provider": "openai",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"temperature": 0.0
|
||||
},
|
||||
"CodeMonkey.code_review": {
|
||||
"provider": "openai",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"temperature": 0.0
|
||||
},
|
||||
"CodeMonkey.describe_files": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini-2024-07-18",
|
||||
"temperature": 0.0
|
||||
},
|
||||
"Frontend": {
|
||||
"provider": "openai",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"temperature": 0.0
|
||||
},
|
||||
"get_relevant_files": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-2024-05-13",
|
||||
"temperature": 0.5
|
||||
},
|
||||
"Developer.parse_task": {
|
||||
"provider": "openai",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"temperature": 0.0
|
||||
},
|
||||
"SpecWriter": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4-0125-preview",
|
||||
"temperature": 0.0
|
||||
},
|
||||
"Developer.breakdown_current_task": {
|
||||
"provider": "openai",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"temperature": 0.5
|
||||
},
|
||||
"TechLead.plan_epic": {
|
||||
"provider": "openai",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"temperature": 0.5
|
||||
},
|
||||
"TechLead.epic_breakdown": {
|
||||
"provider": "openai",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"temperature": 0.5
|
||||
},
|
||||
"Troubleshooter.generate_bug_report": {
|
||||
"provider": "openai",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"temperature": 0.5
|
||||
},
|
||||
"Troubleshooter.get_run_command": {
|
||||
"provider": "openai",
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"temperature": 0.0
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
"paths": [
|
||||
"/Users/sven/projects/pythagora/pythagora-core/core/prompts"
|
||||
]
|
||||
},
|
||||
"log": {
|
||||
"level": "DEBUG",
|
||||
"format": "%(asctime)s %(levelname)s [%(name)s] %(message)s",
|
||||
"output": "pythagora.log"
|
||||
},
|
||||
"db": {
|
||||
"url": "sqlite+aiosqlite:///data/database/pythagora.db",
|
||||
"debug_sql": false
|
||||
},
|
||||
"ui": {
|
||||
"type": "plain"
|
||||
},
|
||||
"fs": {
|
||||
"type": "local",
|
||||
"workspace_root": "/Users/sven/projects/pythagora/pythagora-core/workspace",
|
||||
"ignore_paths": [
|
||||
".git",
|
||||
".gpt-pilot",
|
||||
".idea",
|
||||
".vscode",
|
||||
".next",
|
||||
".DS_Store",
|
||||
"__pycache__",
|
||||
"site-packages",
|
||||
"node_modules",
|
||||
"package-lock.json",
|
||||
"venv",
|
||||
".venv",
|
||||
"dist",
|
||||
"build",
|
||||
"target",
|
||||
"*.min.js",
|
||||
"*.min.css",
|
||||
"*.svg",
|
||||
"*.csv",
|
||||
"*.log",
|
||||
"go.sum",
|
||||
"migration_lock.toml"
|
||||
],
|
||||
"ignore_size_threshold": 50000
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
import asyncio
|
||||
import secrets
|
||||
from uuid import uuid4
|
||||
|
||||
from core.agents.base import BaseAgent
|
||||
from core.agents.convo import AgentConvo
|
||||
from core.agents.git import GitMixin
|
||||
@@ -11,7 +7,6 @@ from core.config import FRONTEND_AGENT_NAME
|
||||
from core.llm.parser import DescriptiveCodeBlockParser
|
||||
from core.log import get_logger
|
||||
from core.telemetry import telemetry
|
||||
from core.templates.registry import PROJECT_TEMPLATES
|
||||
from core.ui.base import ProjectStage
|
||||
|
||||
log = get_logger(__name__)
|
||||
@@ -28,9 +23,7 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
|
||||
display_name = "Frontend"
|
||||
|
||||
async def run(self) -> AgentResponse:
|
||||
if not self.current_state.epics:
|
||||
finished = await self.init_frontend()
|
||||
elif not self.current_state.epics[0]["messages"]:
|
||||
if not self.current_state.epics[0]["messages"]:
|
||||
finished = await self.start_frontend()
|
||||
elif not self.next_state.epics[-1].get("fe_iteration_done"):
|
||||
finished = await self.continue_frontend()
|
||||
@@ -40,63 +33,6 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
|
||||
|
||||
return await self.end_frontend_iteration(finished)
|
||||
|
||||
async def init_frontend(self) -> bool:
|
||||
"""
|
||||
Builds frontend of the app.
|
||||
|
||||
:return: AgentResponse.done(self)
|
||||
"""
|
||||
self.next_state.action = FE_INIT
|
||||
await self.ui.send_project_stage({"stage": ProjectStage.PROJECT_DESCRIPTION})
|
||||
|
||||
auth_needed = await self.ask_question(
|
||||
"Do you need authentication in your app (login, register, etc.)?",
|
||||
buttons={
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
},
|
||||
buttons_only=True,
|
||||
default="no",
|
||||
)
|
||||
|
||||
self.state_manager.template = {}
|
||||
options = {
|
||||
"auth": auth_needed.button == "yes",
|
||||
"jwt_secret": secrets.token_hex(32),
|
||||
"refresh_token_secret": secrets.token_hex(32),
|
||||
}
|
||||
self.state_manager.template["options"] = options
|
||||
|
||||
if not self.state_manager.async_tasks:
|
||||
self.state_manager.async_tasks = []
|
||||
self.state_manager.async_tasks.append(asyncio.create_task(self.apply_template(options)))
|
||||
|
||||
self.next_state.knowledge_base["user_options"] = options
|
||||
self.state_manager.user_options = options
|
||||
|
||||
description = await self.ask_question(
|
||||
"Please describe the app you want to build.",
|
||||
allow_empty=False,
|
||||
full_screen=True,
|
||||
)
|
||||
description = description.text.strip()
|
||||
self.state_manager.template["description"] = description
|
||||
self.next_state.specification.description = description
|
||||
|
||||
self.next_state.epics = [
|
||||
{
|
||||
"id": uuid4().hex,
|
||||
"name": "Build frontend",
|
||||
"source": "frontend",
|
||||
"description": description,
|
||||
"messages": [],
|
||||
"summary": None,
|
||||
"completed": False,
|
||||
}
|
||||
]
|
||||
|
||||
return False
|
||||
|
||||
async def start_frontend(self):
|
||||
"""
|
||||
Starts the frontend of the app.
|
||||
@@ -298,29 +234,6 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
|
||||
|
||||
return AgentResponse.done(self)
|
||||
|
||||
async def apply_template(self, options: dict = {}):
|
||||
"""
|
||||
Applies a template to the frontend.
|
||||
"""
|
||||
template_name = "vite_react"
|
||||
template_class = PROJECT_TEMPLATES.get(template_name)
|
||||
if not template_class:
|
||||
log.error(f"Project template not found: {template_name}")
|
||||
return
|
||||
|
||||
template = template_class(
|
||||
options,
|
||||
self.state_manager,
|
||||
self.process_manager,
|
||||
)
|
||||
self.state_manager.template["template"] = template
|
||||
log.info(f"Applying project template: {template.name}")
|
||||
summary = await template.apply()
|
||||
|
||||
self.next_state.relevant_files = template.relevant_files
|
||||
self.next_state.modified_files = {}
|
||||
self.next_state.specification.template_summary = summary
|
||||
|
||||
async def set_app_details(self):
|
||||
"""
|
||||
Sets the app details.
|
||||
|
||||
@@ -24,6 +24,7 @@ from core.agents.task_completer import TaskCompleter
|
||||
from core.agents.tech_lead import TechLead
|
||||
from core.agents.tech_writer import TechnicalWriter
|
||||
from core.agents.troubleshooter import Troubleshooter
|
||||
from core.agents.wizard import Wizard
|
||||
from core.db.models.project_state import IterationStatus, TaskStatus
|
||||
from core.log import get_logger
|
||||
from core.telemetry import telemetry
|
||||
@@ -392,7 +393,9 @@ class Orchestrator(BaseAgent, GitMixin):
|
||||
if prev_response.type == ResponseType.UPDATE_SPECIFICATION:
|
||||
return SpecWriter(self.state_manager, self.ui, prev_response=prev_response)
|
||||
|
||||
if not state.epics or (state.current_epic and state.current_epic.get("source") == "frontend"):
|
||||
if not state.epics:
|
||||
return Wizard(self.state_manager, self.ui, process_manager=self.process_manager)
|
||||
elif state.current_epic and state.current_epic.get("source") == "frontend":
|
||||
# Build frontend
|
||||
return Frontend(self.state_manager, self.ui, process_manager=self.process_manager)
|
||||
elif not state.specification.description:
|
||||
|
||||
@@ -202,6 +202,7 @@ async def list_projects_json(db: SessionManager):
|
||||
last_updated = None
|
||||
p = {
|
||||
"name": project.name,
|
||||
"project_type": project.project_type,
|
||||
"id": project.id.hex,
|
||||
"branches": [],
|
||||
}
|
||||
|
||||
@@ -129,7 +129,11 @@ async def start_new_project(sm: StateManager, ui: UIBase) -> bool:
|
||||
stack = await ui.ask_question(
|
||||
"What do you want to use to build your app?",
|
||||
allow_empty=False,
|
||||
buttons={"node": "Node.js", "other": "Other (coming soon)"},
|
||||
buttons={
|
||||
"node": "Node.js",
|
||||
"swagger": "Frontend only with OpenAPI (Swagger) backend",
|
||||
"other": "Other (coming soon)",
|
||||
},
|
||||
buttons_only=True,
|
||||
source=pythagora_source,
|
||||
full_screen=True,
|
||||
@@ -148,16 +152,11 @@ async def start_new_project(sm: StateManager, ui: UIBase) -> bool:
|
||||
)
|
||||
await ui.send_message("Thank you for submitting your request to support other languages.")
|
||||
return False
|
||||
elif stack.button == "node":
|
||||
await telemetry.trace_code_event(
|
||||
"stack-choice",
|
||||
{"language": "node"},
|
||||
)
|
||||
elif stack.button == "python":
|
||||
await telemetry.trace_code_event(
|
||||
"stack-choice",
|
||||
{"language": "python"},
|
||||
)
|
||||
|
||||
await telemetry.trace_code_event(
|
||||
"stack-choice",
|
||||
{"language": stack.button},
|
||||
)
|
||||
|
||||
while True:
|
||||
try:
|
||||
@@ -182,7 +181,7 @@ async def start_new_project(sm: StateManager, ui: UIBase) -> bool:
|
||||
else:
|
||||
break
|
||||
|
||||
project_state = await sm.create_project(project_name)
|
||||
project_state = await sm.create_project(name=project_name, project_type=stack.button)
|
||||
return project_state is not None
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ class Project(Base):
|
||||
folder_name: Mapped[str] = mapped_column(
|
||||
default=lambda context: Project.get_folder_from_project_name(context.get_current_parameters()["name"])
|
||||
)
|
||||
project_type: Mapped[str] = mapped_column()
|
||||
|
||||
# Relationships
|
||||
branches: Mapped[list["Branch"]] = relationship(back_populates="project", cascade="all", lazy="raise")
|
||||
|
||||
@@ -78,7 +78,9 @@ class StateManager:
|
||||
async with self.session_manager as session:
|
||||
return await Project.get_all_projects(session)
|
||||
|
||||
async def create_project(self, name: str, folder_name: Optional[str] = None) -> Project:
|
||||
async def create_project(
|
||||
self, name: str, project_type: Optional[str] = "node", folder_name: Optional[str] = None
|
||||
) -> Project:
|
||||
"""
|
||||
Create a new project and set it as the current one.
|
||||
|
||||
@@ -86,7 +88,7 @@ class StateManager:
|
||||
:return: The Project object.
|
||||
"""
|
||||
session = await self.session_manager.start()
|
||||
project = Project(name=name, folder_name=folder_name)
|
||||
project = Project(name=name, project_type=project_type, folder_name=folder_name)
|
||||
branch = Branch(project=project)
|
||||
state = ProjectState.create_initial_state(branch)
|
||||
session.add(project)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import axios, { AxiosRequestConfig, AxiosError } from 'axios';
|
||||
|
||||
{% if options.auth_type == "api_key" %}
|
||||
const API_KEY = import.meta.env.VITE_API_KEY;
|
||||
{% endif %}
|
||||
|
||||
const api = axios.create({
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -10,16 +14,33 @@ const api = axios.create({
|
||||
});
|
||||
|
||||
let accessToken: string | null = null;
|
||||
|
||||
{% if options.auth %}
|
||||
// Axios request interceptor: Attach access token to headers
|
||||
// Axios request interceptor: Attach access token and API key to headers
|
||||
api.interceptors.request.use(
|
||||
(config: AxiosRequestConfig): AxiosRequestConfig => {
|
||||
if (!accessToken) {
|
||||
accessToken = localStorage.getItem('accessToken');
|
||||
}
|
||||
if (accessToken && config.headers) {
|
||||
config.headers.Authorization = `Bearer ${accessToken}`;
|
||||
// Check if the request is not for login or register
|
||||
|
||||
{% if options.auth_type == "api_key" %}
|
||||
const isAuthEndpoint = config.url?.includes('/login') || config.url?.includes('/register');
|
||||
|
||||
if (!isAuthEndpoint) {
|
||||
// Add API key for non-auth endpoints
|
||||
if (config.headers && API_KEY) {
|
||||
config.headers['api_key'] = API_KEY; // or whatever header name your API expects
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
|
||||
// Add authorization token if available
|
||||
if (!accessToken) {
|
||||
accessToken = localStorage.getItem('accessToken');
|
||||
}
|
||||
if (accessToken && config.headers) {
|
||||
config.headers.Authorization = `Bearer ${accessToken}`;
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error: AxiosError): Promise<AxiosError> => Promise.reject(error)
|
||||
@@ -47,6 +68,14 @@ api.interceptors.response.use(
|
||||
// Retry the original request with the new token
|
||||
if (originalRequest.headers) {
|
||||
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
|
||||
// Ensure API key is still present in retry
|
||||
|
||||
{% if options.auth_type == "api_key" %}
|
||||
if (API_KEY) {
|
||||
originalRequest.headers['api_key'] = API_KEY;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
}
|
||||
return api(originalRequest);
|
||||
} catch (err) {
|
||||
|
||||
@@ -179,9 +179,11 @@ async def test_list_projects_json(mock_StateManager, capsys):
|
||||
|
||||
project = MagicMock(
|
||||
id=MagicMock(hex="abcd"),
|
||||
# project_type=MagicMock(hex="abcd"),
|
||||
branches=[branch],
|
||||
)
|
||||
project.name = "project1"
|
||||
project.project_type = "node"
|
||||
sm.list_projects = AsyncMock(return_value=[project])
|
||||
await list_projects_json(None)
|
||||
|
||||
@@ -194,6 +196,7 @@ async def test_list_projects_json(mock_StateManager, capsys):
|
||||
{
|
||||
"name": "project1",
|
||||
"id": "abcd",
|
||||
"project_type": "node",
|
||||
"updated_at": "2021-01-03T00:00:00",
|
||||
"branches": [
|
||||
{
|
||||
@@ -226,6 +229,7 @@ async def test_list_projects(mock_StateManager, capsys):
|
||||
|
||||
project = MagicMock(
|
||||
id="abcd",
|
||||
project_type="node",
|
||||
branches=[branch],
|
||||
)
|
||||
project.name = "project1"
|
||||
@@ -304,7 +308,14 @@ async def test_main(mock_Orchestrator, args, run_orchestrator, retval, tmp_path)
|
||||
with patch("core.cli.helpers.ArgumentParser", new=MockArgumentParser):
|
||||
ui, db, args = init()
|
||||
|
||||
ui.ask_question = AsyncMock(return_value=MagicMock(text="test", cancelled=False))
|
||||
# Create a mock with a string value for the button attribute
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = "test"
|
||||
mock_response.cancelled = False
|
||||
# Set a string value for the button attribute
|
||||
mock_response.button = "node" # or whatever valid project type you want to use
|
||||
|
||||
ui.ask_question = AsyncMock(return_value=mock_response)
|
||||
|
||||
mock_orca = mock_Orchestrator.return_value
|
||||
mock_orca.run = AsyncMock(return_value=True)
|
||||
@@ -330,7 +341,10 @@ async def test_main_handles_crash(mock_Orchestrator, tmp_path):
|
||||
with patch("core.cli.helpers.ArgumentParser", new=MockArgumentParser):
|
||||
ui, db, args = init()
|
||||
|
||||
ui.ask_question = AsyncMock(return_value=MagicMock(text="test", cancelled=False))
|
||||
# Create a mock response with a string value for the button attribute
|
||||
mock_response = MagicMock(text="test", cancelled=False)
|
||||
mock_response.button = "test_project_type" # Set a string value for project_type
|
||||
ui.ask_question = AsyncMock(return_value=mock_response)
|
||||
ui.send_message = AsyncMock()
|
||||
|
||||
mock_orca = mock_Orchestrator.return_value
|
||||
|
||||
@@ -62,7 +62,7 @@ async def agentcontext(testmanager):
|
||||
ask_question=AsyncMock(),
|
||||
)
|
||||
|
||||
await sm.create_project("test")
|
||||
await sm.create_project(name="test", project_type="node")
|
||||
|
||||
mock_llm = None
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@ def create_project_state(project_name="Test Project", branch_name=Branch.DEFAULT
|
||||
:return: The ProjectState object.
|
||||
"""
|
||||
|
||||
project = Project(name=project_name)
|
||||
project = Project(name=project_name, project_type="node")
|
||||
branch = Branch(name=branch_name, project=project)
|
||||
return ProjectState.create_initial_state(branch)
|
||||
|
||||
@@ -22,7 +22,7 @@ async def test_get_by_id_no_match(testdb):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_id(testdb):
|
||||
project = Project(name="test")
|
||||
project = Project(name="test", project_type="node")
|
||||
branch = Branch(project=project)
|
||||
testdb.add(project)
|
||||
await testdb.commit()
|
||||
@@ -34,7 +34,7 @@ async def test_get_by_id(testdb):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_last_state_no_steps(testdb):
|
||||
project = Project(name="test")
|
||||
project = Project(name="test", project_type="node")
|
||||
branch = Branch(project=project)
|
||||
testdb.add(project)
|
||||
await testdb.commit()
|
||||
@@ -61,7 +61,7 @@ async def test_get_last_state(testdb):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_last_state_no_session():
|
||||
project = Project(name="test")
|
||||
project = Project(name="test", project_type="node")
|
||||
branch = Branch(project=project)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
@@ -22,7 +22,7 @@ async def test_get_by_id_no_match(testdb):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_id(testdb):
|
||||
project = Project(name="test")
|
||||
project = Project(name="test", project_type="node")
|
||||
testdb.add(project)
|
||||
await testdb.commit()
|
||||
|
||||
@@ -32,7 +32,7 @@ async def test_get_by_id(testdb):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_by_id(testdb):
|
||||
project = Project(name="test")
|
||||
project = Project(name="test", project_type="node")
|
||||
testdb.add(project)
|
||||
await testdb.commit()
|
||||
|
||||
@@ -43,7 +43,7 @@ async def test_delete_by_id(testdb):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_branch_no_match(testdb):
|
||||
project = Project(name="test")
|
||||
project = Project(name="test", project_type="node")
|
||||
testdb.add(project)
|
||||
await testdb.commit()
|
||||
|
||||
@@ -53,7 +53,7 @@ async def test_get_branch_no_match(testdb):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_branch(testdb):
|
||||
project = Project(name="test")
|
||||
project = Project(name="test", project_type="node")
|
||||
branch = Branch(project=project)
|
||||
testdb.add(project)
|
||||
testdb.add(branch)
|
||||
@@ -65,7 +65,7 @@ async def test_get_branch(testdb):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_branch_no_session():
|
||||
project = Project(name="test")
|
||||
project = Project(name="test", project_type="node")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await project.get_branch()
|
||||
@@ -86,7 +86,7 @@ async def test_get_all_projects(testdb):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_folder_name(testdb):
|
||||
project = Project(name="test project")
|
||||
project = Project(name="test project", project_type="node")
|
||||
testdb.add(project)
|
||||
await testdb.commit()
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ async def test_get_by_id(testdb):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_last_state_no_session():
|
||||
project = Project(name="test")
|
||||
project = Project(name="test", project_type="node")
|
||||
branch = Branch(project=project)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
Reference in New Issue
Block a user