File size: 6,280 Bytes
b60ae2d
df6b24b
 
 
90521dc
77d2314
 
df6b24b
b4fb05c
 
 
 
 
b60ae2d
b4fb05c
d734b5c
b60ae2d
 
 
920f542
df6b24b
 
0237818
920f542
 
 
 
 
b60ae2d
c051177
920f542
b60ae2d
 
 
df6b24b
 
c051177
df6b24b
 
c051177
b60ae2d
 
 
c051177
df6b24b
0237818
b60ae2d
 
 
5261f65
df6b24b
 
5261f65
b60ae2d
5261f65
 
 
 
6678303
5261f65
 
 
 
df6b24b
0237818
6678303
5261f65
0237818
 
b60ae2d
 
 
0237818
77d2314
b60ae2d
 
 
 
 
 
0237818
e36866d
b60ae2d
 
 
 
 
 
90521dc
 
 
b60ae2d
 
 
 
 
 
 
 
 
 
 
 
 
 
df6b24b
b60ae2d
6678303
 
 
b60ae2d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
df6b24b
b60ae2d
 
df6b24b
 
e36866d
6678303
e36866d
b60ae2d
df6b24b
 
 
 
6678303
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import uuid, zipfile, re
from pathlib import Path
from typing import TypedDict, List, Dict, Any, Tuple

from langgraph.graph import StateGraph, END
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.messages.base import BaseMessage

from agents import (
    product_manager_agent,
    project_manager_agent,
    software_architect_agent,
    software_engineer_agent,
    quality_assurance_agent,
)

# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# 1) State definitions
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
class InputState(TypedDict):
    messages: List[BaseMessage]
    chat_log: List[Dict[str, Any]]

class OutputState(TypedDict):
    pm_output: str
    proj_output: str
    arch_output: str
    dev_output: str
    qa_output: str
    chat_log: List[Dict[str, Any]]

# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# 2) Wrap agents so they see full history
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
def wrap_agent(agent_run, output_key: str):
    def node(state: Dict[str, Any]) -> Dict[str, Any]:
        history = state["messages"]
        log     = state["chat_log"]
        result  = agent_run({"messages": history, "chat_log": log})
        return {
            "messages": history + result["messages"],
            "chat_log":  result["chat_log"],
            output_key:  result[output_key],
        }
    return node

# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# 3) Bridge β†’ ProductManager
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
def bridge_to_pm(state: Dict[str, Any]) -> Dict[str, Any]:
    history = state["messages"]
    log     = state["chat_log"]
    if not history or not isinstance(history[-1], HumanMessage):
        raise ValueError("bridge_to_pm expected a HumanMessage at history end")
    prompt = history[-1].content
    spec_prompt = (
        f"# Stakeholder Prompt\n\n"
        f"\"{prompt}\"\n\n"
        "Generate a structured product specification including:\n"
        "- Goals\n"
        "- Key features\n"
        "- User stories\n"
        "- Success metrics\n"
    )
    return {
        "messages": [AIMessage(content=spec_prompt)],
        "chat_log": log + [{"role": "System", "content": spec_prompt}],
    }

# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# 4) Build & compile the LangGraph
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
graph = StateGraph(input=InputState, output=OutputState)

graph.add_node("BridgePM",         bridge_to_pm)
graph.add_node("ProductManager",   wrap_agent(product_manager_agent.run,   "pm_output"))
graph.add_node("ProjectManager",   wrap_agent(project_manager_agent.run,   "proj_output"))
graph.add_node("SoftwareArchitect",wrap_agent(software_architect_agent.run, "arch_output"))
graph.add_node("SoftwareEngineer", wrap_agent(software_engineer_agent.run,  "dev_output"))
graph.add_node("QualityAssurance", wrap_agent(quality_assurance_agent.run,  "qa_output"))

graph.set_entry_point("BridgePM")
graph.add_edge("BridgePM",         "ProductManager")
graph.add_edge("ProductManager",   "ProjectManager")
graph.add_edge("ProjectManager",   "SoftwareArchitect")
graph.add_edge("SoftwareArchitect","SoftwareEngineer")
graph.add_edge("SoftwareEngineer", "QualityAssurance")
graph.add_edge("QualityAssurance", END)

compiled_graph = graph.compile()

# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# 5) Parse spec into sections
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
def parse_spec(spec: str) -> Dict[str, List[str]]:
    sections: Dict[str, List[str]] = {}
    for m in re.finditer(r"##\s*(.+?)\n((?:- .+\n?)+)", spec):
        name = m.group(1).strip()
        items = [line[2:].strip() for line in m.group(2).splitlines() if line.startswith("- ")]
        sections[name] = items
    return sections

# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# 6) Run pipeline, generate site, zip, return (chat_log, zip_path)
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
def run_pipeline_and_save(prompt: str) -> Tuple[List[Dict[str, Any]], str]:
    # a) invoke agents
    initial_state = {"messages": [HumanMessage(content=prompt)], "chat_log": []}
    final_state   = compiled_graph.invoke(initial_state)

    chat_log  = final_state["chat_log"]
    qa_output = final_state["qa_output"]

    # b) parse spec
    spec = parse_spec(qa_output)
    features     = spec.get("Key features", [])
    testimonials = spec.get("User stories", [])

    # c) build HTML
    title = prompt.title()
    domain = prompt.replace(" ", "").lower() + ".com"
    cards_html = "\n".join(f"<div class='card'><h3>{f}</h3></div>" for f in features)
    test_html  = "\n".join(f"<blockquote>{t}</blockquote>" for t in testimonials)

    html_code = f"""<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>{title}</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <header><h1>{title}</h1></header>
  <section id="features">
    <h2>Features</h2>
    <div class="cards">
      {cards_html}
    </div>
  </section>
  <section id="testimonials">
    <h2>Testimonials</h2>
    {test_html or '<p>No testimonials provided.</p>'}
  </section>
  <section id="contact">
    <h2>Contact Us</h2>
    <p>Email: info@{domain}</p>
  </section>
</body>
</html>"""

    # d) basic CSS
    css_code = """
body { font-family: Arial, sans-serif; margin: 1em; line-height: 1.5; }
header { text-align: center; margin-bottom: 2em; }
.cards { display: grid; grid-template-columns: repeat(auto-fit,minmax(150px,1fr)); gap: 1em; }
.card { background: #f9f9f9; padding: 1em; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); text-align: center; }
blockquote { font-style: italic; margin: 1em; padding: 0.5em; background: #eef; border-left: 4px solid #99f; }
"""

    # e) write & zip
    site_id  = uuid.uuid4().hex
    out_dir  = Path("output")
    site_dir = out_dir / f"site_{site_id}"
    site_dir.mkdir(parents=True, exist_ok=True)

    (site_dir / "index.html").write_text(html_code, encoding="utf-8")
    (site_dir / "styles.css").write_text(css_code,  encoding="utf-8")

    zip_path = out_dir / f"site_{site_id}.zip"
    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
        for f in site_dir.iterdir():
            zf.write(f, arcname=f.name)

    return chat_log, str(zip_path)