Skip to content

Commit 3641424

Browse files
committed
feat: Add Autocomplete support
1 parent 3c0b4e4 commit 3641424

File tree

14 files changed

+398
-98
lines changed

14 files changed

+398
-98
lines changed

Readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This also includes source code snippets. Highlighting is done via [highlight.js]
2121
- [Comments](./docs/Comments/Readme.md)
2222
- [Storage Provider](./docs/Storage/Readme.md)
2323
- [Search Engine Optimization (SEO)](./docs/SEO/Readme.md)
24+
- [AI Autocomplete](./docs/Autocomplete/Readme.md)
2425

2526
## Installation
2627

docs/Autocomplete/Readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### Autocomplete
2+
The blog can utilize Microsofts **Semantic Kernel** to either generate or enhance the current blog post.
3+
There are toggles to provide the AI with the Title, Descritpion, Markdown Content and Tags. You can also instruct the AI only to append content and not rewrite the whole article.
4+
5+
You can find the configuration in the `appsettings.json` file under the `AI` section.

docs/Setup/Configuration.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ The appsettings.json file has a lot of options to customize the content of the b
1919
"BackgroundUrl": "assets/profile-background.webp",
2020
"ProfilePictureUrl": "assets/profile-picture.webp"
2121
},
22+
"AI": {
23+
"DeploymentName": "gpt-4o",
24+
"EndpointUrl": "https://the-url",
25+
"ModelId": "gpt-4o",
26+
"ApiKey": "key"
27+
},
2228
"PersistenceProvider": "InMemory",
2329
"ConnectionString": "",
2430
"DatabaseName": "",
@@ -63,6 +69,11 @@ The appsettings.json file has a lot of options to customize the content of the b
6369
| Introduction | | Is used for the introduction part of the blog |
6470
| Description | MarkdownString | Small introduction text for yourself. This is also used for `<meta name="description">` tag. For this the markup will be converted to plain text |
6571
| BackgroundUrl | string | Url or path to the background image. (Optional) |
72+
| AI | | The AI section used for Semantic Kernel |
73+
| DeploymentName | string | Name of the deployment |
74+
| EndpointUrl | string | Url to the endpoint |
75+
| ModelId | string | Model Id |
76+
| ApiKey | string | Api Key |
6677
| ProfilePictureUrl | string | Url or path to your profile picture |
6778
| PersistenceProvider | string | Declares the type of the storage provider (one of the following: `SqlServer`, `Sqlite`, `RavenDb`, `InMemory`, `MySql`). More in-depth explanation [here](./../Storage/Readme.md) |
6879
| ConnectionString | string | Is used for connection to a database. Not used when `InMemoryStorageProvider` is used |
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace LinkDotNet.Blog.Domain;
2+
3+
public class AiSettings
4+
{
5+
public const string AiSettingsSection = "AI";
6+
7+
public string DeploymentName { get; set; }
8+
9+
public string EndpointUrl { get; set; }
10+
11+
public string ModelId { get; set; }
12+
13+
public string ApiKey { get; set; }
14+
15+
public bool IsEnabled => !string.IsNullOrEmpty(DeploymentName)
16+
&& !string.IsNullOrEmpty(EndpointUrl)
17+
&& !string.IsNullOrEmpty(ModelId)
18+
&& !string.IsNullOrEmpty(ApiKey);
19+
}

src/LinkDotNet.Blog.Web/ConfigurationExtension.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public static void AddConfiguration(this IServiceCollection services)
1515
.AddApplicationConfiguration()
1616
.AddAuthenticationConfigurations()
1717
.AddIntroductionConfigurations()
18+
.AddAiConfigurations()
1819
.AddSocialConfigurations()
1920
.AddProfileInformationConfigurations()
2021
.AddGiscusConfiguration()
@@ -66,6 +67,18 @@ private static IServiceCollection AddIntroductionConfigurations(this IServiceCol
6667
return services;
6768
}
6869

