-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSpringbootAIAssistant.py
More file actions
2334 lines (1996 loc) · 110 KB
/
Copy pathSpringbootAIAssistant.py
File metadata and controls
2334 lines (1996 loc) · 110 KB
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import streamlit as st
import ollama
import os
import zipfile
import io
import re
import time
import json
import requests
import tempfile
import subprocess
import shutil
import platform
import base64
from datetime import datetime
from pygments import highlight
from pygments.lexers import JavaLexer, XmlLexer, PropertiesLexer, YamlLexer, JsonLexer
from pygments.formatters import HtmlFormatter
# Initialize session state variables
if "messages" not in st.session_state:
st.session_state.messages = []
if "generated_files" not in st.session_state:
st.session_state.generated_files = {}
if "test_files" not in st.session_state:
st.session_state.test_files = {}
if "file_categories" not in st.session_state:
st.session_state.file_categories = {
"main": [],
"test": [],
"config": []
}
if "logs" not in st.session_state:
st.session_state.logs = []
if "project_metadata" not in st.session_state:
st.session_state.project_metadata = {
"app_name": "spring-boot-app",
"group_id": "com.example",
"artifact_id": "demo",
"description": "Spring Boot Application",
"java_version": "17",
"spring_boot_version": "3.2.3",
}
if "code_execution_result" not in st.session_state:
st.session_state.code_execution_result = None
# Function to add log entries
def add_log(level, message):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"{timestamp} [{level}] {message}"
st.session_state.logs.append(log_entry)
print(log_entry) # Also print to console for debugging
# Function to extract code blocks from the response
def extract_code_blocks(text):
if not text or not text.strip():
add_log("WARNING", "No text to extract code blocks from")
return [], []
add_log("INFO", f"Extracting code blocks from text of length {len(text)}")
# Pattern to match code blocks with language specification
pattern = r"```(?:(java|xml|properties|yml|yaml|json))?\s*([\s\S]*?)```"
matches = re.finditer(pattern, text)
code_blocks = []
languages = []
for match in matches:
lang = match.group(1) if match.group(1) else "text"
code = match.group(2).strip()
code_blocks.append(code)
languages.append(lang)
add_log("INFO", f"Found {len(code_blocks)} code blocks")
return code_blocks, languages
# Function to detect file type based on content
def detect_file_type(content, language_hint=None):
if language_hint in ["java", "xml", "properties", "yml", "yaml", "json"]:
return language_hint
if "public class" in content or "import org.springframework" in content:
return "java"
elif "<project" in content or "<dependencies" in content or "<?xml" in content:
return "xml"
elif "spring.datasource.url" in content or "server.port" in content:
return "properties"
elif "---" in content and (":" in content) and (" " in content):
return "yaml"
elif content.strip().startswith("{") and content.strip().endswith("}"):
return "json"
else:
return "text"
# Function to suggest filename based on content
def suggest_filename(content, file_type):
if file_type == "java":
# Check if it's a test file
is_test = "import org.junit" in content or "@Test" in content
class_match = re.search(r"public\s+class\s+(\w+)", content)
if class_match:
class_name = class_match.group(1)
if is_test:
return f"{class_name}.java", "test"
else:
return f"{class_name}.java", "main"
else:
if is_test:
return "TestClass.java", "test"
else:
return "JavaClass.java", "main"
elif file_type == "xml" and "pom" in content.lower():
return "pom.xml", "config"
elif file_type == "xml" and "application-context" in content.lower():
return "application-context.xml", "config"
elif file_type == "xml":
return "config.xml", "config"
elif file_type == "properties":
if "test" in content.lower():
return "application-test.properties", "config"
else:
return "application.properties", "config"
elif file_type == "yaml" or file_type == "yml":
if "test" in content.lower():
return "application-test.yml", "config"
else:
return "application.yml", "config"
elif file_type == "json":
return "config.json", "config"
else:
return "file.txt", "config"
# Function to generate a zip file with all code files
def generate_zip_file(files_dict, include_spring_initializr=False):
zip_buffer = io.BytesIO()
# If Spring Initializr is requested, generate a base project first
if include_spring_initializr:
try:
# Prepare Spring Initializr request
initializr_url = "https://start.spring.io/starter.zip"
params = {
"type": "maven-project",
"language": "java",
"bootVersion": st.session_state.project_metadata["spring_boot_version"],
"baseDir": st.session_state.project_metadata["app_name"],
"groupId": st.session_state.project_metadata["group_id"],
"artifactId": st.session_state.project_metadata["artifact_id"],
"name": st.session_state.project_metadata["app_name"],
"description": st.session_state.project_metadata["description"],
"packageName": f"{st.session_state.project_metadata['group_id']}.{st.session_state.project_metadata['artifact_id']}",
"packaging": "jar",
"javaVersion": st.session_state.project_metadata["java_version"],
"dependencies": "web,data-jpa,lombok,actuator"
}
add_log("INFO", "Requesting base project from Spring Initializr")
response = requests.get(initializr_url, params=params)
if response.status_code == 200:
add_log("INFO", "Successfully got Spring Initializr template")
# Extract the Spring Initializr ZIP
with zipfile.ZipFile(io.BytesIO(response.content)) as init_zip:
# Create a new ZIP with the Spring Initializr files plus our generated files
with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file:
# Copy all files from Spring Initializr
for item in init_zip.infolist():
zip_file.writestr(item.filename, init_zip.read(item.filename))
# Now add our generated files, properly organizing them
organized_files = organize_project_files(files_dict)
base_package_path = f"{st.session_state.project_metadata['group_id']}.{st.session_state.project_metadata['artifact_id']}".replace('.', '/')
for directory, files in organized_files.items():
for filename, content in files.items():
if directory == "src/main/java" or directory == "src/test/java":
# Place Java files in the correct package structure
file_path = f"{directory}/{base_package_path}/{filename}"
# Update package declarations in the file
if detect_file_type(content) == "java":
content = update_package_declaration(content, f"{st.session_state.project_metadata['group_id']}.{st.session_state.project_metadata['artifact_id']}")
else:
file_path = f"{directory}/{filename}" if directory else filename
# Only write the file if it doesn't exist in the Spring Initializr template
# or if we're intentionally overwriting it (like pom.xml)
try:
init_zip.getinfo(file_path)
if filename == "pom.xml": # Always overwrite pom.xml with our version
zip_file.writestr(file_path, content)
except KeyError: # File doesn't exist in the original ZIP
zip_file.writestr(file_path, content)
return zip_buffer.getvalue()
else:
add_log("ERROR", f"Spring Initializr request failed with status code {response.status_code}")
except Exception as e:
add_log("ERROR", f"Failed to generate project with Spring Initializr: {str(e)}")
# Fallback to regular ZIP file generation
with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file:
organized_files = organize_project_files(files_dict)
for directory, files in organized_files.items():
for filename, content in files.items():
file_path = f"{directory}/{filename}" if directory else filename
zip_file.writestr(file_path, content)
return zip_buffer.getvalue()
# Function to update package declarations in Java files
def update_package_declaration(content, package_name):
# Check if the file already has a package declaration
package_match = re.search(r'^package\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*);', content, re.MULTILINE)
if package_match:
# Replace existing package declaration
return re.sub(r'^package\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*);',
f'package {package_name};', content, 1, re.MULTILINE)
else:
# Add package declaration at the beginning
return f'package {package_name};\n\n{content}'
# Function to get syntax highlighted code
def get_highlighted_code(code, file_type):
if file_type == "java":
lexer = JavaLexer()
elif file_type == "xml":
lexer = XmlLexer()
elif file_type == "properties":
lexer = PropertiesLexer()
elif file_type == "yaml" or file_type == "yml":
lexer = YamlLexer()
elif file_type == "json":
lexer = JsonLexer()
else:
# Default to Java for unknown types
lexer = JavaLexer()
formatter = HtmlFormatter(style="friendly")
highlighted = highlight(code, lexer, formatter)
css = formatter.get_style_defs('.highlight')
return highlighted, css
# Function to test Ollama connection directly
def test_ollama_connection():
try:
add_log("INFO", "Testing Ollama connection...")
response = requests.get("http://localhost:11434/api/tags", timeout=10)
if response.status_code == 200:
models = response.json().get("models", [])
model_names = [model.get("name") for model in models]
add_log("INFO", f"Ollama connection successful. Available models: {', '.join(model_names)}")
return True, model_names
else:
add_log("ERROR", f"Ollama returned status code {response.status_code}")
return False, []
except requests.exceptions.Timeout:
add_log("ERROR", "Ollama connection test timed out after 10 seconds")
return False, []
except Exception as e:
add_log("ERROR", f"Ollama connection test failed: {str(e)}")
return False, []
# Function to check if a specific model is loaded
def check_model_loaded(model_name):
try:
add_log("INFO", f"Checking if model '{model_name}' is loaded...")
response = requests.get(f"http://localhost:11434/api/show?name={model_name}", timeout=10)
if response.status_code == 200:
add_log("INFO", f"Model '{model_name}' is loaded")
return True
add_log("WARNING", f"Model '{model_name}' may not be loaded. Status code: {response.status_code}")
return False
except Exception as e:
add_log("ERROR", f"Error checking model status: {str(e)}")
return False
# Function to send a simple test message to check model is working
def test_model(model_name):
try:
add_log("INFO", f"Testing model '{model_name}' with a simple message...")
payload = {
"model": model_name,
"messages": [{"role": "user", "content": "Hello, are you working?"}],
"stream": False,
"options": {"temperature": 0.1}
}
response = requests.post("http://localhost:11434/api/chat", json=payload, timeout=30)
if response.status_code == 200:
try:
content = response.json().get("message", {}).get("content", "")
if content:
add_log("INFO", f"Model test successful. Response: {content[:50]}...")
return True, content[:100] + "..." if len(content) > 100 else content
else:
add_log("WARNING", "Model returned empty content")
return False, "Empty response"
except Exception as e:
add_log("ERROR", f"Error parsing model response: {str(e)}")
return False, f"Error parsing response: {str(e)}"
else:
add_log("ERROR", f"Model test failed with status code {response.status_code}")
return False, f"Failed with status code {response.status_code}"
except requests.exceptions.Timeout:
add_log("ERROR", f"Model test timed out after 30 seconds")
return False, "Request timed out after 30 seconds"
except Exception as e:
add_log("ERROR", f"Model test failed: {str(e)}")
return False, str(e)
# Function to generate tests for a Java file
def generate_tests(java_file_content, filename):
# Extract class name from the file
class_match = re.search(r"public\s+class\s+(\w+)", java_file_content)
if not class_match:
return None, "Couldn't identify a class name to test"
class_name = class_match.group(1)
test_class_name = f"{class_name}Test"
# Check if it's a Controller, Service, or Repository
is_controller = "@Controller" in java_file_content or "@RestController" in java_file_content
is_service = "@Service" in java_file_content
is_repository = "@Repository" in java_file_content
is_entity = "@Entity" in java_file_content
# Prepare system prompt based on the class type
if is_controller:
test_type = "MockMvc controller tests"
elif is_service:
test_type = "service unit tests with Mockito"
elif is_repository:
test_type = "repository tests with @DataJpaTest"
elif is_entity:
test_type = "entity class validation tests"
else:
test_type = "JUnit tests"
system_prompt = f"""
You are an expert Java Spring Boot test generator.
Generate complete {test_type} for the following Java class.
The test class should follow best practices and include meaningful assertions.
Format the response as pure Java code without any explanations or markdown.
"""
test_prompt = f"""
Generate Spring Boot tests for this class:
```java
{java_file_content}
```
Requirements:
1. Name the test class {test_class_name}
2. Use appropriate testing libraries (JUnit 5, Mockito, etc.)
3. Test all public methods with good coverage
4. Include proper mocking of dependencies
5. Follow standard test naming conventions (given/when/then)
6. Include detailed comments explaining each test case
"""
try:
with st.spinner(f"Generating tests for {filename}..."):
# Try direct API approach first
try:
add_log("INFO", "Generating tests using direct API call")
payload = {
"model": st.session_state.get("model", "mistral:latest"),
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": test_prompt}
],
"stream": False,
"options": {"temperature": st.session_state.get("temperature", 0.7)}
}
response = requests.post(
"http://localhost:11434/api/chat",
json=payload,
timeout=60
)
if response.status_code == 200:
test_code = response.json().get("message", {}).get("content", "")
if test_code:
# Extract only the Java code if it's wrapped in markdown code blocks
if "```java" in test_code:
code_match = re.search(r"```java\s*([\s\S]*?)```", test_code)
if code_match:
test_code = code_match.group(1).strip()
return test_code, test_class_name
else:
add_log("WARNING", "Empty response when generating tests")
except Exception as direct_e:
add_log("WARNING", f"Direct API test generation failed: {str(direct_e)}")
# Fall back to ollama library
add_log("INFO", "Falling back to ollama library for test generation")
response = ollama.chat(
model=st.session_state.get("model", "mistral:latest"),
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": test_prompt}
],
options={"temperature": st.session_state.get("temperature", 0.7)}
)
test_code = response['message']['content']
# Extract only the Java code if it's wrapped in markdown code blocks
if "```java" in test_code:
test_code = re.search(r"```java\s*([\s\S]*?)```", test_code)
if test_code:
test_code = test_code.group(1).strip()
return test_code, test_class_name
except Exception as e:
add_log("ERROR", f"Error generating tests: {str(e)}")
return None, f"Error generating tests: {str(e)}"
# Function to generate integration tests for a REST API
def generate_integration_tests():
# Create a prompt for generating comprehensive integration tests
files_content = ""
for filename, content in st.session_state.generated_files.items():
if filename.endswith(".java"):
files_content += f"\n\n{filename}:\n```java\n{content}\n```"
system_prompt = """
You are an expert Spring Boot integration test generator.
Generate a comprehensive integration test class that tests the REST APIs defined in the provided files.
The test should use MockMvc, @SpringBootTest, and include HTTP requests to test endpoints.
Format the response as pure Java code without any explanations or markdown.
"""
integration_test_prompt = f"""
Generate Spring Boot integration tests for the following files:
{files_content}
Requirements:
1. Name the test class ApplicationIntegrationTest
2. Use @SpringBootTest and TestRestTemplate or WebTestClient
3. Test all REST API endpoints
4. Include tests for success, validation and error conditions
5. Add appropriate assertions for response status and body
6. Include detailed comments explaining the test setup and assertions
"""
try:
with st.spinner("Generating integration tests..."):
try:
add_log("INFO", "Generating integration tests using direct API call")
payload = {
"model": st.session_state.get("model", "mistral:latest"),
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": integration_test_prompt}
],
"stream": False,
"options": {"temperature": st.session_state.get("temperature", 0.7)}
}
response = requests.post(
"http://localhost:11434/api/chat",
json=payload,
timeout=120
)
if response.status_code == 200:
test_code = response.json().get("message", {}).get("content", "")
if test_code:
# Extract only the Java code if it's wrapped in markdown code blocks
if "```java" in test_code:
code_match = re.search(r"```java\s*([\s\S]*?)```", test_code)
if code_match:
test_code = code_match.group(1).strip()
return test_code, "ApplicationIntegrationTest"
else:
add_log("WARNING", "Empty response when generating integration tests")
except Exception as direct_e:
add_log("WARNING", f"Direct API integration test generation failed: {str(direct_e)}")
# Fall back to ollama library
add_log("INFO", "Falling back to ollama library for integration test generation")
response = ollama.chat(
model=st.session_state.get("model", "mistral:latest"),
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": integration_test_prompt}
],
options={"temperature": st.session_state.get("temperature", 0.7)}
)
test_code = response['message']['content']
# Extract only the Java code if it's wrapped in markdown code blocks
if "```java" in test_code:
test_code = re.search(r"```java\s*([\s\S]*?)```", test_code)
if test_code:
test_code = test_code.group(1).strip()
return test_code, "ApplicationIntegrationTest"
except Exception as e:
add_log("ERROR", f"Error generating integration tests: {str(e)}")
return None, f"Error generating integration tests: {str(e)}"
# Function to generate documentation for a Spring Boot project
def generate_documentation():
# Collect all generated files for the documentation
files_content = ""
for filename, content in st.session_state.generated_files.items():
files_content += f"\n\n{filename}:\n```{detect_file_type(content)}\n{content}\n```"
system_prompt = """
You are an expert Spring Boot developer and technical writer.
Generate comprehensive documentation for the provided Spring Boot project.
The documentation should include:
1. Overview of the project architecture
2. API documentation for all REST endpoints
3. Description of key components and their relationships
4. Setup and configuration instructions
5. Examples of API usage with curl commands
Format the response in clean, well-structured Markdown.
"""
documentation_prompt = f"""
Create comprehensive documentation for this Spring Boot project:
{files_content}
Project Details:
- Name: {st.session_state.project_metadata['app_name']}
- Group ID: {st.session_state.project_metadata['group_id']}
- Artifact ID: {st.session_state.project_metadata['artifact_id']}
- Description: {st.session_state.project_metadata['description']}
- Java Version: {st.session_state.project_metadata['java_version']}
- Spring Boot Version: {st.session_state.project_metadata['spring_boot_version']}
Include:
1. Project overview and architecture diagram (described in text)
2. API documentation with endpoints, methods, request/response examples
3. Database schema description (if applicable)
4. Setup and configuration guide
5. Sample curl commands for testing APIs
"""
try:
with st.spinner("Generating project documentation..."):
try:
add_log("INFO", "Generating documentation using direct API call")
payload = {
"model": st.session_state.get("model", "mistral:latest"),
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": documentation_prompt}
],
"stream": False,
"options": {"temperature": st.session_state.get("temperature", 0.7)}
}
response = requests.post(
"http://localhost:11434/api/chat",
json=payload,
timeout=120
)
if response.status_code == 200:
documentation = response.json().get("message", {}).get("content", "")
if documentation:
return documentation
else:
add_log("WARNING", "Empty response when generating documentation")
except Exception as direct_e:
add_log("WARNING", f"Direct API documentation generation failed: {str(direct_e)}")
# Fall back to ollama library
add_log("INFO", "Falling back to ollama library for documentation generation")
response = ollama.chat(
model=st.session_state.get("model", "mistral:latest"),
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": documentation_prompt}
],
options={"temperature": st.session_state.get("temperature", 0.7)}
)
documentation = response['message']['content']
return documentation
except Exception as e:
add_log("ERROR", f"Error generating documentation: {str(e)}")
return f"Error generating documentation: {str(e)}"
# Function to organize files in a project structure
def organize_project_files(files):
project_structure = {
"src/main/java": {},
"src/main/resources": {},
"src/test/java": {},
"src/test/resources": {},
"": {} # Root directory
}
for filename, content in files.items():
file_type = detect_file_type(content)
if file_type == "java" and ("@Test" in content or "import org.junit" in content):
project_structure["src/test/java"][filename] = content
elif file_type == "java":
project_structure["src/main/java"][filename] = content
elif file_type in ["properties", "yml", "yaml", "json"] and "test" in filename.lower():
project_structure["src/test/resources"][filename] = content
elif file_type in ["properties", "yml", "yaml", "json"]:
project_structure["src/main/resources"][filename] = content
elif filename == "pom.xml":
project_structure[""][filename] = content
elif filename.lower() == "readme.md":
project_structure[""][filename] = content
elif filename.lower() == "dockerfile":
project_structure[""][filename] = content
else:
project_structure[""][filename] = content
return project_structure
# Function to generate Docker files for the project
def generate_docker_files():
system_prompt = """
You are an expert in containerization for Spring Boot applications.
Generate a Dockerfile and docker-compose.yml file for a Spring Boot application.
The Dockerfile should follow best practices for Java applications.
Include multi-stage build for optimized container size.
The docker-compose.yml should include the application and any necessary services.
"""
docker_prompt = f"""
Create a Dockerfile and docker-compose.yml for this Spring Boot project:
Project Details:
- Name: {st.session_state.project_metadata['app_name']}
- Java Version: {st.session_state.project_metadata['java_version']}
- Spring Boot Version: {st.session_state.project_metadata['spring_boot_version']}
The Docker setup should:
1. Use multi-stage build for optimization
2. Include appropriate JVM tuning options
3. Set up the application with proper security practices
4. Include any necessary databases or services based on the application
5. Configure health checks and proper networking
The application uses Spring Boot {st.session_state.project_metadata['spring_boot_version']} and Java {st.session_state.project_metadata['java_version']}.
"""
try:
with st.spinner("Generating Docker configuration..."):
try:
add_log("INFO", "Generating Docker files using direct API call")
payload = {
"model": st.session_state.get("model", "mistral:latest"),
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": docker_prompt}
],
"stream": False,
"options": {"temperature": st.session_state.get("temperature", 0.7)}
}
response = requests.post(
"http://localhost:11434/api/chat",
json=payload,
timeout=60
)
if response.status_code == 200:
docker_response = response.json().get("message", {}).get("content", "")
if docker_response:
# Extract Dockerfile and docker-compose.yml
dockerfile_match = re.search(r"```dockerfile\s*([\s\S]*?)```", docker_response)
compose_match = re.search(r"```(yaml|yml)\s*([\s\S]*?)```", docker_response)
dockerfile = dockerfile_match.group(1).strip() if dockerfile_match else ""
docker_compose = compose_match.group(2).strip() if compose_match else ""
return dockerfile, docker_compose
else:
add_log("WARNING", "Empty response when generating Docker files")
except Exception as direct_e:
add_log("WARNING", f"Direct API Docker files generation failed: {str(direct_e)}")
# Fall back to ollama library
add_log("INFO", "Falling back to ollama library for Docker files generation")
response = ollama.chat(
model=st.session_state.get("model", "mistral:latest"),
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": docker_prompt}
],
options={"temperature": st.session_state.get("temperature", 0.7)}
)
docker_response = response['message']['content']
# Extract Dockerfile and docker-compose.yml
dockerfile_match = re.search(r"```dockerfile\s*([\s\S]*?)```", docker_response)
compose_match = re.search(r"```(yaml|yml)\s*([\s\S]*?)```", docker_response)
dockerfile = dockerfile_match.group(1).strip() if dockerfile_match else ""
docker_compose = compose_match.group(2).strip() if compose_match else ""
return dockerfile, docker_compose
except Exception as e:
add_log("ERROR", f"Error generating Docker files: {str(e)}")
return None, f"Error generating Docker files: {str(e)}"
# Function to run the Spring Boot project locally (simplified for demo)
def run_project_locally():
result = {"success": False, "message": "", "output": ""}
try:
# Create a temporary directory
with tempfile.TemporaryDirectory() as temp_dir:
add_log("INFO", f"Created temporary directory: {temp_dir}")
# Generate ZIP file with all project files
all_files = {**st.session_state.generated_files, **st.session_state.test_files}
zip_data = generate_zip_file(all_files, include_spring_initializr=True)
# Extract ZIP to temporary directory
with io.BytesIO(zip_data) as zip_buffer:
with zipfile.ZipFile(zip_buffer) as zip_file:
zip_file.extractall(temp_dir)
add_log("INFO", "Extracted project files to temporary directory")
# Check if Maven or Gradle is installed
maven_command = "mvn" if platform.system() != "Windows" else "mvn.cmd"
try:
# Run Maven commands
add_log("INFO", "Attempting to build the project with Maven")
# Change to project directory
project_dir = os.path.join(temp_dir, st.session_state.project_metadata["app_name"])
if not os.path.exists(project_dir):
project_dir = temp_dir # Fallback if the app_name directory doesn't exist
add_log("INFO", f"Using project directory: {project_dir}")
# Compile project
compile_cmd = [maven_command, "clean", "package", "-DskipTests"]
add_log("INFO", f"Running Maven command: {' '.join(compile_cmd)}")
process = subprocess.Popen(
compile_cmd,
cwd=project_dir,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
stdout, stderr = process.communicate(timeout=300) # 5 minute timeout
if process.returncode != 0:
add_log("ERROR", f"Maven build failed: {stderr}")
return {"success": False, "message": "Build failed", "output": stderr}
add_log("INFO", "Maven build successful")
# Find the generated JAR file
target_dir = os.path.join(project_dir, "target")
jar_files = [f for f in os.listdir(target_dir) if f.endswith(".jar") and not f.endswith("-sources.jar")]
if not jar_files:
add_log("ERROR", "No JAR file found after build")
return {"success": False, "message": "No JAR file found after build", "output": stdout}
jar_file = os.path.join(target_dir, jar_files[0])
add_log("INFO", f"Found JAR file: {jar_file}")
# Run the application
run_cmd = ["java", "-jar", jar_file]
add_log("INFO", f"Running command: {' '.join(run_cmd)}")
# Instead of actually running it (which would block the Streamlit app),
# we'll just return success for demonstration purposes
return {
"success": True,
"message": "Project built successfully!",
"output": f"Build Output:\n{stdout}\n\nTo run the application:\njava -jar {jar_files[0]}"
}
except Exception as e:
add_log("ERROR", f"Error building or running project: {str(e)}")
return {"success": False, "message": f"Error: {str(e)}", "output": ""}
except Exception as e:
add_log("ERROR", f"Error setting up project directory: {str(e)}")
return {"success": False, "message": f"Error setting up project: {str(e)}", "output": ""}
# Function for generating an OpenAPI specification
def generate_openapi_spec():
# Collect all controller files
controller_files = {}
for filename, content in st.session_state.generated_files.items():
if filename.endswith(".java") and ("@RestController" in content or "@Controller" in content):
controller_files[filename] = content
if not controller_files:
return "No controller files found in the project"
system_prompt = """
You are an expert in OpenAPI specification generation.
Create a complete OpenAPI 3.0 specification for the Spring Boot REST controllers provided.
The specification should include all endpoints, request/response schemas, and proper documentation.
Format the response as a YAML OpenAPI specification.
"""
# Create a prompt with all controller files
controllers_content = ""
for filename, content in controller_files.items():
controllers_content += f"\n\n{filename}:\n```java\n{content}\n```"
openapi_prompt = f"""
Generate an OpenAPI 3.0 specification for the following Spring Boot REST controllers:
{controllers_content}
Project Details:
- Name: {st.session_state.project_metadata['app_name']}
- Description: {st.session_state.project_metadata['description']}
- Version: 1.0.0
Requirements:
1. Include all REST endpoints with proper paths, methods, and parameters
2. Define request and response schemas based on the Java objects used
3. Add detailed descriptions for all operations and schemas
4. Include example values where appropriate
5. Format as a valid OpenAPI 3.0 YAML specification
"""
try:
with st.spinner("Generating OpenAPI specification..."):
try:
add_log("INFO", "Generating OpenAPI specification using direct API call")
payload = {
"model": st.session_state.get("model", "mistral:latest"),
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": openapi_prompt}
],
"stream": False,
"options": {"temperature": st.session_state.get("temperature", 0.7)}
}
response = requests.post(
"http://localhost:11434/api/chat",
json=payload,
timeout=60
)
if response.status_code == 200:
openapi_spec = response.json().get("message", {}).get("content", "")
if openapi_spec:
# Extract the YAML content if wrapped in code blocks
yaml_match = re.search(r"```(yaml|yml)\s*([\s\S]*?)```", openapi_spec)
if yaml_match:
openapi_spec = yaml_match.group(2).strip()
return openapi_spec
else:
add_log("WARNING", "Empty response when generating OpenAPI specification")
except Exception as direct_e:
add_log("WARNING", f"Direct API OpenAPI generation failed: {str(direct_e)}")
# Fall back to ollama library
add_log("INFO", "Falling back to ollama library for OpenAPI generation")
response = ollama.chat(
model=st.session_state.get("model", "mistral:latest"),
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": openapi_prompt}
],
options={"temperature": st.session_state.get("temperature", 0.7)}
)
openapi_spec = response['message']['content']
# Extract the YAML content if wrapped in code blocks
yaml_match = re.search(r"```(yaml|yml)\s*([\s\S]*?)```", openapi_spec)
if yaml_match:
openapi_spec = yaml_match.group(2).strip()
return openapi_spec
except Exception as e:
add_log("ERROR", f"Error generating OpenAPI specification: {str(e)}")
return f"Error generating OpenAPI specification: {str(e)}"
# Function to generate GitHub Actions workflow for CI/CD
def generate_github_actions():
system_prompt = """
You are an expert in CI/CD for Java Spring Boot applications.
Create a complete GitHub Actions workflow file for building, testing, and deploying a Spring Boot application.
The workflow should include proper caching, testing, and deployment steps.
"""
github_actions_prompt = f"""
Generate a GitHub Actions workflow file for this Spring Boot project:
Project Details:
- Name: {st.session_state.project_metadata['app_name']}
- Java Version: {st.session_state.project_metadata['java_version']}
- Spring Boot Version: {st.session_state.project_metadata['spring_boot_version']}
- Build Tool: Maven
Requirements:
1. Create a workflow that builds and tests the application on push to main and pull requests
2. Include proper Java setup with caching for Maven dependencies
3. Run unit and integration tests
4. Build and publish a Docker image
5. Add a deployment step (to a staging environment)
6. Include security scanning for vulnerabilities
7. Format as a YAML file for .github/workflows/ci-cd.yml
"""
try:
with st.spinner("Generating GitHub Actions workflow..."):
try:
add_log("INFO", "Generating GitHub Actions workflow using direct API call")
payload = {
"model": st.session_state.get("model", "mistral:latest"),
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": github_actions_prompt}
],
"stream": False,
"options": {"temperature": st.session_state.get("temperature", 0.7)}
}
response = requests.post(
"http://localhost:11434/api/chat",
json=payload,
timeout=60
)
if response.status_code == 200:
workflow = response.json().get("message", {}).get("content", "")
if workflow:
# Extract the YAML content if wrapped in code blocks
yaml_match = re.search(r"```(yaml|yml)\s*([\s\S]*?)```", workflow)
if yaml_match:
workflow = yaml_match.group(2).strip()
return workflow
else:
add_log("WARNING", "Empty response when generating GitHub Actions workflow")
except Exception as direct_e:
add_log("WARNING", f"Direct API GitHub Actions workflow generation failed: {str(direct_e)}")
# Fall back to ollama library
add_log("INFO", "Falling back to ollama library for GitHub Actions workflow generation")
response = ollama.chat(
model=st.session_state.get("model", "mistral:latest"),
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": github_actions_prompt}
],
options={"temperature": st.session_state.get("temperature", 0.7)}
)
workflow = response['message']['content']
# Extract the YAML content if wrapped in code blocks
yaml_match = re.search(r"```(yaml|yml)\s*([\s\S]*?)```", workflow)
if yaml_match:
workflow = yaml_match.group(2).strip()
return workflow
except Exception as e:
add_log("ERROR", f"Error generating GitHub Actions workflow: {str(e)}")
return f"Error generating GitHub Actions workflow: {str(e)}"
# Set up the Streamlit UI
st.set_page_config(page_title="Java Spring Boot Developer Chatbot", page_icon="🤖", layout="wide")
# Custom CSS for enhanced UI
st.markdown("""
<style>
.main-header {
font-size: 2.5rem;
color: #3366ff;
margin-bottom: 0;
}
.sub-header {
font-size: 1.1rem;
color: #666;
margin-bottom: 2rem;
}
.stTabs [data-baseweb="tab-list"] {
gap: 10px;