Logger
-@inject PostNoteClient client
-@inject NavigationManager Navigation
-@using NoteBookmark.BlazorApp
-
-@rendermode InteractiveServer
-
-
-
-Settings
-
-
-
-
-
-
-
-
-
-
-
-
- @context
-
-
-
-
-
-
-
-
-
-@if( settings != null)
-{
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Save
-
-
-
-
-}
-
-
-@code {
- public DesignThemeModes Mode { get; set; }
- public OfficeColor? OfficeColor { get; set; }
-
- private Domain.Settings? settings;
-
- protected override async Task OnInitializedAsync()
- {
- settings = await client.GetSettings();
- }
-
- private async Task SaveSettings()
- {
- if (settings != null)
- {
- await client.SaveSettings(settings);
- Navigation.NavigateTo("/");
- }
- }
-
- void OnLoaded(LoadedEventArgs e)
- {
- Logger.LogInformation($"Loaded: {(e.Mode == DesignThemeModes.System ? "System" : "")} {(e.IsDark ? "Dark" : "Light")}");
- }
-
- void OnLuminanceChanged(LuminanceChangedEventArgs e)
- {
- Logger.LogInformation($"Changed: {(e.Mode == DesignThemeModes.System ? "System" : "")} {(e.IsDark ? "Dark" : "Light")}");
- }
-
- private void IncrementCounter()
- {
- var cnt = Convert.ToInt32(settings!.ReadingNotesCounter)+1;
- settings.ReadingNotesCounter = (cnt).ToString();
- }
-}
+@page "/settings"
+
+@using Microsoft.FluentUI.AspNetCore.Components.Extensions
+@using NoteBookmark.Domain
+@inject ILogger Logger
+@inject PostNoteClient client
+@inject NavigationManager Navigation
+@using NoteBookmark.BlazorApp
+
+@rendermode InteractiveServer
+
+
+
+Settings
+
+
+
+
+
+
+
+
+
+
+
+
+ @context
+
+
+
+
+
+
+
+
+
+@if( settings != null)
+{
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AI Provider Configuration
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
+
+}
+
+
+@code {
+ public DesignThemeModes Mode { get; set; }
+ public OfficeColor? OfficeColor { get; set; }
+
+ private Domain.Settings? settings;
+
+ protected override async Task OnInitializedAsync()
+ {
+ settings = await client.GetSettings();
+ }
+
+ private async Task SaveSettings()
+ {
+ if (settings != null)
+ {
+ await client.SaveSettings(settings);
+ Navigation.NavigateTo("/");
+ }
+ }
+
+ void OnLoaded(LoadedEventArgs e)
+ {
+ Logger.LogInformation($"Loaded: {(e.Mode == DesignThemeModes.System ? "System" : "")} {(e.IsDark ? "Dark" : "Light")}");
+ }
+
+ void OnLuminanceChanged(LuminanceChangedEventArgs e)
+ {
+ Logger.LogInformation($"Changed: {(e.Mode == DesignThemeModes.System ? "System" : "")} {(e.IsDark ? "Dark" : "Light")}");
+ }
+
+ private void IncrementCounter()
+ {
+ var cnt = Convert.ToInt32(settings!.ReadingNotesCounter)+1;
+ settings.ReadingNotesCounter = (cnt).ToString();
+ }
+}
diff --git a/src/NoteBookmark.BlazorApp/Data/research_response_2026-02-14_15-38.json b/src/NoteBookmark.BlazorApp/Data/research_response_2026-02-14_15-38.json
new file mode 100644
index 0000000..c522dfa
--- /dev/null
+++ b/src/NoteBookmark.BlazorApp/Data/research_response_2026-02-14_15-38.json
@@ -0,0 +1,25 @@
+{
+ "suggestions": [
+ {
+ "title": "5 Ways Your .NET Developers Can Get Started with Azure Machine Learning",
+ "author": "James Serra, Azure Developer Ecosystem blog",
+ "summary": "Azure Machine Learning is a cloud-based platform for building and deploying AI models. In this article we will explore how your .Net developers can get started working with Azure Machine Learning through Visual Studio Code.",
+ "publication_date": {},
+ "url": "https://jamesmicrosoftcom/5-ways-get-started-with-azure-machine-learning-as-a-net-developer/"
+ },
+ {
+ "title": "C# and C++ Machine Learning for .NET Developers and AI Researchers",
+ "author": "Pankaj Dua, aka ‘AI Guy’ on Microsoft’s Developer Community blog.",
+ "summary": "In this article we'll present how to use open-source libraries like Accord.NET to build machine learning models in your choice of languages i.e., C#, F# or even C++. We'll walk through building your 'first ML model' using popular tools that you might have never used.",
+ "publication_date": {},
+ "url": "https://blogs.msdn.microsoft.com/ptgoa/c-cpp-companion-piece-on-ml-in-dot-net-world/"
+ },
+ {
+ "title": ".NET AI – a new home for .NET Machine Learning",
+ "author": "Microsoft .Net blog",
+ "summary": "Find out about latest developments in the world of machine learning on .net, including deep dive into ONNX and .NET Core. ",
+ "publication_date": {},
+ "url": "https://devblogs.microsoft.com/dotnet/net-ai-a-new-home-for-net-machine-learning/"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/NoteBookmark.BlazorApp/NoteBookmark.BlazorApp.csproj b/src/NoteBookmark.BlazorApp/NoteBookmark.BlazorApp.csproj
index e9f531d..7cf93ce 100644
--- a/src/NoteBookmark.BlazorApp/NoteBookmark.BlazorApp.csproj
+++ b/src/NoteBookmark.BlazorApp/NoteBookmark.BlazorApp.csproj
@@ -1,6 +1,8 @@
+
+
diff --git a/src/NoteBookmark.BlazorApp/PostNoteClient.cs b/src/NoteBookmark.BlazorApp/PostNoteClient.cs
index b6d734f..4ceee52 100644
--- a/src/NoteBookmark.BlazorApp/PostNoteClient.cs
+++ b/src/NoteBookmark.BlazorApp/PostNoteClient.cs
@@ -1,181 +1,181 @@
-using System;
-using NoteBookmark.Domain;
-
-namespace NoteBookmark.BlazorApp;
-
-public class PostNoteClient(HttpClient httpClient)
-{
- public async Task> GetUnreadPosts()
- {
- var posts = await httpClient.GetFromJsonAsync>("api/posts");
- return posts ?? new List();
- }
-
- public async Task> GetReadPosts()
- {
- var posts = await httpClient.GetFromJsonAsync>("api/posts/read");
- return posts ?? new List();
- }
-
- public async Task> GetSummaries()
- {
- var summaries = await httpClient.GetFromJsonAsync>("api/summary");
- return summaries ?? new List();
- }
-
- public async Task CreateNote(Note note)
- {
- var rnCounter = await httpClient.GetStringAsync("api/settings/GetNextReadingNotesCounter");
- note.PartitionKey = rnCounter;
- var response = await httpClient.PostAsJsonAsync("api/notes/note", note);
- response.EnsureSuccessStatusCode();
- }
-
- public async Task GetNote(string noteId)
- {
- var note = await httpClient.GetFromJsonAsync($"api/notes/note/{noteId}");
- return note;
- }
-
- public async Task UpdateNote(Note note)
- {
- var response = await httpClient.PutAsJsonAsync("api/notes/note", note);
- return response.IsSuccessStatusCode;
- }
-
- public async Task DeleteNote(string noteId)
- {
- var response = await httpClient.DeleteAsync($"api/notes/note/{noteId}");
- return response.IsSuccessStatusCode;
- }
-
- public async Task CreateReadingNotes()
- {
- var rnCounter = await httpClient.GetStringAsync("api/settings/GetNextReadingNotesCounter");
- var readingNotes = new ReadingNotes(rnCounter);
-
- //Get all unused notes
- var unsortedNotes = await httpClient.GetFromJsonAsync>($"api/notes/GetNotesForSummary/{rnCounter}");
-
- if(unsortedNotes == null || unsortedNotes.Count == 0){
- return readingNotes;
- }
-
- Dictionary> sortedNotes = GroupNotesByCategory(unsortedNotes);
-
- readingNotes.Notes = sortedNotes;
- readingNotes.Tags = readingNotes.GetAllUniqueTags();
-
- return readingNotes;
- }
-
- public async Task GetReadingNotes(string number)
- {
- ReadingNotes? readingNotes;
- readingNotes = await httpClient.GetFromJsonAsync($"api/summary/{number}");
-
- return readingNotes;
- }
-
-
- private Dictionary> GroupNotesByCategory(List notes)
- {
- var sortedNotes = new Dictionary>();
-
- foreach (var note in notes)
- {
- var tags = note.Tags?.ToLower().Split(',') ?? Array.Empty();
-
- if(string.IsNullOrEmpty(note.Category)){
- note.Category = NoteCategories.GetCategory(tags[0]);
- }
-
- string category = note.Category;
- if (sortedNotes.ContainsKey(category))
- {
- sortedNotes[category].Add(note);
- }
- else
- {
- sortedNotes.Add(category, new List {note});
- }
- }
-
- return sortedNotes;
- }
-
- public async Task SaveReadingNotes(ReadingNotes readingNotes)
- {
- var response = await httpClient.PostAsJsonAsync("api/notes/SaveReadingNotes", readingNotes);
-
- string jsonURL = ((string)await response.Content.ReadAsStringAsync()).Replace("\"", "");
-
- if (response.IsSuccessStatusCode && !string.IsNullOrEmpty(jsonURL))
- {
- var summary = new Summary
- {
- PartitionKey = readingNotes.Number,
- RowKey = readingNotes.Number,
- Title = readingNotes.Title,
- Id = readingNotes.Number,
- IsGenerated = "true",
- PublishedURL = readingNotes.PublishedUrl,
- FileName = jsonURL
- };
-
- var summaryResponse = await httpClient.PostAsJsonAsync("api/summary/summary", summary);
- return summaryResponse.IsSuccessStatusCode;
- }
-
- return false;
- }
-
-
- public async Task GetPost(string id)
- {
- var post = await httpClient.GetFromJsonAsync($"api/posts/{id}");
- return post;
- }
-
-
- public async Task SavePost(Post post)
- {
- var response = await httpClient.PostAsJsonAsync("api/posts", post);
- return response.IsSuccessStatusCode;
- }
-
- public async Task GetSettings()
- {
- var settings = await httpClient.GetFromJsonAsync("api/settings");
- return settings;
- }
-
- public async Task SaveSettings(Settings settings)
- {
- var response = await httpClient.PostAsJsonAsync("api/settings/SaveSettings", settings);
- return response.IsSuccessStatusCode;
- }
-
- public async Task ExtractPostDetailsAndSave(string url)
- {
- //var encodedUrl = System.Net.WebUtility.UrlEncode(url);
- var requestBody = new {url = url};
-
- var response = await httpClient.PostAsJsonAsync($"api/posts/extractPostDetails", requestBody);
- // var response = await httpClient.PostAsJsonAsync($"api/posts/extractPostDetails?url={encodedUrl}", url);
- return response.IsSuccessStatusCode;
- }
-
- public async Task DeletePost(string id)
- {
- var response = await httpClient.DeleteAsync($"api/posts/{id}");
- return response.IsSuccessStatusCode;
- }
-
- public async Task SaveReadingNotesMarkdown(string markdown, string number)
- {
- var request = new { Markdown = markdown };
- var response = await httpClient.PostAsJsonAsync($"api/summary/{number}/markdown", request);
- return response.IsSuccessStatusCode;
- }
-}
+using System;
+using NoteBookmark.Domain;
+
+namespace NoteBookmark.BlazorApp;
+
+public class PostNoteClient(HttpClient httpClient)
+{
+ public async Task> GetUnreadPosts()
+ {
+ var posts = await httpClient.GetFromJsonAsync>("api/posts");
+ return posts ?? new List();
+ }
+
+ public async Task> GetReadPosts()
+ {
+ var posts = await httpClient.GetFromJsonAsync>("api/posts/read");
+ return posts ?? new List();
+ }
+
+ public async Task> GetSummaries()
+ {
+ var summaries = await httpClient.GetFromJsonAsync>("api/summary");
+ return summaries ?? new List();
+ }
+
+ public async Task CreateNote(Note note)
+ {
+ var rnCounter = await httpClient.GetStringAsync("api/settings/GetNextReadingNotesCounter");
+ note.PartitionKey = rnCounter;
+ var response = await httpClient.PostAsJsonAsync("api/notes/note", note);
+ response.EnsureSuccessStatusCode();
+ }
+
+ public async Task GetNote(string noteId)
+ {
+ var note = await httpClient.GetFromJsonAsync($"api/notes/note/{noteId}");
+ return note;
+ }
+
+ public async Task UpdateNote(Note note)
+ {
+ var response = await httpClient.PutAsJsonAsync("api/notes/note", note);
+ return response.IsSuccessStatusCode;
+ }
+
+ public async Task DeleteNote(string noteId)
+ {
+ var response = await httpClient.DeleteAsync($"api/notes/note/{noteId}");
+ return response.IsSuccessStatusCode;
+ }
+
+ public async Task CreateReadingNotes()
+ {
+ var rnCounter = await httpClient.GetStringAsync("api/settings/GetNextReadingNotesCounter");
+ var readingNotes = new ReadingNotes(rnCounter);
+
+ //Get all unused notes
+ var unsortedNotes = await httpClient.GetFromJsonAsync>($"api/notes/GetNotesForSummary/{rnCounter}");
+
+ if(unsortedNotes == null || unsortedNotes.Count == 0){
+ return readingNotes;
+ }
+
+ Dictionary> sortedNotes = GroupNotesByCategory(unsortedNotes);
+
+ readingNotes.Notes = sortedNotes;
+ readingNotes.Tags = readingNotes.GetAllUniqueTags();
+
+ return readingNotes;
+ }
+
+ public async Task GetReadingNotes(string number)
+ {
+ ReadingNotes? readingNotes;
+ readingNotes = await httpClient.GetFromJsonAsync($"api/summary/{number}");
+
+ return readingNotes;
+ }
+
+
+ private Dictionary> GroupNotesByCategory(List notes)
+ {
+ var sortedNotes = new Dictionary>();
+
+ foreach (var note in notes)
+ {
+ var tags = note.Tags?.ToLower().Split(',') ?? Array.Empty();
+
+ if(string.IsNullOrEmpty(note.Category)){
+ note.Category = NoteCategories.GetCategory(tags[0]);
+ }
+
+ string category = note.Category;
+ if (sortedNotes.ContainsKey(category))
+ {
+ sortedNotes[category].Add(note);
+ }
+ else
+ {
+ sortedNotes.Add(category, new List {note});
+ }
+ }
+
+ return sortedNotes;
+ }
+
+ public async Task SaveReadingNotes(ReadingNotes readingNotes)
+ {
+ var response = await httpClient.PostAsJsonAsync("api/notes/SaveReadingNotes", readingNotes);
+
+ string jsonURL = ((string)await response.Content.ReadAsStringAsync()).Replace("\"", "");
+
+ if (response.IsSuccessStatusCode && !string.IsNullOrEmpty(jsonURL))
+ {
+ var summary = new Summary
+ {
+ PartitionKey = readingNotes.Number,
+ RowKey = readingNotes.Number,
+ Title = readingNotes.Title,
+ Id = readingNotes.Number,
+ IsGenerated = "true",
+ PublishedURL = readingNotes.PublishedUrl,
+ FileName = jsonURL
+ };
+
+ var summaryResponse = await httpClient.PostAsJsonAsync("api/summary/summary", summary);
+ return summaryResponse.IsSuccessStatusCode;
+ }
+
+ return false;
+ }
+
+
+ public async Task GetPost(string id)
+ {
+ var post = await httpClient.GetFromJsonAsync($"api/posts/{id}");
+ return post;
+ }
+
+
+ public async Task SavePost(Post post)
+ {
+ var response = await httpClient.PostAsJsonAsync("api/posts", post);
+ return response.IsSuccessStatusCode;
+ }
+
+ public async Task GetSettings()
+ {
+ var settings = await httpClient.GetFromJsonAsync("api/settings");
+ return settings;
+ }
+
+ public async Task SaveSettings(Settings settings)
+ {
+ var response = await httpClient.PostAsJsonAsync("api/settings/SaveSettings", settings);
+ return response.IsSuccessStatusCode;
+ }
+
+ public async Task ExtractPostDetailsAndSave(string url)
+ {
+ //var encodedUrl = System.Net.WebUtility.UrlEncode(url);
+ var requestBody = new {url = url};
+
+ var response = await httpClient.PostAsJsonAsync($"api/posts/extractPostDetails", requestBody);
+ // var response = await httpClient.PostAsJsonAsync($"api/posts/extractPostDetails?url={encodedUrl}", url);
+ return response.IsSuccessStatusCode;
+ }
+
+ public async Task DeletePost(string id)
+ {
+ var response = await httpClient.DeleteAsync($"api/posts/{id}");
+ return response.IsSuccessStatusCode;
+ }
+
+ public async Task SaveReadingNotesMarkdown(string markdown, string number)
+ {
+ var request = new { Markdown = markdown };
+ var response = await httpClient.PostAsJsonAsync($"api/summary/{number}/markdown", request);
+ return response.IsSuccessStatusCode;
+ }
+}
diff --git a/src/NoteBookmark.BlazorApp/Program.cs b/src/NoteBookmark.BlazorApp/Program.cs
index 896b180..bdee7e0 100644
--- a/src/NoteBookmark.BlazorApp/Program.cs
+++ b/src/NoteBookmark.BlazorApp/Program.cs
@@ -6,40 +6,55 @@
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
+builder.AddAzureTableClient("nb-tables");
-// Register ResearchService with a manual HttpClient to bypass Aspire resilience policies
-// builder.Services.AddTransient(sp =>
-// {
-// var handler = new SocketsHttpHandler
-// {
-// PooledConnectionLifetime = TimeSpan.FromMinutes(5),
-// ConnectTimeout = TimeSpan.FromMinutes(5)
-// };
-
-// var httpClient = new HttpClient(handler)
-// {
-// Timeout = TimeSpan.FromMinutes(5)
-// };
-
-// var logger = sp.GetRequiredService>();
-// var config = sp.GetRequiredService();
-
-// return new ResearchService(httpClient, logger, config);
-// });
-
+// Add HTTP client for API calls
builder.Services.AddHttpClient(client =>
{
client.BaseAddress = new Uri("https+http://api");
});
-builder.Services.AddHttpClient(client =>
+// Register server-side AI settings provider (direct database access, unmasked)
+builder.Services.AddScoped();
+
+// Register AI services with settings provider that reads directly from database
+builder.Services.AddTransient(sp =>
{
- client.Timeout = TimeSpan.FromMinutes(5);
+ var logger = sp.GetRequiredService>();
+ var settingsProvider = sp.GetRequiredService();
+
+ // Settings provider that fetches directly from database (server-side, unmasked)
+ Func> provider = async () =>
+ {
+ var settings = await settingsProvider.GetAISettingsAsync();
+ return (
+ settings.ApiKey,
+ settings.BaseUrl,
+ settings.ModelName
+ );
+ };
+
+ return new SummaryService(logger, provider);
});
-
-builder.Services.AddHttpClient();
- // .AddStandardResilienceHandler();
+builder.Services.AddTransient(sp =>
+{
+ var logger = sp.GetRequiredService>();
+ var settingsProvider = sp.GetRequiredService();
+
+ // Settings provider that fetches directly from database (server-side, unmasked)
+ Func> provider = async () =>
+ {
+ var settings = await settingsProvider.GetAISettingsAsync();
+ return (
+ settings.ApiKey,
+ settings.BaseUrl,
+ settings.ModelName
+ );
+ };
+
+ return new ResearchService(logger, provider);
+});
// Add services to the container.
diff --git a/src/NoteBookmark.Domain/PostSuggestion.cs b/src/NoteBookmark.Domain/PostSuggestion.cs
index f6fe06a..54cd6b3 100644
--- a/src/NoteBookmark.Domain/PostSuggestion.cs
+++ b/src/NoteBookmark.Domain/PostSuggestion.cs
@@ -31,15 +31,57 @@ public class DateOnlyJsonConverter : JsonConverter
if (reader.TokenType == JsonTokenType.Null)
return null;
- var dateString = reader.GetString();
- if (string.IsNullOrEmpty(dateString))
- return null;
+ try
+ {
+ // Handle different JSON token types the AI might return
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.String:
+ var dateString = reader.GetString();
+ if (string.IsNullOrEmpty(dateString))
+ return null;
+
+ // Try to parse as DateTime and format to yyyy-MM-dd
+ if (DateTime.TryParse(dateString, out var date))
+ {
+ return date.ToString(DateFormat);
+ }
+ // If parsing fails, return the original string
+ return dateString;
+
+ case JsonTokenType.Number:
+ // Handle Unix timestamp (seconds or milliseconds)
+ if (reader.TryGetInt64(out var timestamp))
+ {
+ DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ // Assume milliseconds if > year 2100 in seconds (2147483647)
+ var dateTime = timestamp > 2147483647
+ ? epoch.AddMilliseconds(timestamp)
+ : epoch.AddSeconds(timestamp);
+ return dateTime.ToString(DateFormat);
+ }
+ break;
- if (DateTime.TryParse(dateString, out var date))
+ case JsonTokenType.True:
+ case JsonTokenType.False:
+ // Handle unexpected boolean - convert to string
+ return reader.GetBoolean().ToString();
+
+ case JsonTokenType.StartObject:
+ case JsonTokenType.StartArray:
+ // Handle complex types - skip and return null
+ reader.Skip();
+ return null;
+ }
+ }
+ catch
{
- return date.ToString(DateFormat);
+ // If any parsing fails, skip the value and return null to gracefully degrade
+ try { reader.Skip(); } catch { /* ignore */ }
+ return null;
}
- return dateString;
+
+ return null;
}
public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
diff --git a/src/NoteBookmark.Domain/Settings.cs b/src/NoteBookmark.Domain/Settings.cs
index fe5e4eb..f37d026 100644
--- a/src/NoteBookmark.Domain/Settings.cs
+++ b/src/NoteBookmark.Domain/Settings.cs
@@ -1,40 +1,52 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-using System.Runtime.Serialization;
-using Azure;
-using Azure.Data.Tables;
-
-namespace NoteBookmark.Domain;
-
-public class Settings: ITableEntity
-{
- [DataMember(Name="last_bookmark_date")]
- public string? LastBookmarkDate { get; set; }
-
-
- [DataMember(Name="reading_notes_counter")]
- public string? ReadingNotesCounter { get; set; }
-
-
- [DataMember(Name="favorite_domains")]
- public string? FavoriteDomains { get; set; }
-
-
- [DataMember(Name="blocked_domains")]
- public string? BlockedDomains { get; set; }
-
-
- [DataMember(Name="summary_prompt")]
- [ContainsPlaceholder("content")]
- public string? SummaryPrompt { get; set; }
-
-
- [DataMember(Name="search_prompt")]
- [ContainsPlaceholder("topic")]
- public string? SearchPrompt { get; set; }
-
- public required string PartitionKey { get ; set; }
- public required string RowKey { get ; set; }
- public DateTimeOffset? Timestamp { get; set; }
- public ETag ETag { get; set; }
-}
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Runtime.Serialization;
+using Azure;
+using Azure.Data.Tables;
+
+namespace NoteBookmark.Domain;
+
+public class Settings: ITableEntity
+{
+ [DataMember(Name="last_bookmark_date")]
+ public string? LastBookmarkDate { get; set; }
+
+
+ [DataMember(Name="reading_notes_counter")]
+ public string? ReadingNotesCounter { get; set; }
+
+
+ [DataMember(Name="favorite_domains")]
+ public string? FavoriteDomains { get; set; }
+
+
+ [DataMember(Name="blocked_domains")]
+ public string? BlockedDomains { get; set; }
+
+
+ [DataMember(Name="summary_prompt")]
+ [ContainsPlaceholder("content")]
+ public string? SummaryPrompt { get; set; }
+
+
+ [DataMember(Name="search_prompt")]
+ [ContainsPlaceholder("topic")]
+ public string? SearchPrompt { get; set; }
+
+
+ [DataMember(Name="ai_api_key")]
+ public string? AiApiKey { get; set; }
+
+
+ [DataMember(Name="ai_base_url")]
+ public string? AiBaseUrl { get; set; }
+
+
+ [DataMember(Name="ai_model_name")]
+ public string? AiModelName { get; set; }
+
+ public required string PartitionKey { get ; set; }
+ public required string RowKey { get ; set; }
+ public DateTimeOffset? Timestamp { get; set; }
+ public ETag ETag { get; set; }
+}