Skip to content

Commit

Permalink
Add SKonsole configuration command and Spectre.Console package
Browse files Browse the repository at this point in the history
Summary:
This pull request updates the SKonsole project with the addition of a new configuration command and the Spectre.Console package. The new "config" command allows users to manage configuration settings for the application, including getting and setting specific configuration keys. The configuration values are stored in a file named ".skonsole". Additionally, the Spectre.Console package is added as a new package reference to enhance the user interface with interactive prompts and colored output.

Changes:
- Added a new command, "config", to the SKonsole application.
- Implemented subcommands for getting and setting configuration values.
- Created a ConfigurationProvider class to handle reading and writing configuration values.
- Added a new file, "ConfigCommand.cs", to handle the "config" command logic.
- Added a new file, "ConfigurationProvider.cs", to handle reading and writing configuration values.
- Modified the "Program.cs" file to include the new "config" command and its subcommands.
- Updated the "ConfigCommand.cs" file to handle getting and setting configuration values.
- Updated the "Program.cs" file to use the ConfigurationProvider class to get and set configuration values.
- Added validation and error handling for empty configuration values.
- Updated the "Program.cs" file to use the Spectre.Console library for interactive prompts and colored output.

Note: This pull request focuses on adding the new "config" command and its related functionality, as well as integrating the Spectre.Console package for improved user interface. Other changes in the diff are related to existing functionality and are not directly related to the new command.

Co-authored-by: xbotter <xbotter@users.noreply.github.com>
  • Loading branch information
lemillermicrosoft and xbotter authored Aug 15, 2023
2 parents b69aed7 + 926060d commit a112bd1
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 13 deletions.
102 changes: 102 additions & 0 deletions apps/SKonsole/Commands/ConfigCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using Newtonsoft.Json.Linq;
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SKonsole.Commands
{
public class ConfigCommand : Command
{
private readonly ConfigurationProvider _config;

public ConfigCommand(ConfigurationProvider config) : base("config", "skonsole configuration")
{
this._config = config;
Add(ConfigGetCommand());
Add(ConfigSetCommand());

this.SetHandler(async context => await RunConfigAsync(context.GetCancellationToken()));
}

Command ConfigGetCommand()
{
var keyArgument = new Argument<string>("key", "configuration key");

var getCommand = new Command("get", "get configuration value");

getCommand.AddArgument(keyArgument);

getCommand.SetHandler((key) =>
{
var value = _config.Get(key);
if (value == null)
{
AnsiConsole.MarkupLine($"[red]Configuration key '{key}' not found.[/]");
}
else
{
AnsiConsole.MarkupLine($"[green]{key}[/]: {value}");
}
}, keyArgument);
return getCommand;
}


Command ConfigSetCommand()
{
var keyArgument = new Argument<string>("key", "configuration key");
var valueArgument = new Argument<string>("value", "configuration value");

var setCommand = new Command("set", "set configuration value");
setCommand.AddArgument(keyArgument);
setCommand.AddArgument(valueArgument);

setCommand.SetHandler(_config.SaveConfig, keyArgument, valueArgument);
return setCommand;
}

static async Task RunConfigAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var keys = new[]
{
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME",
"AZURE_OPENAI_API_ENDPOINT",
"AZURE_OPENAI_API_KEY"
};

var config = new ConfigurationProvider();
var configKey = await new SelectionPrompt<string>()
.Title("Select key to config:")
.AddChoices(keys)
.ShowAsync(AnsiConsole.Console, token);

var currentValue = config.Get(configKey);

var value = await new TextPrompt<string>($"Set value for [green]{configKey}[/]")
.DefaultValue(currentValue ?? string.Empty)
.HideDefaultValue()
.Validate((value) =>
{
if (string.IsNullOrWhiteSpace(value))
{
return ValidationResult.Error("[red]Value cannot be empty[/]");
}
return ValidationResult.Success();
})
.AllowEmpty()
.ShowAsync(AnsiConsole.Console, token);
if (!string.IsNullOrWhiteSpace(value))
{
await config.SaveConfig(configKey, value.Trim());
}
}
}
}

}
60 changes: 60 additions & 0 deletions apps/SKonsole/ConfigurationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Microsoft.Extensions.Configuration;
using System.Text.Json;

public class ConfigurationProvider
{
public static ConfigurationProvider Instance = new();

const string _file = ".skonsole";

private readonly string _path;
private readonly IConfiguration _configuration;
private readonly Dictionary<string, string?> _config = new();

public ConfigurationProvider()
{
var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
_path = Path.Combine(userProfile, _file);

if (File.Exists(_path))
{
_config = FromJson<Dictionary<string, string?>>(File.ReadAllText(_path)) ?? new();
}

_configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddInMemoryCollection(_config)
.Build();
}

public async Task SaveConfig(string key, string value)
{
_config[key] = value;

await File.WriteAllTextAsync(_path, ToJson(_config));
}

public string? Get(string key)
{
return _configuration[key];
}

private static string ToJson<T>(T obj)
{
return JsonSerializer.Serialize(obj);
}

private static T? FromJson<T>(string json)
{
if (json == null)
return default;
try
{
return JsonSerializer.Deserialize<T>(json);
}
catch
{
return default;
}
}
}
42 changes: 29 additions & 13 deletions apps/SKonsole/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.CommandLine;
using System.CommandLine.Builder;
using System.Diagnostics;
using System.Text;
using Microsoft.Extensions.Logging;
Expand All @@ -10,6 +11,7 @@
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.Skills.Web;
using Microsoft.SemanticKernel.Skills.Web.Bing;
using SKonsole.Commands;
using SKonsole.Skills;