70+
private static IServiceCollection AddAiConfigurations(this IServiceCollection services)
71+
{
72+
ArgumentNullException.ThrowIfNull(services);
73+
74+
services.AddOptions<AiSettings>()
75+
.Configure<IConfiguration>((settings, config) =>
76+
{
77+
config.GetSection(AiSettings.AiSettingsSection).Bind(settings);
78+
});
79+
return services;
80+
}
81+
6982
private static IServiceCollection AddSocialConfigurations(this IServiceCollection services)
7083
{
7184
ArgumentNullException.ThrowIfNull(services);
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
@using LinkDotNet.Blog.Web.Features.Admin.BlogPostEditor.Services
2+
<ModalDialog @ref="Dialog" Title="AutoComplete Content">
3+
<form>
4+
<div class="mb-3">
5+
<label for="systemMessage" class="form-label">Message</label>
6+
<TextAreaWithShortcuts Class="form-control" Id="systemMessage" Rows="2" Placeholder="Enter message..." @bind-Value="@options.Prompt"></TextAreaWithShortcuts>
7+
</div>
8+
9+
<div class="mb-3">
10+
<small class="text-muted">Include the blog post's title in the AI input.</small>
11+
<div class="form-check form-switch">
12+
<input class="form-check-input" type="checkbox" id="includeTitle" @bind-value="@options.IncludeTitle">
13+
<label class="form-check-label" for="includeTitle">Include Title</label>
14+
</div>
15+
</div>
16+
17+
<div class="mb-3">
18+
<small class="text-muted">Include the blog post's short description in the AI input.</small>
19+
<div class="form-check form-switch">
20+
<input class="form-check-input" type="checkbox" id="includeShortDescription" @bind-value="@options.IncludeShortDescription">
21+
<label class="form-check-label" for="includeShortDescription">Include Short Description</label>
22+
</div>
23+
</div>
24+
25+
<div class="mb-3">
26+
<small class="text-muted">Include the blog post's tags in the AI input.</small>
27+
<div class="form-check form-switch">
28+
<input class="form-check-input" type="checkbox" id="includeTags" @bind-value="@options.IncludeTags">
29+
<label class="form-check-label" for="includeTags">Include Tags</label>
30+
</div>
31+
</div>
32+
33+
<div class="mb-3">
34+
<small class="text-muted">Include the blog post's content in the AI input.</small>
35+
<div class="form-check form-switch">
36+
<input class="form-check-input" type="checkbox" id="includeContent" @bind-value="@options.IncludeContent">
37+
<label class="form-check-label" for="includeContent">Include Content</label>
38+
</div>
39+
</div>
40+
41+
<div class="mb-3">
42+
<small class="text-muted">Keep original text and append AI-generated content, or allow AI to rewrite the content.</small>
43+
<div class="form-check form-switch">
44+
<input class="form-check-input" type="checkbox" id="keepOriginalText" @bind-value="@options.KeepOriginalText">
45+
<label class="form-check-label" for="keepOriginalText">Keep Original Text</label>
46+
</div>
47+
</div>
48+
49+
<button type="button" class="btn btn-primary btn-sm" @onclick="Generate">Generate</button>
50+
51+
<div class="mt-3">
52+
<label for="outputField" class="form-label">Output</label>
53+
<textarea class="form-control" id="outputField" rows="6" readonly>@options.Content</textarea>
54+
</div>
55+
56+
<div class="d-flex mt-3 justify-content-end gap-3">
57+
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancel</button>
58+
<button type="button" class="btn btn-success" @onclick="Save" disabled="@(!options.AllowSave)">Save</button>
59+
</div>
60+
</form>
61+
</ModalDialog>
62+
63+
@code {
64+
[Inject] private AutocompleteService AutocompleteService { get; set; }
65+
66+
[Parameter] public CreateNewModel Model { get; set; }
67+
[Parameter] public EventCallback<string> ContentGenerated { get; set; }
68+
69+
private ModalDialog Dialog { get; set; }
70+
private Options options = new ();
71+
72+
public void Open()
73+
{
74+
Dialog.Open();
75+
StateHasChanged();
76+
}
77+
78+
private void Cancel()
79+
{
80+
Dialog.Close();
81+
}
82+
83+
private async Task Generate()
84+
{
85+
var completeOptions = new AutocompleteOptions(
86+
options.IncludeTitle ? Model.Title : string.Empty,
87+
options.IncludeShortDescription ? Model.ShortDescription : string.Empty,
88+
options.IncludeTags ? Model.Tags : string.Empty,
89+
options.IncludeContent ? Model.Content : string.Empty,
90+
options.Prompt,
91+
options.KeepOriginalText);
92+
options.Content = await AutocompleteService.GetAutocomplete(completeOptions);
93+
options.AllowSave = true;
94+
}
95+
96+
private async Task Save()
97+
{
98+
await ContentGenerated.InvokeAsync(options.Content);
99+
Dialog.Close();
100+
options = new();
101+
}
102+
103+
private sealed class Options
104+
{
105+
public string Prompt { get; set; }
106+
public bool IncludeTitle { get; set; }
107+
public bool IncludeShortDescription { get; set; }
108+
public bool IncludeTags { get; set; }
109+
public bool IncludeContent { get; set; }
110+
public bool KeepOriginalText { get; set; }
111+
public string Content { get; set; }
112+
113+
public bool AllowSave { get; set; }
114+
}
115+
}

0 commit comments

Comments
 (0)