Simulating an AI Assistant in a WinForms App (DevExpress 22.1) Using OpenAI API for File Summarization

I’m working on a WinForms application built with DevExpress 22.1, which includes a simulated AI Assistant powered by the OpenAI API. One key functionality involves processing file attachments uploaded by the user and generating a summary of their content. Below is the high-level functionality and the problem I’m encountering.


Functionality Overview

  1. Attachment Handling:
  • The form accepts multiple file types: images, Word documents (.docx), Excel sheets (.xlsx), JSON, XML, and plain text files (.txt).
  • Uploaded files are read as a byte[], and their content is sent to an internal File API for storage.
  • After uploading, the file’s content is passed to the OpenAI API for summarization.
  1. AI Integration:
  • An instance of the Assistant API is created.
  • A new thread is spawned to run the AI assistant.
  • The AI response is formatted and returned as RTF to be displayed in the application UI.

Issue Faced

When attempting to summarize certain file types (especially Word documents), I receive the following error message from the OpenAI API:

“The file test.docx appears to contain a mix of human-readable text and what seems to be binary data or code snippets that are generally not human-readable… [XML parts and other binary structures]… For a complete and intelligible content overview, the document would need to be opened in a compatible word processing software or decoded properly.”

In some cases, I also get errors indicating that the file is damaged, corrupted, or encrypted, preventing OpenAI from reading it properly.


Suspected Cause

The issue appears to stem from directly sending raw binary data (from byte[]) of non-plain-text files like .docx, .xlsx, or .json to the OpenAI API, without extracting their actual human-readable content. The AI treats this mix of XML metadata, binary blobs, and encoded structures as noise.


What I Need

I’m looking for a reliable and extensible approach to:

  • Correctly extract and convert file content to plain text before sending it to OpenAI.
  • Handle various file formats intelligently (e.g., extract text from .docx, .xlsx, .json, etc.).
  • Ensure the AI receives only meaningful text data, avoiding raw binary content or file structure artifacts.

If you’ve tackled similar scenarios, I’d appreciate any guidance.
Thanks!

using DevExpress.XtraRichEdit;
using DevExpress.XtraRichEdit.API.Native;
using Microsoft.VisualStudio.Threading;
using Newtonsoft.Json;
using OpenAI;
using OpenAI.Managers;
using OpenAI.ObjectModels.RequestModels;
using OpenAI.ObjectModels.SharedModels;
using static DevExpress.Utils.HashCodeHelper;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;

namespace AI_Test
{
    public partial class XtraForm_AI_Assistant : XtraForm
    {
        private readonly HttpClient _httpClient;
        private readonly OpenAIService _OpenAiClient;
        private readonly JoinableTaskFactory _joinableTaskFactory;
        private readonly string _ApiKey = "<YOUR_API_KEY>"; // Replace with your OpenAI API Key at runtime or from secure storage
        private readonly string _DefaultModel = "gpt-4-1106-preview";
        private readonly string _BasePrompt = "You are a helpful assistant that can summarize or translate any type of document or file.";
        private List<Attachment> Attachments;

        public XtraForm_AI_Assistant()
        {
            InitializeComponent();

            _joinableTaskFactory = new JoinableTaskFactory(new JoinableTaskContext());
            _httpClient = new HttpClient();
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _ApiKey);
            _httpClient.DefaultRequestHeaders.Add("OpenAI-Beta", "assistants=v2");

            _OpenAiClient = new OpenAIService(new OpenAiOptions
            {
                ApiKey = _ApiKey,
                DefaultModelId = _DefaultModel
            });

            Load += async (s, e) =>
            {
                Attachments = await DataService.GetInstance().GetAttachmentsByBProcessIdAsync(51, "C", true);
                foreach (var att in Attachments)
                    att.FileContent = await DataService.GetInstance().GetAttachedFileContentByAttachmentId(att.AttachmentId);

                await HandleAttachmentAction("summarize");
            };
        }

