CHS Minutes - New

CHS Minutes - New

Minutes

  • We will have AI Enhancement in Minutes menu for correcting sentence, summarizing or expanding notes

Doubts

  1. for getting prompt method will be part of minutes only ? -> In minutes okay
  2. store html in new table & allow user to download further once confirmed by Vanshika Ma’am

System Flow

  1. On MinutesBookDetails we will have a button Enhance with AI
  2. Once user click on this it will open a dialog from this dialog user can upload the Minutes PDF, one info msg we will give on this form, Pleas download minutes and attach here
  3. We will have a select combo By default
    1. Grammar Correction -> GrammarCorrection
    2. Summarize Notes -> SummarizeNotes
    3. Expand Notes -> ExpandNotes
  4. Once user upload pdf we will save in path, after that they can click on save
  5. Now as per new logic of System Prompt management new table we will also have for storing prompt, we will have a new table
//Web Appsetting
 "OpenAI": {
  "ApiKey": "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",
  "Model": "gpt-5-nano-2025-08-07",
  "Endpoint": "https://api.openai.com/v1/responses",
  "ProjectId": "proj_Fcp9lygHF46qrItsTmD3TgQH"
 },
//Program.cs web
builder.Services.AddHttpClient("openai", client =>
{
    var config = builder.Configuration;

    var apiBaseAddress = config["OpenAI:BaseAddress"] ?? "https://api.openai.com/";
    var apiKey = config["OpenAI:ApiKey"] ?? throw new ArgumentNullException("OpenAI:ApiKey not set");
    var projectId = config["OpenAI:ProjectId"];

    client.BaseAddress = new Uri(apiBaseAddress);
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.DefaultRequestHeaders.Authorization =
        new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiKey);

    if (!string.IsNullOrEmpty(projectId))
    {
        client.DefaultRequestHeaders.Add("OpenAI-Project", projectId);
    }
});
CREATE TABLE AISystemPrompts (
    id INT PRIMARY KEY IDENTITY(1,1),
    ContextTable NVARCHAR(20),
    SysRoleName NVARCHAR(50), 
    PromptCategory NVARCHAR(20), 
    PromptText NVARCHAR(MAX)
);