Console.OutputEncoding = Encoding.Unicode;
Expand Down Expand Up @@ -37,27 +39,25 @@
UseExponentialBackoff = true,
});
})
// .WithAzureTextCompletionService(
// EnvVar("AZURE_OPENAI_DEPLOYMENT_NAME"),
// EnvVar("AZURE_OPENAI_API_ENDPOINT"),
// EnvVar("AZURE_OPENAI_API_KEY"),
// EnvVar("AZURE_OPENAI_DEPLOYMENT_LABEL"))
.WithAzureChatCompletionService(
EnvVar("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"),
EnvVar("AZURE_OPENAI_API_ENDPOINT"),
EnvVar("AZURE_OPENAI_API_KEY"))
ConfigVar("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"),
ConfigVar("AZURE_OPENAI_API_ENDPOINT"),
ConfigVar("AZURE_OPENAI_API_KEY"))
.WithLogger(kernelLogger)
.Build();

_kernel.Logger.LogTrace("KernelSingleton.Instance: adding Azure OpenAI backends");


var rootCommand = new RootCommand();
var commitCommand = new Command("commit", "Commit subcommand");
var prCommand = new Command("pr", "Pull Request feedback subcommand");
var prFeedbackCommand = new Command("feedback", "Pull Request feedback subcommand");
var prDescriptionCommand = new Command("description", "Pull Request description subcommand");
var plannerCommand = new Command("createPlan", "Planner subcommand");
var promptChatCommand = new Command("promptChat", "Prompt chat subcommand");


var messageArgument = new Argument<string>
("message", "An argument that is parsed as a string.");

Expand All @@ -79,6 +79,7 @@
prCommand.Add(prFeedbackCommand);
prCommand.Add(prDescriptionCommand);

rootCommand.Add(new ConfigCommand(ConfigurationProvider.Instance));
rootCommand.Add(commitCommand);
rootCommand.Add(prCommand);
rootCommand.Add(plannerCommand);
Expand Down Expand Up @@ -211,7 +212,7 @@ static async Task RunCreatePlan(IKernel kernel, ILogger? logger, string message)

kernel.ImportSkill(new WriterSkill(kernel), "writer");

var bingConnector = new BingConnector(EnvVar("BING_API_KEY"));
var bingConnector = new BingConnector(ConfigVar("BING_API_KEY"));
var bing = new WebSearchEngineSkill(bingConnector);
var search = kernel.ImportSkill(bing, "bing");

Expand Down Expand Up @@ -247,10 +248,11 @@ static async Task RunPromptChat(IKernel kernel, ILogger? logger)
await RunChat(kernel, logger, chatFunction);
}

static string EnvVar(string name)
static string ConfigVar(string name)
{
var value = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrEmpty(value)) throw new Exception($"Env var not set: {name}");
var provider = ConfigurationProvider.Instance;
var value = provider.Get(name);
if (string.IsNullOrEmpty(value)) throw new Exception($"Configuration var not set: {name}.Please run `skonsole config` to set it.");
return value;
}

Expand All @@ -270,12 +272,26 @@ static async Task RunChat(IKernel kernel, ILogger? logger, ISKFunction chatFunct
(logger ?? kernel.Logger).LogInformation("{botMessage}", botMessageFormatted);
(logger ?? kernel.Logger).LogInformation(">>>");

userMessage = Console.ReadLine(); // TODO -- How to support multi-line input?

userMessage = ReadMutiLineInput();
if (userMessage == "exit") break;

history += $"{botMessageFormatted}Human: {userMessage}\nAI:";
contextVariables.Set("history", history);

botMessage = await kernel.RunAsync(contextVariables, chatFunction);
}
}

static string ReadMutiLineInput()
{
var input = new StringBuilder();
var line = string.Empty;

while ((line = Console.ReadLine()) != string.Empty)
{
input.AppendLine(line);
}

return input.ToString().Trim();
}
2 changes: 2 additions & 0 deletions apps/SKonsole/SKonsole.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
<ToolCommandName>skonsole</ToolCommandName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
<PackageReference Include="Microsoft.SemanticKernel" Version="0.19.230804.2-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Skills.Web" Version="0.19.230804.2-preview" />
<PackageReference Include="Spectre.Console" Version="0.47.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
<ItemGroup>
Expand Down

0 comments on commit a112bd1

Please sign in to comment.