Skip to content

Commit

Permalink
✨ Implement ConfigCommand and improve configuration handling
Browse files Browse the repository at this point in the history
Implement the ConfigCommand class to manage configuration settings in
SKonsole. Add get and set commands for handling configuration keys and
values. Update ConfigurationProvider to read and write configuration data.
Replace the usage of environment variables with configuration variables
using the ConfigVar method. Update the error message to instruct users to
run `skonsole config` to set the configuration variable. Add a new method
ReadMutiLineInput to support multi-line user input in the console.

PR: #3

Co-authored-by: xbotter <xbotter@users.noreply.github.com>
  • Loading branch information
lemillermicrosoft and xbotter committed Aug 15, 2023
1 parent b69aed7 commit 7c1226d
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 7c1226d

Please sign in to comment.