INSERT INTO AISystemPrompts (ContextTable, SysRoleName, PromptCategory, PromptText) VALUES
('MinutesGlobal', 'MinutesAi',  'Minutes',
'You are an assistant for Cooperative Housing Society meeting minutes.

DOCUMENT VALIDATION (MANDATORY):
1. The uploaded document MUST be official Cooperative Housing Society meeting minutes.
2. The document must contain identifiable meeting elements such as:
   - Meeting title
   - Date and time
   - Location
   - Agenda or discussion points
   - Resolutions or decisions
3. If the document is anything other than meeting minutes
   (examples: bill, invoice, voucher, receipt, notice, letter,
   bank statement, GST document, or unrelated PDF),
   respond ONLY with:
   <error>Invalid request. Uploaded document is not meeting minutes.</error>

DEFAULT BEHAVIOR (ALWAYS APPLIED):
4. Always correct spelling mistakes.
5. Always correct grammatical and punctuation errors.

FORMAT PRESERVATION RULES (MANDATORY):
6. Preserve the ORIGINAL layout, alignment, spacing, numbering, and table structure EXACTLY as provided,
   UNLESS the user explicitly requests summarization or expansion.
7. Preserve all original bold, underline, italics, capitalization, and alignment.
8. Do NOT rearrange sections, tables, or headings unless explicitly requested by the user.
9. Do NOT modify table rows, columns, merged cells, or borders unless explicitly requested.
10. Do NOT add CSS, inline styles, or extra formatting.

USER-CONTROLLED OPERATIONS:
11. If the user explicitly requests:
   - "summarize", then condense content while keeping the same section order.
   - "expand", then add more detail to existing points without changing headings.
   - "enhance", then improve clarity while preserving structure.
12. Never summarize or expand unless the user clearly asks for it.

OUTPUT RULES:
13. Return ONLY valid HTML suitable for conversion to Word.
14. HTML structure must mirror the original document unless changed per user request.
15. Use <table>, <tr>, <td>, <th> only where tables exist.
16. Do NOT include explanations, comments, or text outside the HTML.
');

INSERT INTO AISystemPrompts (ContextTable, SysRoleName, PromptCategory, PromptText) VALUES
('GrammarCorrection', 'MinutesAi',  'Minutes',
'Apply the following modification:
  Generate a professional and formal version of the meeting minutes in structured paragraphs. 
  Correct grammar, spelling, and sentence structure to make the minutes polished.
  Expand the meeting minutes into detailed descriptive paragraphs with complete context.');

INSERT INTO AISystemPrompts (ContextTable, SysRoleName, PromptCategory, PromptText) VALUES
('SummarizeNotes', 'MinutesAi',  'Minutes',
'Summarize the meeting minutes into short points for quick reference.');

INSERT INTO AISystemPrompts (ContextTable, SysRoleName, PromptCategory, PromptText) VALUES
('ExpandNotes', 'MinutesAi',  'Minutes',
'Summarize the meeting minutes into short points for quick reference.');

--GetAISystemPrompts
SELECT PromptText FROM AISystemPrompts
WHERE ContextTable = @ContextTable

Service Helper method

          public async Task<string> MinutesAIEnhance(string? dbname, string newfilename, MinutesBook _MinutesBook)
        {
            string systemPrompt = await GetAISystemPrompts("MinutesGlobal");
            string userPrompts = await GetAISystemPrompts("GrammarCorrection");
            if(_MinutesBook.PromptCategory != "GrammarCorrection")
            {
                userPrompts += await GetAISystemPrompts(_MinutesBook.PromptCategory);
            }
            // Upload PDF to OpenAI
            var pdfFilePath = $"\\CHSReports\\Reports\\{dbname}\\{newfilename}";
            using var multipartForm = new MultipartFormDataContent
            {
                { new StringContent("assistants"), "purpose" }, // purpose must be "assistants" as we have to use this file for next call
                { new StreamContent(File.OpenRead(pdfFilePath)), "file", Path.GetFileName(pdfFilePath) }
            };

            var uploadResponse = await _OpenAIHttpClient.PostAsync("v1/files", multipartForm);
            uploadResponse.EnsureSuccessStatusCode();
            var uploadJson = await uploadResponse.Content.ReadFromJsonAsync<OpenAIFileResponse>();

            var fileId = uploadJson!.id?.ToString();

            var requestBody = new
            {
                model = _model,
                input = new object[]
                {
                    new
                    {
                        role = "system",
                        content = systemPrompt
                    },
                    new
                    {
                        role = "user",
                        content = new object[]
                        {
                            new { type = "input_text", text = userPrompts },
                            new { type = "input_file", file_id = fileId }
                        }
                    }
                }
            };

            // Call OpenAI endpoint
            var response = await _OpenAIHttpClient.PostAsJsonAsync("v1/responses", requestBody);
            if (!response.IsSuccessStatusCode)
            {
                var error = await response.Content.ReadAsStringAsync();
                // Doing this as we are checking ex.Message.Contains("status code") in main layout so it will see the error is there
                throw new HttpRequestException($"OpenAI API request failed with status code {response.StatusCode}: {error}");
            }

            var openAIResponse = await response.Content.ReadFromJsonAsync<OpenAIResponseV2>();

            if (openAIResponse?.output?.Count > 0 && openAIResponse.output[0].content?.Count != null)
            {
                var htmlcode = openAIResponse.output[1].content[0].text.Trim();

                // --- Direct File Saving Logic ---
                Random rnd = new();
                var randomfilename = $"{rnd.Next(1000000000)}.html";
                string pathdirname = $"\\CHSReports\\Reports\\{dbname}";

                if (!Directory.Exists(pathdirname))
                {
                    Directory.CreateDirectory(pathdirname);
                }

                string pathname = $"{pathdirname}\\{randomfilename}";
                await File.WriteAllTextAsync(pathname, htmlcode);
                return randomfilename; // Return the name of the file you just 
            }
            await _OpenAIHttpClient.DeleteAsync($"v1/files/{fileId}");

            return string.Empty;
        }