Mohammed Foud commited on
Commit
5a2d62e
·
1 Parent(s): 5cc14db

Add application file

Browse files
.gitignore ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .DS_Store
2
+
3
+ # Byte-compiled / optimized / DLL files
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ test/
87
+
88
+ # pyenv
89
+ # For a library or package, you might want to ignore these files since the code is
90
+ # intended to run in multiple environments; otherwise, check them in:
91
+ # .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # poetry
101
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
102
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
103
+ # commonly ignored for libraries.
104
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
105
+ #poetry.lock
106
+
107
+ # pdm
108
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
109
+ #pdm.lock
110
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
111
+ # in version control.
112
+ # https://pdm.fming.dev/#use-with-ide
113
+ .pdm.toml
114
+
115
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
116
+ __pypackages__/
117
+
118
+ # Celery stuff
119
+ celerybeat-schedule
120
+ celerybeat.pid
121
+
122
+ # SageMath parsed files
123
+ *.sage.py
124
+
125
+ # Environments
126
+ .env
127
+ .venv
128
+ env/
129
+ venv/
130
+ ENV/
131
+ env.bak/
132
+ venv.bak/
133
+
134
+ # Spyder project settings
135
+ .spyderproject
136
+ .spyproject
137
+
138
+ # Rope project settings
139
+ .ropeproject
140
+
141
+ # mkdocs documentation
142
+ /site
143
+
144
+ # mypy
145
+ .mypy_cache/
146
+ .dmypy.json
147
+ dmypy.json
148
+
149
+ # Pyre type checker
150
+ .pyre/
151
+
152
+ # pytype static type analyzer
153
+ .pytype/
154
+
155
+ # Cython debug symbols
156
+ cython_debug/
157
+
158
+ # PyCharm
159
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
160
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
161
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
162
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
163
+ #.idea/
164
+
165
+ /threads
166
+ state.json
167
+ /workspace/
168
+ /workspace/*
169
+ /workspace/**
170
+
171
+ *.venvy/*
172
+ *.venvy*
173
+
174
+ # SQLite
175
+ *.db
176
+
177
+ # .DS_Store files
178
+ .DS_Store
179
+ **/.DS_Store
180
+ .aider*
181
+ supabase/.temp/cli-latest
182
+ supabase/.temp/gotrue-version
183
+ supabase/.temp/pooler-url
184
+ supabase/.temp/postgres-version
185
+ supabase/.temp/project-ref
186
+ supabase/.temp/rest-version
187
+ supabase/.temp/storage-version
188
+
189
+ **/.prompts/
190
+ **/__pycache__/
191
+
192
+
193
+ .env.scripts
194
+ server/.git
195
+ trash
196
+ templates
197
+ output
198
+
199
+ rand.edu.backend/.git
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user ./requirements.txt requirements.txt
10
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
+
12
+ COPY --chown=user . /app
13
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
agents/analysis/__init__.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .entities_analysis import EntitiesAnalysis
2
+ from .actors_analysis import ActorsAnalysis
3
+ from .sequence_analysis import SequenceAnalysis
4
+ from .state_analysis import StateAnalysis
5
+ from .architecture_analysis import ArchitectureAnalysis
6
+ from .deployment_analysis import DeploymentAnalysis
7
+
8
+ __all__ = [
9
+ 'EntitiesAnalysis',
10
+ 'ActorsAnalysis',
11
+ 'SequenceAnalysis',
12
+ 'StateAnalysis',
13
+ 'ArchitectureAnalysis',
14
+ 'DeploymentAnalysis'
15
+ ]
agents/analysis/actors_analysis.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class ActorsAnalysis(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Conduct a thorough actor and use case analysis for the following project:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+
14
+ Analysis Requirements:
15
+ 1. Identify all actor types:
16
+ - Primary actors (initiate interactions)
17
+ - Secondary actors (support the system)
18
+ - External systems (integrated services)
19
+ - Offstage actors (affected but don't interact)
20
+ 2. For each actor, specify:
21
+ - Role/purpose
22
+ - Interaction frequency
23
+ - Special characteristics
24
+ 3. Identify core use cases with:
25
+ - Clear actor-action-benefit format
26
+ - Preconditions and postconditions
27
+ - Main success scenarios
28
+ - Key variations/alternate flows
29
+ 4. Highlight relationships between actors
30
+ 5. Identify system boundaries
31
+
32
+ Format your response as follows:
33
+
34
+ ### Actor Analysis
35
+ #### Primary Actors
36
+ - [Actor Name]
37
+ * Role: [description]
38
+ * Frequency: [constant/occasional/rare]
39
+ * Characteristics: [key attributes]
40
+
41
+ #### Supporting Actors
42
+ - [System/Service Name]
43
+ * Role: [description]
44
+ * Integration Type: [API/database/event]
45
+
46
+ ### Use Case Analysis
47
+ #### Core Use Cases
48
+ 1. "[Actor] performs [action] to [benefit]"
49
+ - Preconditions: [required state]
50
+ - Main Flow: [step-by-step]
51
+ - Postconditions: [system state after]
52
+ - Variations: [alternate paths]
53
+
54
+ #### Supporting Use Cases
55
+ 1. "[System] provides [functionality] for [purpose]"
56
+
57
+ ### System Boundaries
58
+ - Included: [what's in scope]
59
+ - Excluded: [out-of-scope items]
60
+
61
+ Example:
62
+ ### Actor Analysis
63
+ #### Primary Actors
64
+ - Online Customer
65
+ * Role: Initiates purchases and manages account
66
+ * Frequency: Constant
67
+ * Characteristics: Authenticated, varying technical skill
68
+
69
+ ### Use Case Analysis
70
+ #### Core Use Cases
71
+ 1. "Customer places order for products"
72
+ - Preconditions: Customer is logged in, cart not empty
73
+ - Main Flow:
74
+ 1. Customer selects checkout
75
+ 2. System validates inventory
76
+ 3. Customer enters payment
77
+ 4. System confirms order
78
+ - Postconditions: Order is persisted, inventory updated
79
+ - Variations: Payment fails, inventory unavailable
80
+ """
81
+
82
+ async for chunk in self._stream_process(
83
+ state=state,
84
+ prompt_template=prompt_template,
85
+ output_key="actors_use_cases",
86
+ step_name="extract_actors",
87
+ project_name=state["project_name"],
88
+ project_description=state["project_description"]
89
+ ):
90
+ yield chunk
agents/analysis/architecture_analysis.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class ArchitectureAnalysis(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Analyze the following project and identify system components:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+ Entities: {entities_classes}
14
+ Use Cases: {actors_use_cases}
15
+
16
+ Instructions:
17
+ 1. Identify major system components/modules
18
+ 2. List their responsibilities
19
+ 3. Describe how they interact
20
+ 4. Format as a structured component hierarchy
21
+ """
22
+
23
+ async for chunk in self._stream_process(
24
+ state=state,
25
+ prompt_template=prompt_template,
26
+ output_key="architecture_components",
27
+ step_name="extract_architecture",
28
+ project_name=state["project_name"],
29
+ project_description=state["project_description"],
30
+ entities_classes=state["entities_classes"],
31
+ actors_use_cases=state["actors_use_cases"]
32
+ ):
33
+ yield chunk
agents/analysis/deployment_analysis.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class DeploymentAnalysis(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Analyze the following project and identify deployment context:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+ Architecture Components: {architecture_components}
14
+
15
+ Instructions:
16
+ 1. Identify the deployment nodes (servers, clients, etc.)
17
+ 2. List the artifacts to be deployed
18
+ 3. Show the relationships between nodes
19
+ 4. Format as a structured list of deployment elements
20
+ """
21
+
22
+ async for chunk in self._stream_process(
23
+ state=state,
24
+ prompt_template=prompt_template,
25
+ output_key="deployment_context",
26
+ step_name="extract_deployment",
27
+ project_name=state["project_name"],
28
+ project_description=state["project_description"],
29
+ architecture_components=state.get("architecture_components", "")
30
+ ):
31
+ yield chunk
agents/analysis/entities_analysis.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class EntitiesAnalysis(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Conduct a comprehensive domain analysis for the following project to identify:
10
+ - Core entities
11
+ - Their attributes (with data types)
12
+ - Key relationships
13
+ - Important methods/operations
14
+ - Business rules/constraints
15
+
16
+ Project: {project_name}
17
+ Description: {project_description}
18
+
19
+ Format your response using the following structure for each entity:
20
+
21
+ ### [EntityName]
22
+ **Description**: [Brief purpose description]
23
+ **Attributes**:
24
+ - name: type (constraints/notes)
25
+ **Relationships**:
26
+ - [Cardinality] to [RelatedEntity] (description)
27
+ **Methods**:
28
+ - methodName(params): returnType (description)
29
+ **Business Rules**:
30
+ - [Rule description]
31
+
32
+ Example:
33
+ ### User
34
+ **Description**: Represents system users with authentication
35
+ **Attributes**:
36
+ - id: UUID (primary key)
37
+ - username: string (unique, 3-20 chars)
38
+ - email: string (valid email format)
39
+ - passwordHash: string (encrypted)
40
+ **Relationships**:
41
+ - One-to-Many to Order (user places orders)
42
+ **Methods**:
43
+ - authenticate(password: string): boolean
44
+ - placeOrder(items: List[CartItem]): Order
45
+ **Business Rules**:
46
+ - Password must be at least 8 characters
47
+ - Email must be verified before ordering
48
+
49
+ Provide this analysis for all major domain entities.
50
+ """
51
+
52
+ async for chunk in self._stream_process(
53
+ state=state,
54
+ prompt_template=prompt_template,
55
+ output_key="entities_classes",
56
+ step_name="extract_entities",
57
+ project_name=state["project_name"],
58
+ project_description=state["project_description"]
59
+ ):
60
+ yield chunk
agents/analysis/sequence_analysis.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class SequenceAnalysis(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Conduct a comprehensive sequence analysis for the key workflow in:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+ Use Cases: {actors_use_cases}
14
+
15
+ Analysis Requirements:
16
+ 1. Identify all participating components:
17
+ - Primary actors (initiators)
18
+ - Secondary systems/services
19
+ - Internal components
20
+ 2. For each interaction step specify:
21
+ - Sender and receiver
22
+ - Message content/type
23
+ - Synchronization points
24
+ - Alternative/error flows
25
+ 3. Include timing considerations where relevant
26
+ 4. Note any parallel/concurrent activities
27
+ 5. Highlight critical system boundaries
28
+
29
+ Format your response as:
30
+
31
+ ### Workflow: [Workflow Name]
32
+ #### Components:
33
+ - [Component1] (type/role)
34
+ - [Component2] (type/role)
35
+
36
+ #### Main Flow:
37
+ 1. [ComponentA] -> [ComponentB]: [Message/Purpose]
38
+ - Preconditions: [required state]
39
+ - Postconditions: [resulting state]
40
+ - Variations: [alternate paths]
41
+
42
+ 2. [ComponentB] --> [ComponentA]: [Response]
43
+ ...
44
+
45
+ #### Parallel Flows:
46
+ - Concurrent with step 2: [Description]
47
+
48
+ #### Error Handling:
49
+ - At step 3: [Error Condition] → [Recovery Path]
50
+
51
+ Example:
52
+ ### Workflow: Order Processing
53
+ #### Components:
54
+ - Customer (primary actor)
55
+ - OrderService (core system)
56
+ - PaymentGateway (external service)
57
+
58
+ #### Main Flow:
59
+ 1. Customer -> OrderService: SubmitOrder(cart)
60
+ - Preconditions: Cart not empty, user authenticated
61
+ - Postconditions: Order pending payment
62
+ - Variations: Invalid items → Rejection notice
63
+
64
+ 2. OrderService -> PaymentGateway: ProcessPayment(details)
65
+ ...
66
+ """
67
+
68
+ async for chunk in self._stream_process(
69
+ state=state,
70
+ prompt_template=prompt_template,
71
+ output_key="sequence_interactions",
72
+ step_name="extract_sequence",
73
+ project_name=state["project_name"],
74
+ project_description=state["project_description"],
75
+ actors_use_cases=state["actors_use_cases"]
76
+ ):
77
+ yield chunk
agents/analysis/state_analysis.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class StateAnalysis(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Analyze the following project and identify state transitions for key entities:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+ Entities: {entities_classes}
14
+ Sequence: {sequence_interactions}
15
+
16
+ Instructions:
17
+ 1. For each key entity, identify its possible states
18
+ 2. List the events that trigger state transitions
19
+ 3. Format as a structured list of states and transitions
20
+ """
21
+
22
+ async for chunk in self._stream_process(
23
+ state=state,
24
+ prompt_template=prompt_template,
25
+ output_key="state_transitions",
26
+ step_name="extract_state_transitions",
27
+ project_name=state["project_name"],
28
+ project_description=state["project_description"],
29
+ entities_classes=state["entities_classes"],
30
+ sequence_interactions=state["sequence_interactions"]
31
+ ):
32
+ yield chunk
agents/code/__init__.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .class_diagram_generator import ClassDiagramGenerator
2
+ from .use_case_diagram_generator import UseCaseDiagramGenerator
3
+ from .sequence_diagram_generator import SequenceDiagramGenerator
4
+ from .activity_diagram_generator import ActivityDiagramGenerator
5
+ from .component_diagram_generator import ComponentDiagramGenerator
6
+ from .deployment_diagram_generator import DeploymentDiagramGenerator
7
+ from .state_diagram_generator import StateDiagramGenerator
8
+ from .timing_diagram_generator import TimingDiagramGenerator
9
+ from .object_diagram_generator import ObjectDiagramGenerator
10
+
11
+ __all__ = [
12
+ 'ClassDiagramGenerator',
13
+ 'UseCaseDiagramGenerator',
14
+ 'SequenceDiagramGenerator',
15
+ 'ActivityDiagramGenerator',
16
+ 'ComponentDiagramGenerator',
17
+ 'DeploymentDiagramGenerator',
18
+ 'StateDiagramGenerator',
19
+ 'TimingDiagramGenerator',
20
+ 'ObjectDiagramGenerator'
21
+ ]
agents/code/activity_diagram_generator.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class ActivityDiagramGenerator(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Generate PlantUML code for an activity diagram based on the following project:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+ Use Cases: {actors_use_cases}
14
+
15
+ Instructions:
16
+ 1. Show the main workflows as activities
17
+ 2. Include decision points and parallel activities where appropriate
18
+ 3. Use standard PlantUML activity diagram syntax
19
+ 4. Don't include any explanation, just the PlantUML code
20
+
21
+ Example format (DO NOT TREAT THIS AS VARIABLES):
22
+ '''plantuml
23
+ @startuml
24
+ start
25
+ :Main Activity;
26
+ if (Decision?) then (yes)
27
+ :Activity 1;
28
+ else (no)
29
+ :Activity 2;
30
+ endif
31
+ stop
32
+ @enduml
33
+ '''
34
+ """
35
+
36
+ async for chunk in self._stream_process(
37
+ state=state,
38
+ prompt_template=prompt_template,
39
+ output_key="activity_diagram",
40
+ step_name="generate_activity_diagram",
41
+ project_name=state["project_name"],
42
+ project_description=state["project_description"],
43
+ actors_use_cases=state["actors_use_cases"]
44
+ ):
45
+ yield chunk
agents/code/class_diagram_generator.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class ClassDiagramGenerator(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Generate comprehensive PlantUML code for a class diagram based on the following domain analysis:
10
+
11
+ {entities_classes}
12
+
13
+ Instructions:
14
+ 1. Include all classes with their complete attributes (visibility, type, constraints)
15
+ 2. Use proper UML relationships with correct cardinality and labels:
16
+ - Inheritance: <|--
17
+ - Implementation: <|..
18
+ - Composition: *--
19
+ - Aggregation: o--
20
+ - Dependency: -->
21
+ 3. For complex relationships, use association classes when needed
22
+ 4. Include proper visibility markers:
23
+ - Public: +
24
+ - Private: -
25
+ - Protected: #
26
+ - Package: ~
27
+ 5. Use advanced PlantUML features where appropriate:
28
+ - Abstract classes/interfaces
29
+ - Enumerations
30
+ - Stereotypes
31
+ - Notes and constraints
32
+ - Method signatures
33
+ 6. Organize classes into packages if the domain suggests logical groupings
34
+ 7. Use proper formatting with alignment and spacing
35
+ 8. Include only the raw PlantUML code without explanations
36
+
37
+ Example Structure (DO NOT TREAT AS VARIABLES):
38
+ @startuml
39
+ package "Domain Model" {{
40
+ abstract class AbstractEntity {{
41
+ + id: UUID {{readonly}}
42
+ # createdAt: DateTime
43
+ + equals(other: Object): boolean {{abstract}}
44
+ }}
45
+
46
+ class User {{
47
+ + username: String {{unique}}
48
+ + email: String
49
+ - passwordHash: String
50
+ + authenticate(password: String): boolean
51
+ }}
52
+
53
+ enum UserRole {{
54
+ ADMIN
55
+ MEMBER
56
+ GUEST
57
+ }}
58
+
59
+ User "1" *-- "0..*" Order : places >
60
+ User o-- UserRole : has >
61
+
62
+ (User, Order) .. OrderAssociation
63
+ class OrderAssociation {{
64
+ + orderDate: DateTime
65
+ + status: OrderStatus
66
+ }}
67
+ }}
68
+
69
+ note top of User
70
+ User represents all system users
71
+ with authentication capabilities
72
+ end note
73
+ @enduml
74
+ """
75
+
76
+ async for chunk in self._stream_process(
77
+ state=state,
78
+ prompt_template=prompt_template,
79
+ output_key="class_diagram",
80
+ step_name="generate_class_diagram",
81
+ entities_classes=state["entities_classes"]
82
+ ):
83
+ yield chunk
agents/code/component_diagram_generator.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class ComponentDiagramGenerator(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Generate PlantUML code for a component diagram based on the following project:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+ Entities: {entities_classes}
14
+
15
+ Instructions:
16
+ 1. Show the main system components and their interfaces
17
+ 2. Include relationships between components
18
+ 3. Use standard PlantUML component diagram syntax
19
+ 4. Don't include any explanation, just the PlantUML code
20
+
21
+ Example format (DO NOT TREAT THIS AS VARIABLES):
22
+ '''plantuml
23
+ @startuml
24
+ component [Component1] as C1
25
+ component [Component2] as C2
26
+ C1 --> C2 : uses
27
+ @enduml
28
+ '''
29
+ """
30
+
31
+ async for chunk in self._stream_process(
32
+ state=state,
33
+ prompt_template=prompt_template,
34
+ output_key="component_diagram",
35
+ step_name="generate_component_diagram",
36
+ project_name=state["project_name"],
37
+ project_description=state["project_description"],
38
+ entities_classes=state["entities_classes"]
39
+ ):
40
+ yield chunk
agents/code/deployment_diagram_generator.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class DeploymentDiagramGenerator(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Generate PlantUML code for a deployment diagram based on the following project:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+
14
+ Instructions:
15
+ 1. Show the physical deployment architecture
16
+ 2. Include nodes, artifacts, and their relationships
17
+ 3. Use standard PlantUML deployment diagram syntax
18
+ 4. Don't include any explanation, just the PlantUML code
19
+
20
+ Example format (DO NOT TREAT THIS AS VARIABLES):
21
+ '''plantuml
22
+ @startuml
23
+ node "Server" as server {{
24
+ artifact "WebApp"
25
+ }}
26
+ node "Client" as client
27
+ client --> server : HTTP
28
+ @enduml
29
+ '''
30
+ """
31
+
32
+ async for chunk in self._stream_process(
33
+ state=state,
34
+ prompt_template=prompt_template,
35
+ output_key="deployment_diagram",
36
+ step_name="generate_deployment_diagram",
37
+ project_name=state["project_name"],
38
+ project_description=state["project_description"]
39
+ ):
40
+ yield chunk
agents/code/object_diagram_generator.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class ObjectDiagramGenerator(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Generate PlantUML code for an object diagram based on the following project:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+ Entities: {entities_classes}
14
+
15
+ Instructions:
16
+ 1. Show object instances and their relationships
17
+ 2. Include attribute values for a specific scenario
18
+ 3. Use standard PlantUML object diagram syntax
19
+ 4. Don't include any explanation, just the PlantUML code
20
+
21
+ Example format (DO NOT TREAT THIS AS VARIABLES):
22
+ '''plantuml
23
+ @startuml
24
+ object "Object1" as o1 {{
25
+ attribute1 = value1
26
+ attribute2 = value2
27
+ }}
28
+ object "Object2" as o2 {{
29
+ attribute3 = value3
30
+ }}
31
+ o1 --> o2 : relationship
32
+ @enduml
33
+ '''
34
+ """
35
+
36
+ async for chunk in self._stream_process(
37
+ state=state,
38
+ prompt_template=prompt_template,
39
+ output_key="object_diagram",
40
+ step_name="generate_object_diagram",
41
+ project_name=state["project_name"],
42
+ project_description=state["project_description"],
43
+ entities_classes=state["entities_classes"]
44
+ ):
45
+ yield chunk
agents/code/sequence_diagram_generator.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class SequenceDiagramGenerator(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Generate comprehensive PlantUML code for a sequence diagram based on:
10
+
11
+ Workflow Analysis: {sequence_interactions}
12
+
13
+ Instructions:
14
+ 1. Use proper participant types:
15
+ - actor for human users
16
+ - boundary for UI elements
17
+ - control for controllers
18
+ - entity for domain objects
19
+ - database for storage
20
+ 2. Include all message types:
21
+ - Synchronous: ->
22
+ - Asynchronous: ->>
23
+ - Return: -->
24
+ - Self: -> self
25
+ 3. Add activation bars with:
26
+ - activate/deactivate
27
+ - Nested activations where needed
28
+ 4. Use advanced features:
29
+ - Groups (alt/opt/loop/par)
30
+ - Notes
31
+ - Dividers (== Sections ==)
32
+ - Stereotypes <<component>>
33
+ - Arrows with:
34
+ * Lost messages (->x)
35
+ * Found messages (x->)
36
+ * Parallel (->||)
37
+ 5. Format for readability:
38
+ - Proper indentation
39
+ - Section comments
40
+ - Color coding where helpful
41
+
42
+ Example Structure (DO NOT TREAT AS VARIABLES):
43
+ @startuml
44
+ skinparam style strictuml
45
+ skinparam lifelineStrategy solid
46
+
47
+ actor Customer <<Human>>
48
+ boundary "Web UI" as UI
49
+ control OrderController
50
+ entity Order
51
+ database OrderDB
52
+
53
+ == Order Submission ==
54
+ Customer -> UI: Submit Order
55
+ activate UI
56
+ UI -> OrderController: processOrder()
57
+ activate OrderController
58
+
59
+ group Payment Processing
60
+ OrderController -> PaymentService <<External>>: authorizePayment()
61
+ activate PaymentService
62
+ PaymentService --> OrderController: status
63
+ deactivate PaymentService
64
+ end
65
+
66
+ alt payment approved
67
+ OrderController -> OrderDB: persistOrder()
68
+ OrderController --> UI: confirmation
69
+ else payment declined
70
+ OrderController --> UI: error
71
+ end
72
+
73
+ deactivate OrderController
74
+ UI --> Customer: Order Status
75
+ deactivate UI
76
+ @enduml
77
+ """
78
+
79
+ async for chunk in self._stream_process(
80
+ state=state,
81
+ prompt_template=prompt_template,
82
+ output_key="sequence_diagram",
83
+ step_name="generate_sequence_diagram",
84
+ sequence_interactions=state["sequence_interactions"]
85
+ ):
86
+ yield chunk
agents/code/state_diagram_generator.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class StateDiagramGenerator(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Generate PlantUML code for a state diagram based on the following project:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+ Entities: {entities_classes}
14
+
15
+ Instructions:
16
+ 1. Show the state transitions for one key entity
17
+ 2. Include states, transitions, and events
18
+ 3. Use standard PlantUML state diagram syntax
19
+ 4. Don't include any explanation, just the PlantUML code
20
+
21
+ Example format (DO NOT TREAT THIS AS VARIABLES):
22
+ '''plantuml
23
+ @startuml
24
+ [*] --> State1
25
+ State1 --> State2 : Event1
26
+ State2 --> [*]
27
+ @enduml
28
+ '''
29
+ """
30
+
31
+ async for chunk in self._stream_process(
32
+ state=state,
33
+ prompt_template=prompt_template,
34
+ output_key="state_diagram",
35
+ step_name="generate_state_diagram",
36
+ project_name=state["project_name"],
37
+ project_description=state["project_description"],
38
+ entities_classes=state["entities_classes"]
39
+ ):
40
+ yield chunk
agents/code/timing_diagram_generator.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class TimingDiagramGenerator(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Generate PlantUML code for a timing diagram based on the following project:
10
+
11
+ Project: {project_name}
12
+ Description: {project_description}
13
+ Sequence: {sequence_interactions}
14
+
15
+ Instructions:
16
+ 1. Show the timing constraints between components
17
+ 2. Include lifelines and state changes over time
18
+ 3. Use standard PlantUML timing diagram syntax
19
+ 4. Don't include any explanation, just the PlantUML code
20
+
21
+ Example format (DO NOT TREAT THIS AS VARIABLES):
22
+ '''plantuml
23
+ @startuml
24
+ clock "Clock" as C with period 1
25
+ binary "Signal" as S
26
+ @0
27
+ S is low
28
+ @5
29
+ S is high
30
+ @10
31
+ S is low
32
+ @enduml
33
+ '''
34
+ """
35
+
36
+ async for chunk in self._stream_process(
37
+ state=state,
38
+ prompt_template=prompt_template,
39
+ output_key="timing_diagram",
40
+ step_name="generate_timing_diagram",
41
+ project_name=state["project_name"],
42
+ project_description=state["project_description"],
43
+ sequence_interactions=state["sequence_interactions"]
44
+ ):
45
+ yield chunk
agents/code/use_case_diagram_generator.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base_agent import BaseAgent
2
+ from models import AgentState
3
+ from typing import AsyncGenerator
4
+ import json
5
+
6
+ class UseCaseDiagramGenerator(BaseAgent):
7
+ async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
8
+ prompt_template = """
9
+ Generate comprehensive PlantUML code for a use case diagram based on:
10
+
11
+ Project: {project_name}
12
+ Actors and Use Cases: {actors_use_cases}
13
+
14
+ Instructions:
15
+ 1. Include all actors with proper typing:
16
+ - Primary actors on left
17
+ - Supporting actors on right
18
+ - System actors if applicable
19
+ 2. Organize use cases into logical packages/subsystems when appropriate
20
+ 3. Use proper relationship types:
21
+ - Association: --> (actor to use case)
22
+ - Include: .> (base use case includes another)
23
+ - Extend: .> (use case extends another with conditions)
24
+ 4. Apply stereotypes where helpful (<<system>>, <<external>>, etc.)
25
+ 5. Include notes for complex relationships when needed
26
+ 6. Use left-to-right direction for better readability
27
+ 7. Format with clear alignment and spacing
28
+ 8. Include only raw PlantUML code without explanations
29
+
30
+ Example Structure (DO NOT TREAT AS VARIABLES):
31
+ @startuml
32
+ left to right direction
33
+ skinparam packageStyle rectangle
34
+
35
+ actor "Primary Actor" <<Human>> as user
36
+ actor "Supporting System" <<System>> as legacy
37
+
38
+ rectangle "Order Processing" {{
39
+ usecase "Place Order" as UC1
40
+ usecase "Process Payment" as UC2
41
+ usecase "Validate Order" as UC3
42
+
43
+ UC1 .> UC2 : include
44
+ UC1 .> UC3 : include
45
+ }}
46
+
47
+ rectangle "Inventory" {{
48
+ usecase "Check Stock" as UC4
49
+ }}
50
+
51
+ user --> UC1
52
+ legacy --> UC3
53
+
54
+ note right of UC1
55
+ This use case initiates the
56
+ order fulfillment workflow
57
+ end note
58
+ @enduml
59
+ """
60
+
61
+ async for chunk in self._stream_process(
62
+ state=state,
63
+ prompt_template=prompt_template,
64
+ output_key="use_case_diagram",
65
+ step_name="generate_use_case_diagram",
66
+ actors_use_cases=state["actors_use_cases"],
67
+ project_name=state["project_name"]
68
+ ):
69
+ yield chunk
app.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, jsonify, render_template, request, Response, stream_with_context
2
+ from research_generator import ResearchGenerator
3
+ import asyncio
4
+ import json
5
+ from langchain_core.output_parsers import StrOutputParser
6
+ from langchain_core.prompts import ChatPromptTemplate
7
+
8
+ app = Flask(__name__)
9
+ generator = ResearchGenerator()
10
+
11
+ @app.route('/')
12
+ def index():
13
+ return render_template('index.html')
14
+
15
+ async def process_state(state):
16
+ if "index" in state and state["index"]:
17
+ # If we have an index, process it with the LLM
18
+ prompt = ChatPromptTemplate.from_template("""
19
+ Process the following research state:
20
+ {state}
21
+ """)
22
+ chain = prompt | generator.llm | StrOutputParser()
23
+
24
+ async for chunk in chain.astream({"state": json.dumps(state)}):
25
+ yield f"data: {json.dumps({'chunk': chunk, 'state': state})}\n\n"
26
+ else:
27
+ # If we're generating the index, stream it directly
28
+ yield f"data: {json.dumps({'state': state})}\n\n"
29
+
30
+ @app.route('/generate', methods=['POST'])
31
+ def generate_research():
32
+ data = request.get_json()
33
+ subject = data.get('subject')
34
+ if not subject:
35
+ return jsonify({'error': 'Subject is required'}), 400
36
+
37
+ async def generate():
38
+ generator = ResearchGenerator()
39
+ async for state in generator.astream({
40
+ "subject": subject,
41
+ "index": [],
42
+ "content": {},
43
+ "current_step": 0
44
+ }):
45
+ async for chunk in process_state(state):
46
+ yield chunk
47
+
48
+ def sync_generate():
49
+ loop = asyncio.new_event_loop()
50
+ asyncio.set_event_loop(loop)
51
+ try:
52
+ async_gen = generate()
53
+ while True:
54
+ try:
55
+ chunk = loop.run_until_complete(async_gen.__anext__())
56
+ yield chunk
57
+ except StopAsyncIteration:
58
+ break
59
+ finally:
60
+ loop.close()
61
+
62
+ return Response(
63
+ stream_with_context(sync_generate()),
64
+ mimetype='text/event-stream'
65
+ )
66
+
67
+ if __name__ == '__main__':
68
+ app.run(debug=True)
base_agent.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from typing import Dict, Generator, AsyncGenerator
3
+ from langchain_core.prompts import ChatPromptTemplate
4
+ from langchain_core.callbacks import StreamingStdOutCallbackHandler
5
+ from config import Config
6
+ from logger import Logger
7
+ from file_manager import FileManager
8
+ from models import AgentState
9
+ from langchain_ollama import OllamaLLM
10
+ from langchain_core.runnables import RunnableConfig
11
+ from langchain_core.output_parsers import StrOutputParser
12
+
13
+ class BaseAgent:
14
+ def __init__(self):
15
+ self.config = Config()
16
+ self.logger = Logger()
17
+ self.file_manager = FileManager()
18
+
19
+ # Set up callbacks based on streaming config
20
+ callbacks = [StreamingStdOutCallbackHandler()] if self.config.streaming else None
21
+
22
+ self.llm = OllamaLLM(
23
+ model=self.config.ollama_model,
24
+ base_url=self.config.ollama_base_url,
25
+ temperature=self.config.temperature,
26
+ top_p=self.config.top_p,
27
+ callbacks=callbacks,
28
+
29
+ )
30
+
31
+ async def _stream_process(self, state: AgentState, prompt_template: str, output_key: str, step_name: str, **kwargs) -> AsyncGenerator[str, None]:
32
+ prompt = ChatPromptTemplate.from_template(prompt_template)
33
+ chain = prompt | self.llm | StrOutputParser()
34
+
35
+ # Use stream method to get partial chunks
36
+ async for chunk in chain.astream(kwargs):
37
+ yield json.dumps({output_key: chunk})
38
+
39
+ async def _process(self, state: AgentState, prompt_template: str,
40
+ output_key: str, step_name: str, **kwargs) -> Dict:
41
+ prompt = ChatPromptTemplate.from_template(prompt_template)
42
+ chain = prompt | self.llm
43
+
44
+ result = await chain.ainvoke(kwargs)
45
+
46
+ return {output_key: result}
config.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import os
3
+ from enum import Enum
4
+
5
+ load_dotenv()
6
+
7
+ class DiagramType(Enum):
8
+ CLASS = 1
9
+ USE_CASE = 2
10
+ SEQUENCE = 3
11
+ OBJECT = 4
12
+ ACTIVITY = 5
13
+ COMPONENT = 6
14
+ DEPLOYMENT = 7
15
+ STATE = 8
16
+ TIMING = 9
17
+
18
+ class Config:
19
+ def __init__(self):
20
+ self.ollama_model = os.getenv("OLLAMA_MODEL", "llama3.3:70b")
21
+ self.ollama_base_url = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
22
+ self.temperature = float(os.getenv("TEMPERATURE", "0.7"))
23
+ self.top_p = float(os.getenv("TOP_P", "0.9"))
24
+ self.streaming = True#os.getenv("STREAMING", "false").lower() == "true"
25
+ self.callbacks = None
d.sh ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ git add .
2
+ git commit -m "Add application file"
3
+ git push
file_manager.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import datetime
3
+ from typing import Dict
4
+ import subprocess
5
+
6
+ class FileManager:
7
+ def __init__(self):
8
+ self.output_base = "output"
9
+
10
+ def create_output_dir(self, project_name: str, timestamp: str) -> str:
11
+ """Create output directory structure for this run."""
12
+ dir_name = f"{project_name}_{timestamp}"
13
+ output_dir = os.path.join(self.output_base, dir_name)
14
+
15
+ # Create main directories
16
+ os.makedirs(output_dir, exist_ok=True)
17
+ os.makedirs(os.path.join(output_dir, "code"), exist_ok=True)
18
+ os.makedirs(os.path.join(output_dir, "analysis"), exist_ok=True)
19
+ os.makedirs(os.path.join(output_dir, "images"), exist_ok=True)
20
+
21
+ return output_dir
22
+
23
+ def save_step_result(self, output_dir: str, step_name: str, content: str):
24
+ """Save intermediate step result with clean PlantUML code."""
25
+ ext = ".pu" if "diagram" in step_name else ".md"
26
+
27
+ # Determine target directory based on file type
28
+ if ext == ".pu":
29
+ target_dir = os.path.join(output_dir, "code")
30
+ # Render PlantUML diagram
31
+ self._render_plantuml(content, output_dir, step_name)
32
+ else:
33
+ target_dir = os.path.join(output_dir, "analysis")
34
+
35
+ filename = f"{step_name}{ext}"
36
+ filepath = os.path.join(target_dir, filename)
37
+
38
+ print(f"Saving {filename} to {target_dir}") # Add this line for debugging
39
+
40
+ # Clean PlantUML code by removing markdown code block markers
41
+ if ext == ".pu":
42
+ content = self._clean_plantuml_content(content)
43
+
44
+ with open(filepath, "w") as f:
45
+ f.write(content)
46
+
47
+ def _render_plantuml(self, content: str, output_dir: str, step_name: str):
48
+ """Render PlantUML diagram to PNG."""
49
+ # Clean the content first
50
+ content = self._clean_plantuml_content(content)
51
+
52
+ # Create temporary .pu file
53
+ temp_pu = os.path.join(output_dir, "code", f"{step_name}.pu")
54
+ with open(temp_pu, "w") as f:
55
+ f.write(content)
56
+
57
+ # Render to PNG
58
+ output_png = os.path.join(output_dir, "images", f"{step_name}.png")
59
+ try:
60
+ subprocess.run([
61
+ "plantuml",
62
+ "-tpng",
63
+ temp_pu,
64
+ "-o", "images"
65
+ ], check=True)
66
+ except subprocess.CalledProcessError as e:
67
+ print(f"Error rendering PlantUML diagram: {e}")
68
+ except FileNotFoundError:
69
+ print("PlantUML not found. Please install PlantUML to generate images.")
70
+
71
+ def _clean_plantuml_content(self, content: str) -> str:
72
+ """Remove triple quotes and plantuml markers from the content."""
73
+ # Remove '''plantuml and ''' markers
74
+ content = content.replace("'''plantuml", "").replace("'''", "")
75
+ content = content.replace("```plantuml", "").replace("```", "")
76
+
77
+
78
+ # Remove any remaining leading/trailing whitespace
79
+ content = content.strip()
80
+
81
+ return content
82
+
83
+ def save_final_results(self, state: Dict):
84
+ """Save all final results."""
85
+ output_dir = state["output_dir"]
86
+
87
+ # Save all analysis results and diagram codes
88
+ for key, value in state.items():
89
+ # Skip metadata fields
90
+ if key in ["project_name", "project_description", "output_dir", "selected_diagrams"]:
91
+ continue
92
+
93
+ if value: # Only save if there's content
94
+ self.save_step_result(output_dir, key, value)
95
+
96
+ # Save metadata
97
+ metadata = {
98
+ "project_name": state["project_name"],
99
+ "project_description": state["project_description"],
100
+ "generated_diagrams": state["selected_diagrams"],
101
+ "timestamp": datetime.now().isoformat()
102
+ }
103
+
104
+ import json
105
+ with open(os.path.join(output_dir, "metadata.json"), "w") as f:
106
+ json.dump(metadata, f, indent=2)
logger.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Logger:
2
+ def __init__(self):
3
+ self.verbose = True
4
+
5
+ def log(self, message: str):
6
+ if self.verbose:
7
+ print(message)
8
+
9
+ def log_title(self, title: str):
10
+ if self.verbose:
11
+ print(f"\n{'='*40}")
12
+ print(f"{title.center(40)}")
13
+ print(f"{'='*40}")
14
+
15
+ def log_step(self, step_name: str):
16
+ if self.verbose:
17
+ print(f"\n{'='*40}")
18
+ print(f"STEP: {step_name.upper().replace('_', ' ')}")
19
+ print(f"{'='*40}")
20
+
21
+ def log_success(self, message: str):
22
+ if self.verbose:
23
+ print(f"\n✅ {message}")
24
+
25
+ def log_error(self, message: str):
26
+ print(f"\n❌ ERROR: {message}")
main.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from logging import Logger
2
+ import os
3
+ from datetime import datetime
4
+ from typing import Dict, List, Optional
5
+ from dotenv import load_dotenv
6
+ from langchain_ollama import OllamaLLM
7
+ from langgraph.graph import END, StateGraph
8
+ from models import AgentState, DiagramType
9
+ from config import Config
10
+ from langchain_core.prompts import ChatPromptTemplate
11
+ from file_manager import FileManager
12
+ from agents.analysis import (
13
+ EntitiesAnalysis,
14
+ ActorsAnalysis,
15
+ SequenceAnalysis,
16
+ StateAnalysis,
17
+ ArchitectureAnalysis,
18
+ DeploymentAnalysis
19
+ )
20
+ from agents.code import (
21
+ ClassDiagramGenerator,
22
+ UseCaseDiagramGenerator,
23
+ SequenceDiagramGenerator,
24
+ ActivityDiagramGenerator,
25
+ ComponentDiagramGenerator,
26
+ DeploymentDiagramGenerator,
27
+ StateDiagramGenerator,
28
+ TimingDiagramGenerator,
29
+ ObjectDiagramGenerator
30
+ )
31
+ from fastapi import FastAPI, HTTPException
32
+ from fastapi.responses import StreamingResponse
33
+ from fastapi.middleware.cors import CORSMiddleware
34
+ from pydantic import BaseModel
35
+ import asyncio
36
+ from langchain_core.callbacks import StreamingStdOutCallbackHandler
37
+ import json
38
+
39
+ # Load environment variables
40
+ load_dotenv()
41
+
42
+ # Initialize FastAPI app
43
+ app = FastAPI()
44
+
45
+ # Add CORS middleware configuration
46
+ app.add_middleware(
47
+ CORSMiddleware,
48
+ allow_origins=["*"],
49
+ allow_credentials=True,
50
+ allow_methods=["*"],
51
+ allow_headers=["*"],
52
+ )
53
+
54
+
55
+ # Request model
56
+ class DiagramRequest(BaseModel):
57
+ project_name: str
58
+ project_description: str
59
+ selected_diagrams: List[int]
60
+ output_dir: Optional[str] = "output"
61
+
62
+
63
+
64
+
65
+ @app.post("/generate-diagrams")
66
+ async def generate_diagrams(request: DiagramRequest):
67
+ try:
68
+ initial_state = AgentState(
69
+ project_name=request.project_name,
70
+ project_description=request.project_description,
71
+ output_dir=request.output_dir,
72
+ selected_diagrams=request.selected_diagrams,
73
+ entities_classes="",
74
+ actors_use_cases="",
75
+ sequence_interactions="",
76
+ class_diagram="",
77
+ use_case_diagram="",
78
+ sequence_diagram="",
79
+ activity_diagram="",
80
+ component_diagram="",
81
+ deployment_diagram="",
82
+ state_diagram="",
83
+ timing_diagram="",
84
+ object_diagram=""
85
+ )
86
+
87
+
88
+ diagram_requirements = {
89
+ DiagramType.CLASS: ["extract_entities"],
90
+ DiagramType.USE_CASE: ["extract_actors"],
91
+ DiagramType.SEQUENCE: ["extract_actors", "extract_sequence"],
92
+ DiagramType.OBJECT: ["extract_entities"],
93
+ DiagramType.ACTIVITY: ["extract_actors"],
94
+ DiagramType.COMPONENT: ["extract_entities", "extract_architecture"],
95
+ DiagramType.DEPLOYMENT: ["extract_architecture", "extract_deployment"],
96
+ DiagramType.STATE: ["extract_entities", "extract_sequence", "extract_states"],
97
+ DiagramType.TIMING: ["extract_sequence"]
98
+ }
99
+
100
+
101
+ analysis_functions = {
102
+ "extract_entities": EntitiesAnalysis(),
103
+ "extract_actors": ActorsAnalysis(),
104
+ "extract_sequence": SequenceAnalysis(),
105
+ "extract_states": StateAnalysis(),
106
+ "extract_architecture": ArchitectureAnalysis(),
107
+ "extract_deployment": DeploymentAnalysis()
108
+ }
109
+
110
+ generation_functions = {
111
+ DiagramType.CLASS: ("generate_class", ClassDiagramGenerator()),
112
+ DiagramType.USE_CASE: ("generate_use_case", UseCaseDiagramGenerator()),
113
+ DiagramType.SEQUENCE: ("generate_sequence", SequenceDiagramGenerator()),
114
+ DiagramType.ACTIVITY: ("generate_activity", ActivityDiagramGenerator()),
115
+ DiagramType.COMPONENT: ("generate_component", ComponentDiagramGenerator()),
116
+ DiagramType.DEPLOYMENT: ("generate_deployment", DeploymentDiagramGenerator()),
117
+ DiagramType.STATE: ("generate_state", StateDiagramGenerator()),
118
+ DiagramType.TIMING: ("generate_timing", TimingDiagramGenerator()),
119
+ DiagramType.OBJECT: ("generate_object", ObjectDiagramGenerator())
120
+ }
121
+
122
+
123
+ selected_types = [DiagramType(d) for d in request.selected_diagrams]
124
+ added_analysis_steps = set()
125
+ steps = []
126
+
127
+ for diagram_type in selected_types:
128
+ required_analysis = diagram_requirements.get(diagram_type, [])
129
+ for step in required_analysis:
130
+ if step not in added_analysis_steps:
131
+ steps.append((step, analysis_functions[step]))
132
+ added_analysis_steps.add(step)
133
+
134
+ if diagram_type in generation_functions:
135
+ steps.append(generation_functions[diagram_type])
136
+
137
+
138
+ async def event_stream():
139
+ for step_name, step_func in steps:
140
+ # Determine if it's an analysis step or generation step
141
+ step_type = "analysis" if step_name in analysis_functions else "generation"
142
+ # Determine file extension based on step type
143
+ file_ext = "pu" if step_type == "generation" else "md"
144
+ yield f'data: {{"type": "step", "step_type": "{step_type}", "file_name": "{step_name}.{file_ext}"}}\n\n'
145
+ async for chunk in step_func(initial_state):
146
+ yield f"data: {chunk}\n\n"
147
+
148
+ return StreamingResponse(event_stream(), media_type="text/event-stream")
149
+
150
+ except Exception as e:
151
+ raise HTTPException(status_code=500, detail=str(e))
152
+
153
+
models.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import TypedDict, List
2
+ from enum import Enum
3
+
4
+ class DiagramType(Enum):
5
+ CLASS = 1
6
+ USE_CASE = 2
7
+ SEQUENCE = 3
8
+ OBJECT = 4
9
+ ACTIVITY = 5
10
+ COMPONENT = 6
11
+ DEPLOYMENT = 7
12
+ STATE = 8
13
+ TIMING = 9
14
+
15
+ class AgentState(TypedDict):
16
+ project_name: str
17
+ project_description: str
18
+ output_dir: str
19
+ selected_diagrams: List[int]
20
+ entities_classes: str
21
+ actors_use_cases: str
22
+ sequence_interactions: str
23
+ class_diagram: str
24
+ use_case_diagram: str
25
+ sequence_diagram: str
26
+ activity_diagram: str
27
+ component_diagram: str
28
+ deployment_diagram: str
29
+ state_diagram: str
30
+ timing_diagram: str
31
+ object_diagram: str
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ langgraph
2
+ langchain-community
3
+ langchain-core
4
+ pydantic
5
+ python-dotenv
6
+ langchain-ollama
7
+ fastapi
8
+ uvicorn
research_generator.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List, TypedDict
2
+ from langgraph.graph import StateGraph, END
3
+ from base_agent import BaseAgent
4
+ from models import AgentState
5
+ import os
6
+ from datetime import datetime
7
+ import json
8
+
9
+ class ResearchState(TypedDict):
10
+ subject: str
11
+ index: List[str]
12
+ content: Dict[str, str]
13
+ current_step: int
14
+
15
+ class ResearchGenerator(BaseAgent):
16
+ def __init__(self):
17
+ super().__init__()
18
+ self.workflow = self._create_workflow()
19
+ self.output_dir = "research_output"
20
+ os.makedirs(self.output_dir, exist_ok=True)
21
+ self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
22
+ self.research_dir = f"{self.output_dir}/research_{self.timestamp}"
23
+ os.makedirs(self.research_dir, exist_ok=True)
24
+
25
+ def _create_workflow(self):
26
+ # Create the graph
27
+ workflow = StateGraph(ResearchState)
28
+
29
+ # Add nodes
30
+ workflow.add_node("generate_index", self._generate_index)
31
+ workflow.add_node("generate_content", self._generate_content)
32
+ workflow.add_node("check_completion", self._check_completion)
33
+
34
+ # Add edges
35
+ workflow.add_edge("generate_index", "generate_content")
36
+ workflow.add_edge("generate_content", "check_completion")
37
+ workflow.add_conditional_edges(
38
+ "check_completion",
39
+ self._should_continue,
40
+ {
41
+ True: "generate_content",
42
+ False: END
43
+ }
44
+ )
45
+
46
+ # Set entry point
47
+ workflow.set_entry_point("generate_index")
48
+
49
+ return workflow.compile()
50
+
51
+ def _save_step(self, state: ResearchState, step_name: str, content: str):
52
+ """Save individual step content to a file"""
53
+ filename = f"{self.research_dir}/{step_name}.md"
54
+ with open(filename, 'w', encoding='utf-8') as f:
55
+ f.write(content)
56
+ print(f"Saved {step_name} to: {filename}")
57
+
58
+ def _save_final_markdown(self, state: ResearchState):
59
+ """Save the complete research in a single markdown file"""
60
+ filename = f"{self.research_dir}/complete_research.md"
61
+
62
+ with open(filename, 'w', encoding='utf-8') as f:
63
+ # Write title
64
+ f.write(f"# {state['subject']}\n\n")
65
+
66
+ # Write index
67
+ f.write("## الفهرس\n\n")
68
+ for item in state['index']:
69
+ f.write(f"{item}\n")
70
+ f.write("\n")
71
+
72
+ # Write content
73
+ f.write("## المحتوى\n\n")
74
+ for topic, content in state['content'].items():
75
+ f.write(f"### {topic}\n\n")
76
+ f.write(f"{content}\n\n")
77
+ f.write("---\n\n")
78
+
79
+ return filename
80
+
81
+ async def _generate_index(self, state: ResearchState) -> Dict:
82
+ prompt_template = """
83
+ Create a detailed research index for the following subject:
84
+
85
+ Subject: {subject}
86
+
87
+ Instructions:
88
+ 1. Create a comprehensive list of topics and subtopics
89
+ 2. Organize them in a logical order
90
+ 3. Include main sections and subsections
91
+ 4. Format as a numbered list
92
+
93
+ Example format:
94
+ 1. Main Topic 1
95
+ 1.1 Subtopic 1.1
96
+ 1.2 Subtopic 1.2
97
+ 2. Main Topic 2
98
+ 2.1 Subtopic 2.1
99
+ 2.2 Subtopic 2.2
100
+ """
101
+
102
+ result = await self._process(
103
+ state=state,
104
+ prompt_template=prompt_template,
105
+ output_key="index",
106
+ step_name="research index",
107
+ subject=state["subject"]
108
+ )
109
+
110
+ # Save index immediately
111
+ index_content = f"# {state['subject']}\n\n## الفهرس\n\n{result['index']}"
112
+ self._save_step(state, "index", index_content)
113
+
114
+ return {"index": result["index"].split("\n"), "current_step": 0}
115
+
116
+ async def _generate_content(self, state: ResearchState) -> Dict:
117
+ current_topic = state["index"][state["current_step"]]
118
+
119
+ prompt_template = """
120
+ Generate detailed content for the following research topic:
121
+
122
+ Subject: {subject}
123
+ Topic: {current_topic}
124
+
125
+ Instructions:
126
+ 1. Provide comprehensive information about the topic
127
+ 2. Include relevant examples and explanations
128
+ 3. Use clear and academic language
129
+ 4. Structure the content with proper paragraphs
130
+ 5. Include key points and supporting details
131
+ """
132
+
133
+ result = await self._process(
134
+ state=state,
135
+ prompt_template=prompt_template,
136
+ output_key="content",
137
+ step_name=f"content for {current_topic}",
138
+ subject=state["subject"],
139
+ current_topic=current_topic
140
+ )
141
+
142
+ # Update content dictionary
143
+ content = state.get("content", {})
144
+ content[current_topic] = result["content"]
145
+
146
+ # Save this topic's content immediately
147
+ topic_content = f"### {current_topic}\n\n{result['content']}"
148
+ self._save_step(state, f"topic_{state['current_step']}", topic_content)
149
+
150
+ return {
151
+ "content": content,
152
+ "current_step": state["current_step"] + 1
153
+ }
154
+
155
+ def _check_completion(self, state: ResearchState) -> Dict:
156
+ return state
157
+
158
+ def _should_continue(self, state: ResearchState) -> bool:
159
+ return state["current_step"] < len(state["index"])
160
+
161
+ async def generate_research(self, subject: str) -> Dict:
162
+ initial_state = {
163
+ "subject": subject,
164
+ "index": [],
165
+ "content": {},
166
+ "current_step": 0
167
+ }
168
+
169
+ result = await self.workflow.ainvoke(initial_state)
170
+
171
+ # Save final complete research
172
+ filename = self._save_final_markdown(result)
173
+ print(f"\nComplete research saved to: {filename}")
174
+
175
+ return result
176
+
177
+ async def astream(self, initial_state: ResearchState):
178
+ """Stream the workflow execution state."""
179
+ async for state in self.workflow.astream(initial_state):
180
+ yield state
181
+
182
+ # Example usage
183
+ if __name__ == "__main__":
184
+ import asyncio
185
+
186
+ async def main():
187
+ generator = ResearchGenerator()
188
+ research = await generator.generate_research("الذكاء الاصطناعي والاستثمار")
189
+
190
+ # Print summary
191
+ print("\n=== البحث المولد ===\n")
192
+ print("\n=== الفهرس ===")
193
+ for item in research["index"]:
194
+ print(item.strip())
195
+
196
+ print("\n=== المحتوى ===")
197
+ for topic, content in research["content"].items():
198
+ print(f"\n{topic.strip()}:")
199
+ print(content.strip())
200
+ print("-" * 50)
201
+
202
+ asyncio.run(main())