mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-28 18:59:47 +00:00
Implemented writing generated code to disk
This commit is contained in:
parent
170cda428c
commit
cbb0a36198
@ -337,5 +337,13 @@ else
|
|||||||
AI has made in which files.
|
AI has made in which files.
|
||||||
</MudText>
|
</MudText>
|
||||||
|
|
||||||
|
<MudText Typo="Typo.body1" Class="mb-2">
|
||||||
|
When you rebuild / re-generate the ERI server code, AI Studio proceeds as follows: All files generated last time will be deleted. All
|
||||||
|
other files you have created remain. Then, the AI generates the new files. <b>But beware:</b> It may happen that the AI generates a
|
||||||
|
file this time that you manually created last time. In this case, your manually created file will then be overwritten. Therefore,
|
||||||
|
you should always create a Git repository and commit or revert all changes before using this assistant. With a diff visualization,
|
||||||
|
you can immediately see where the AI has made changes. It is best to use an IDE suitable for your selected language for this purpose.
|
||||||
|
</MudText>
|
||||||
|
|
||||||
<MudTextSwitch Label="Should we write the generated code to the file system?" Disabled="@this.IsNoneERIServerSelected" @bind-Value="@this.writeToFilesystem" LabelOn="Yes, please write or update all generated code to the file system" LabelOff="No, just show me the code" />
|
<MudTextSwitch Label="Should we write the generated code to the file system?" Disabled="@this.IsNoneERIServerSelected" @bind-Value="@this.writeToFilesystem" LabelOn="Yes, please write or update all generated code to the file system" LabelOff="No, just show me the code" />
|
||||||
<SelectDirectory Label="Base directory where to write the code" @bind-Directory="@this.baseDirectory" Disabled="@(this.IsNoneERIServerSelected || !this.writeToFilesystem)" DirectoryDialogTitle="Select the target directory for the ERI server"/>
|
<SelectDirectory Label="Base directory where to write the code" @bind-Directory="@this.baseDirectory" Disabled="@(this.IsNoneERIServerSelected || !this.writeToFilesystem)" DirectoryDialogTitle="Select the target directory for the ERI server"/>
|
||||||
|
@ -335,6 +335,7 @@ public partial class AssistantERI : AssistantBaseCore
|
|||||||
this.additionalLibraries = string.Empty;
|
this.additionalLibraries = string.Empty;
|
||||||
this.writeToFilesystem = false;
|
this.writeToFilesystem = false;
|
||||||
this.baseDirectory = string.Empty;
|
this.baseDirectory = string.Empty;
|
||||||
|
this.previouslyGeneratedFiles = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,6 +367,7 @@ public partial class AssistantERI : AssistantBaseCore
|
|||||||
this.additionalLibraries = this.selectedERIServer.AdditionalLibraries;
|
this.additionalLibraries = this.selectedERIServer.AdditionalLibraries;
|
||||||
this.writeToFilesystem = this.selectedERIServer.WriteToFilesystem;
|
this.writeToFilesystem = this.selectedERIServer.WriteToFilesystem;
|
||||||
this.baseDirectory = this.selectedERIServer.BaseDirectory;
|
this.baseDirectory = this.selectedERIServer.BaseDirectory;
|
||||||
|
this.previouslyGeneratedFiles = this.selectedERIServer.PreviouslyGeneratedFiles;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,6 +424,7 @@ public partial class AssistantERI : AssistantBaseCore
|
|||||||
this.selectedERIServer.AdditionalLibraries = this.additionalLibraries;
|
this.selectedERIServer.AdditionalLibraries = this.additionalLibraries;
|
||||||
this.selectedERIServer.WriteToFilesystem = this.writeToFilesystem;
|
this.selectedERIServer.WriteToFilesystem = this.writeToFilesystem;
|
||||||
this.selectedERIServer.BaseDirectory = this.baseDirectory;
|
this.selectedERIServer.BaseDirectory = this.baseDirectory;
|
||||||
|
this.selectedERIServer.PreviouslyGeneratedFiles = this.previouslyGeneratedFiles;
|
||||||
await this.SettingsManager.StoreSettings();
|
await this.SettingsManager.StoreSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,6 +451,7 @@ public partial class AssistantERI : AssistantBaseCore
|
|||||||
private string additionalLibraries = string.Empty;
|
private string additionalLibraries = string.Empty;
|
||||||
private bool writeToFilesystem;
|
private bool writeToFilesystem;
|
||||||
private string baseDirectory = string.Empty;
|
private string baseDirectory = string.Empty;
|
||||||
|
private List<string> previouslyGeneratedFiles = new();
|
||||||
|
|
||||||
private bool AreServerPresetsBlocked => !this.SettingsManager.ConfigurationData.ERI.PreselectOptions;
|
private bool AreServerPresetsBlocked => !this.SettingsManager.ConfigurationData.ERI.PreselectOptions;
|
||||||
|
|
||||||
@ -905,6 +909,17 @@ public partial class AssistantERI : AssistantBaseCore
|
|||||||
yield return file;
|
yield return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("""
|
||||||
|
\s*#+\s+[\w\\/.]+\s*```\w*\s+([\s\w\W]+)\s*```\s*
|
||||||
|
""", RegexOptions.Singleline)]
|
||||||
|
private static partial Regex CodeExtractRegex();
|
||||||
|
|
||||||
|
private string ExtractCode(string markdown)
|
||||||
|
{
|
||||||
|
var match = CodeExtractRegex().Match(markdown);
|
||||||
|
return match.Success ? match.Groups[1].Value : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task GenerateServer()
|
private async Task GenerateServer()
|
||||||
{
|
{
|
||||||
if(this.IsNoneERIServerSelected)
|
if(this.IsNoneERIServerSelected)
|
||||||
@ -959,6 +974,32 @@ public partial class AssistantERI : AssistantBaseCore
|
|||||||
}
|
}
|
||||||
""", true);
|
""", true);
|
||||||
var fileListAnswer = await this.AddAIResponseAsync(time, true);
|
var fileListAnswer = await this.AddAIResponseAsync(time, true);
|
||||||
|
|
||||||
|
// Is this an update of the ERI server? If so, we need to delete the previously generated files:
|
||||||
|
if (this.writeToFilesystem && this.previouslyGeneratedFiles.Count > 0 && !string.IsNullOrWhiteSpace(fileListAnswer))
|
||||||
|
{
|
||||||
|
foreach (var file in this.previouslyGeneratedFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(file))
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
this.Logger.LogInformation($"The previously created file '{file}' was deleted.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.Logger.LogWarning($"The previously created file '{file}' could not be found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
this.Logger.LogWarning($"The previously created file '{file}' could not be deleted: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var generatedFiles = new List<string>();
|
||||||
foreach (var file in this.ExtractFiles(fileListAnswer))
|
foreach (var file in this.ExtractFiles(fileListAnswer))
|
||||||
{
|
{
|
||||||
this.Logger.LogInformation($"The LLM want to create the file: '{file}'");
|
this.Logger.LogInformation($"The LLM want to create the file: '{file}'");
|
||||||
@ -978,9 +1019,69 @@ public partial class AssistantERI : AssistantBaseCore
|
|||||||
content of the file
|
content of the file
|
||||||
```
|
```
|
||||||
""", true);
|
""", true);
|
||||||
await this.AddAIResponseAsync(time);
|
var generatedCodeMarkdown = await this.AddAIResponseAsync(time);
|
||||||
|
if (this.writeToFilesystem)
|
||||||
|
{
|
||||||
|
var desiredFilePath = Path.Join(this.baseDirectory, file);
|
||||||
|
|
||||||
|
// Security check: ensure that the desired file path is inside the base directory.
|
||||||
|
// We cannot trust the beginning of the file path because it would be possible
|
||||||
|
// to escape by using `..` in the file path.
|
||||||
|
if (!desiredFilePath.StartsWith(this.baseDirectory, StringComparison.InvariantCultureIgnoreCase) || desiredFilePath.Contains(".."))
|
||||||
|
this.Logger.LogWarning($"The file path '{desiredFilePath}' is may not inside the base directory '{this.baseDirectory}'.");
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var code = this.ExtractCode(generatedCodeMarkdown);
|
||||||
|
if (string.IsNullOrWhiteSpace(code))
|
||||||
|
this.Logger.LogWarning($"The file content for '{desiredFilePath}' is empty or was not found.");
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
// Ensure that the directory exists:
|
||||||
|
var fileDirectory = Path.GetDirectoryName(desiredFilePath);
|
||||||
|
if (fileDirectory is null)
|
||||||
|
this.Logger.LogWarning($"The file path '{desiredFilePath}' does not contain a directory.");
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
generatedFiles.Add(desiredFilePath);
|
||||||
|
var fileDirectoryInfo = new DirectoryInfo(fileDirectory);
|
||||||
|
if(!fileDirectoryInfo.Exists)
|
||||||
|
{
|
||||||
|
fileDirectoryInfo.Create();
|
||||||
|
this.Logger.LogInformation($"The directory '{fileDirectory}' was created.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the file to the file system:
|
||||||
|
await File.WriteAllTextAsync(desiredFilePath, code, Encoding.UTF8);
|
||||||
|
this.Logger.LogInformation($"The file '{desiredFilePath}' was created.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.writeToFilesystem)
|
||||||
|
{
|
||||||
|
this.previouslyGeneratedFiles = generatedFiles;
|
||||||
|
this.selectedERIServer!.PreviouslyGeneratedFiles = generatedFiles;
|
||||||
|
await this.SettingsManager.StoreSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// ---------------------------------
|
||||||
|
// Ask the AI for further steps
|
||||||
|
// ---------------------------------
|
||||||
|
//
|
||||||
|
time = this.AddUserRequest("""
|
||||||
|
Thank you for implementing the files. Please explain what the next steps are.
|
||||||
|
The goal is for the code to compile and the server to start. We assume that
|
||||||
|
the developer has installed the compiler. We will not consider DevOps tools
|
||||||
|
like Docker.
|
||||||
|
""", true);
|
||||||
|
await this.AddAIResponseAsync(time);
|
||||||
await this.SendToAssistant(Tools.Components.CHAT, default);
|
await this.SendToAssistant(Tools.Components.CHAT, default);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -99,10 +99,15 @@ public sealed class DataERIServer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Do you want to write all generated code to the filesystem?
|
/// Do you want to write all generated code to the filesystem?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool WriteToFilesystem { get; set; } = false;
|
public bool WriteToFilesystem { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The base directory where to write the generated code to.
|
/// The base directory where to write the generated code to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string BaseDirectory { get; set; } = string.Empty;
|
public string BaseDirectory { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// We save which files were generated previously.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> PreviouslyGeneratedFiles { get; set; } = new();
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user