        private async Task HandleAttachmentAction(string actionType)
        {
            foreach (var att in Attachments)
            {
                try
                {
                    string fileId = await UploadFileAsync(att.FileContent, att.FileName);
                    string assistantId = await CreateAssistantAsync();
                    string threadId = await CreateThreadAsync(fileId, actionType);
                    string runId = await RunAssistantAsync(threadId, assistantId);
                    await WaitForRunCompletionAsync(threadId, runId);
                    string plainText = await GetAssistantReplyAsync(threadId);
                    string rtf = await GenerateRtfAsync(actionType, plainText);

                    using var viewer = new XtraForm_RtfViewer(rtf, actionType.ToUpper() + " Result");
                    viewer.ShowDialog(this);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Failed to process {att.FileName}:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private async Task<string> UploadFileAsync(byte[] fileBytes, string fileName)
        {
            using var content = new MultipartFormDataContent();
            var fileContent = new ByteArrayContent(fileBytes);
            fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
            content.Add(fileContent, "file", fileName);
            content.Add(new StringContent("assistants"), "purpose");

            var response = await _httpClient.PostAsync("https://api.openai.com/v1/files", content);
            string json = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
                throw new Exception($"File upload failed: {json}");
            dynamic result = JsonConvert.DeserializeObject(json);
            return result.id;
        }

        private async Task<string> CreateAssistantAsync()
        {
            var payload = new
            {
                name = "Universal Processor",
                instructions = _BasePrompt,
                tools = new[] { new { type = "file_search" } },
                model = _DefaultModel
            };
            var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
            var response = await _httpClient.PostAsync("https://api.openai.com/v1/assistants", content);
            string json = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
                throw new Exception($"Assistant creation failed: {json}");
            dynamic result = JsonConvert.DeserializeObject(json);
            return result.id;
        }

        private async Task<string> CreateThreadAsync(string fileId, string action)
        {
            string prompt = action.ToLower() == "translate"
                ? "Please extract and translate all readable content in the attached file."
                : "Please extract and summarize all readable content in the attached file.";

            var payload = new
            {
                messages = new[] {
                    new {
                        role = "user",
                        content = prompt,
                        attachments = new[] {
                            new {
                                file_id = fileId,
                                tools = new[] { new { type = "file_search" } }
                            }
                        }
                    }
                }
            };

            var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
            var response = await _httpClient.PostAsync("https://api.openai.com/v1/threads", content);
            string json = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
                throw new Exception($"Thread creation failed: {json}");
            dynamic result = JsonConvert.DeserializeObject(json);
            return result.id;
        }

        private async Task<string> RunAssistantAsync(string threadId, string assistantId)
        {
            var payload = new { assistant_id = assistantId };
            var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
            var response = await _httpClient.PostAsync($"https://api.openai.com/v1/threads/{threadId}/runs", content);
            string json = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
                throw new Exception($"Run failed: {json}");
            dynamic result = JsonConvert.DeserializeObject(json);
            return result.id;
        }

        private async Task WaitForRunCompletionAsync(string threadId, string runId)
        {
            string status = "";
            do
            {
                await Task.Delay(2000);
                var response = await _httpClient.GetAsync($"https://api.openai.com/v1/threads/{threadId}/runs/{runId}");
                string json = await response.Content.ReadAsStringAsync();
                if (!response.IsSuccessStatusCode)
                    throw new Exception($"Polling run failed: {json}");
                dynamic result = JsonConvert.DeserializeObject(json);
                status = result.status;
            }
            while (status != "completed" && status != "failed");

            if (status == "failed")
                throw new Exception("Assistant run failed.");
        }

        private async Task<string> GetAssistantReplyAsync(string threadId)
        {
            var response = await _httpClient.GetAsync($"https://api.openai.com/v1/threads/{threadId}/messages");
            string json = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
                throw new Exception($"Message fetch failed: {json}");
            dynamic result = JsonConvert.DeserializeObject(json);
            return result.data[0].content[0].text.value;
        }

        private Task<string> GenerateRtfAsync(string action, string plainText)
        {
            var messages = new List<ChatMessage>
            {
                ChatMessage.FromSystem("Convert the following text into well-formatted RTF using Times New Roman, 12pt. Include bold titles, spacing, lists. Return only valid RTF starting with '{\\rtf1'."),
                ChatMessage.FromUser(plainText)
            };

            return Task.Run(async () =>
            {
                var result = await _OpenAiClient.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest
                {
                    Messages = messages
                });
                return result.Successful ? result.Choices[0].Message.Content.Trim() : plainText;
            });
        }
    }
}