Altschaden

Was im laufe der Zeit alles mit gegnerischen Versicherungen erlebt habe, von nicht leisten wollen, weil der Versicherungsnehmer die Versicherung nicht gezahlt hat, bis hin zu den wirtschaftlichen Totalschäden, alles irgendwie überschaubar, aber dass ist schon irgendwie lustig.

Also so zur Einordnung der folgenden Ereignisse

Die Ardetzenbergstraße ist eine kleine, idyllische Straße auf den Ardetzenberg, der eigentlich weniger Berg mehr Hügel ist, aber den Wildpark auf seinem Gipfel beheimatet. So weit so schön.
Nun gibt es nicht nur den Wildpark zu bewundern, sondern auch eine Schule – das ehrwürdige Institut St. Josef. Eigentlich ist die Ardetzenbergstraße eine Durchgangsstraße- Also wenn man von der einen Seite rauf fährt kommt man am anderen Ende wieder runter. Bei dem ganzen Verkehr auf der Ardetzenbergstraße gab es trotz des Interessenkonflikts auf der engen Straße zwischen Schülerinnen, Eltern die ihre Kinder in die Schule bringen müssen und Schülerinnen die bereits den Führerschein haben und selbst in die Schule fahren, bis vor kurzem wenige Probleme.

Bis ja bis die Stadt beschlossen hat die halbe Straße zu sperren um die Ill zu erweitern, damit Feldkirch vor einem 100jährigen Hochwasser geschützt ist. An das letzte 100jährige erinnert man sich nicht – also auf Nachfrage wusste das von der Stadt niemand – nicht mal der von der Wasserwirtschaft. Naja egal aber Gerüchten zu folge war es 1910. Wie jeder studierte Verkehrsplaner, gelernter Politiker und erfahrene Wasserexperte weiß ist, da ja über 100 Jahre her, Gefahr in Verzug und es muss auf der Stelle gesprengt werden. Zumal eine ein Wöchige Verkehrszählung ergeben hat das eh kein Verkehr ist. Naja außerdem dürfen die Anzahl der Autos die vom Bahnhof in die Ardetzenbergstraße fahren nicht mit der Anzahl der Autos die von der anderen Seite kommen zusammengezählt werden. Ja nicht – die was auf dem abgesprengten Teil der Ardetzenbergstraße ihre Kinder zur Schule bzw. Wildpark befeuern – fahren ja nicht mehr – weil ja keine Straße.

Also gut wir wissen – Ardetzenbergstraße eng, viel Verkehr unter anderem, ich traue es mich fast nicht zu sagen, weil Feldkirch einen ÖVP Bürgermeister hat und naja Fußgänger gibt es nicht, es ist auch die Spezies der Fußgängerin (Fußgänger sind mit gemeint) unterwegs.

Auf Grund dessen kann es schon vor kommen, das Abstände nicht richtig eingeschätzt werden und so mancher Seitenspiegel kaputt geht.

Auch in dieser Geschichte. Eine Dame für in meinen Spiegel und sie war so freundlich stehen zu bleiben. Wir tauschten die Daten aus und sie meldete den absoluten belanglosen Unfall ihrer Versicherung. Die ihren Namen einer Stadt aus einem Nachbarland im Westen verdankt.

Es war ein netter Brief, den ich dann von der Versicherung bekam.
Der erste Satz war gleich viel versprechend „Ihr Schadenfall rasch abwickeln“ stand da.

Da es Mitte November war und ich definitiv keine Lust hatte den Spiegel selbst zu tauschen und bei einer Ablöse die Versicherung der Gewinner ist, brachte ich das Auto in die Werkstatt meines Vertrauens. Der machte ein Gutachten – die Fotos halt und schickte das Ganze nach Zürich.

Ungefähr 2 Wochen danach bekam ich einen Anruf von der Werkstatt, Reparatur abgelehnt weil „Altschaden“. Naja der Schaden war ja schon einen Monat alt – deshalb will ich ihn ja so rasch wie möglich reparieren lassen, dachte ich.
Also mal angerufen bei der Nummer auf dem freundlichen Schreiben. Nach ungefähr 5 Versuchen und um 17 Uhr 01 nahm einer ab, der mir erklärte das das Leistung KFZ Team ab 17.00 nicht mehr da ist. Ich bat also um einen Rückruf.

Drei Tage verstrichen – kein Rückruf. Ich versuche es nochmal siehe da eine Dame hob ab und erklärte mir Altschaden – auf die Frage was das heißt meinte sie keine Ahnung das weis nur der Sachbearbeiter.

Ich war schon etwas von der Sache genervt.

Die Tage verstrichen kein Sachbearbeiter weit und breit – dann ein neuerlicher Anruf bzw. Mail usw. Keine Reaktion.

Dann ja irgendwie Feiertage – so Erholung muss auch sein – vor allem für überarbeitete Sachbearbeiter. Dann mal wieder eine Mail, Anrufe beim letzten Anruf legte die Dame einfach auf. Also ein Mail an das Service Center – war ziemlich böse von mir. Und siehe da nach 3 Monaten bekam ich eine Mail.
In der wurde einem Herrn (der Name tut nichts zur Sache, nennen wir ihn Noname) – wer immer das auch sein möge – mittgeteilt wurde das zu keiner Schadenserweiterung gekommen ist.

Das ist total schön für unseren Herr Noname, das er weiß, das es keine Schadenserweiterung bei meinem Auto gab. Irgendwie wäre es vielleicht sinnvoll gewesen mir das auch mitzuteilen.

Am nächsten Tag – also in affenartiger Geschwindigkeit – Die Bits sind nur so geflutscht, die Finger haben geglüht kam ein Einzeiler – „Ich schreibe den Altschaden habe ich behoben ob ich ihnen eine Rechnung schicken kann“.

Also schicke ich ihnen Fotos und eine Rechnung. Ich habe das auch eingekringelt auf den Fotos was der Schaden war und was er ist. siehe unten.

Und wieder, ganz untypisch für das Land aus dem die Versicherung stammt, am nächsten Werktag kommt wieder ein Einzeiler.

„Wir sprechen von einem Altschaden vom 9.8.2023“.

Zuerst dachte ich, das der „Pluralis Majestatis“ nicht mehr so gebräuchlich ist – aber es kann ja sein das Versicherungen sich vielleicht so fühlen – hat ja auch was stehen wir zu unserer Leistung, oder nicht – hat ja schon was von Herrschen.
Außerdem war mir nicht bewusst, das ich im August einen Schaden hatte – Also ist ja auch schon lang her. Also fragte ich Woher er die Information hätte. Da er offensichtlich jedoch sein Pensum für den heutigen Tag erledigt hatte, musste ich auf eine Antwort warten.

Da viel mir ein woher. Irgendwann im Juli 2023 fuhr ein Taxler viel zu schnell die Ardetzenbergstraße runter und sein Rückspiegel kollidierte mit meinem. ein kleines Stück brach ab und der Taxler führ weiter. Da ich nur damals das Taxi sah aber keine Nummer und die Stadtpolizei daher meinte eine Anzeige ist sinnlos, weil eh nix passiert. Auf Grund der polizeilichen Ablehnung eine Anzeige zu machen, habe ich ein Photo gemacht und es auf meinem Instagramm Account gestellt. So weit so gut oder eigentlich schlecht.

Also hat die Versicherung meine Sozialen Medien durchforstet und festgestellt ah da gibt es einen Vorschaden.

Das ist echt ein Wahnsinn – ich hoffe nur das es wenigstens eine Software war, die das herausgefunden hat und kein Mensch. Wenn ein Mensch dann Hut ab die Kostenrechnung der Versicherung möchte ich nicht wissen, Schaden laut Werkstatt 1.300
Werkstatt, Sachbearbeiter, Gutachter, meine Zeit und die Zeit irgendeines armen Menschen, der sich Soziale Medien von vollkommen „normalen“ Menschen wie mir – naja a paar Fotos sind schon lustig, ansonsten eher für die große Rundablage, anschauen muss zusammenrechnet – sind wir locker beim 3 oder 4 fachen des Schaden. Wenn ich den Schaden ablösen wollen würde wären wir beim 5 bis 6 fachen.
Der Materialwert des Schadens ist im übrigen 100€.

Das war der Schaden von dem ich dachte, die Versicherung meint Altschaden – mit 700€ abgelöst – von einer Versicherung aus Wien die das auch im Namen trägt.

Der Schaden vom Taxler, mit der Fahrerflucht:

Und der jüngste Schaden

Wer zwischen den Schäden keine Veränderung sieht, kann sich sicher mit dieser Referenz bei Versicherungen als Gutachter bewerben oder mir einen Rückspiegel schenken.

Natürlich ist dieser Text als Satire zu verstehen und hat ganz und gar nichts mit irgendwelchen realen Ereignissen zu tun.

SQL Beispiele

Jedes Datum in einem Zeitraum finden

Hin und wieder braucht man jedes einzelne Datum in einem Zeitraum in SQL. Oft wird eine Tabelle mit Zahlen dafür verwendet (nur ein Wert von 1…) verwendet.

Eine Alternative ist dieses kleine Select mit dem jedes Datum in einem Zeitraum gefunden werden kann.

DECLARE @StartDate as Date =convert(date,'18.09.23',4)
DECLARE @EndDate as Date =convert(date,'24.09.23',4)

SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, convert(date,@EndDate,4))
FROM sys.all_objects a
CROSS JOIN sys.all_objects b

Grundlegend tut das Select nichts anderes als einen Cross Join über alle objekte in der Datenbank zu machen um eine möglichst große Zahlreihe zu erstellen. Diese Zahlenreihe wird anhand der Row_Number Funktion. Dann muss nur noch das Datum ermittelt werden.

SQL geht ganz einfach 🙂

Twitter API

Habe heute eine Weile gebraucht bis ich einen Tweet absetzen konnte. Für mich und im Fall, das es jemand anderer braucht.

Twitter Developer Platform overview | Docs | Twitter Developer Platform

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class TwitterClient
{
    private readonly string consumerKey;
    private readonly string consumerSecret;
    private readonly string accessToken;
    private readonly string tokenSecret;

    private readonly HttpClient httpClient;

    public TwitterClient(HttpClient httpClient, string consumerKey, string consumerSecret, string accessToken, string tokenSecret)
    {
        this.httpClient = httpClient;
        this.consumerKey = consumerKey;
        this.consumerSecret = consumerSecret;
        this.accessToken = accessToken;
        this.tokenSecret = tokenSecret;
    }

    public async Task PostTweet(string text)
    {
        var timstamp = CreateTimestamp();
        var nonce = CreateNonce();
        var body = JsonSerializer.Serialize(new { text });
        var uri = new Uri("https://api.twitter.com/2/tweets");

        var request = new HttpRequestMessage
        {
            RequestUri = uri,
            Method = HttpMethod.Post,
            Content = new StringContent(body, Encoding.ASCII, "application/json")
        };

        var signatureBase64 = CreateSignature(uri.ToString(), "POST", nonce, timstamp);

        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("OAuth",
            $@"oauth_consumer_key=""{Uri.EscapeDataString(consumerKey)}""",
            $@",oauth_token=""{Uri.EscapeDataString(accessToken)}""",
            $@",oauth_signature_method=""HMAC-SHA1"",oauth_timestamp=""{Uri.EscapeDataString(timstamp)}""",
            $@",oauth_nonce=""{Uri.EscapeDataString(nonce)}"",oauth_version=""1.0""",
            $@",oauth_signature=""{Uri.EscapeDataString(signatureBase64)}""");

        var response = await httpClient.SendAsync(request);

        response.EnsureSuccessStatusCode();
    }

    private string CreateSignature(string url, string method, string nonce, string timestamp)
    {
        var parameters = new Dictionary<string, string>();

        parameters.Add("oauth_consumer_key", consumerKey);
        parameters.Add("oauth_nonce", nonce);
        parameters.Add("oauth_signature_method", "HMAC-SHA1");
        parameters.Add("oauth_timestamp", timestamp);
        parameters.Add("oauth_token", accessToken);
        parameters.Add("oauth_version", "1.0");

        var sigBaseString = CombineQueryParams(parameters);

        var signatureBaseString = $"{method}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(sigBaseString)}";

        var compositeKey = $"{Uri.EscapeDataString(consumerSecret)}&{Uri.EscapeDataString(tokenSecret)}";

        using (var hasher = new HMACSHA1(Encoding.ASCII.GetBytes(compositeKey)))
        {
            return Convert.ToBase64String(hasher.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString)));
        }
    }

    private string CreateTimestamp()
    {
        var totalSeconds = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc))
            .TotalSeconds;

        return Convert.ToInt64(totalSeconds).ToString();
    }

    private string CreateNonce()
    {
        return Convert.ToBase64String(
            new ASCIIEncoding().GetBytes(
                DateTime.Now.Ticks.ToString()));
    }

    public string CombineQueryParams(Dictionary<string, string> parameters)
    {
        var sb = new StringBuilder();
        var first = true;

        foreach (var param in parameters)
        {
            if (!first)
            {
                sb.Append("&");
            }

            sb.Append(param.Key);
            sb.Append("=");
            sb.Append(Uri.EscapeDataString(param.Value));

            first = false;
        }

        return sb.ToString

Imports System.Net.Http
Imports System.Security.Cryptography
Imports System.Text
Imports System.Text.Json

Public Class TwitterClient
    Private ReadOnly consumerKey As String
    Private ReadOnly consumerSecret As String
    Private ReadOnly accessToken As String
    Private ReadOnly tokenSecret As String

    Private ReadOnly httpClient As HttpClient

    Public Sub New(httpClient As HttpClient, consumerKey As String, consumerSecret As String, accessToken As String, tokenSecret As String)
        Me.httpClient = httpClient
        Me.consumerKey = consumerKey
        Me.consumerSecret = consumerSecret
        Me.accessToken = accessToken
        Me.tokenSecret = tokenSecret
    End Sub

    Public Async Function PostTweet(text As String) As Task
        Dim timstamp = CreateTimestamp()
        Dim nonce = CreateNonce()
        Dim body = JsonSerializer.Serialize(New With {Key .text = text})
        Dim uri = New Uri("https://api.twitter.com/2/tweets")

        Dim request = New HttpRequestMessage With {
            .RequestUri = uri,
            .Method = HttpMethod.Post,
            .Content = New StringContent(body, Encoding.ASCII, "application/json")
        }

        Dim signatureBase64 = CreateSignature(uri.ToString(), "POST", nonce, timstamp)

        request.Headers.Authorization = New System.Net.Http.Headers.AuthenticationHeaderValue("OAuth", $"oauth_consumer_key=""{Uri.EscapeDataString(consumerKey)}"",oauth_token=""{Uri.EscapeDataString(accessToken)}"", oauth_signature_method=""HMAC-SHA1"",oauth_timestamp=""{Uri.EscapeDataString(timstamp)}"",oauth_nonce=""{Uri.EscapeDataString(nonce)}"", oauth_version=""1.0"", oauth_signature=""{Uri.EscapeDataString(signatureBase64)}""")

        Dim response = Await httpClient.SendAsync(request)

        response.EnsureSuccessStatusCode()
    End Function

    Private Function CreateSignature(url As String, method As String, nonce As String, timestamp As String) As String
        Dim parameters = New Dictionary(Of String, String)()

        parameters.Add("oauth_consumer_key", consumerKey)
        parameters.Add("oauth_nonce", nonce)
        parameters.Add("oauth_signature_method", "HMAC-SHA1")
        parameters.Add("oauth_timestamp", timestamp)
        parameters.Add("oauth_token", accessToken)
        parameters.Add("oauth_version", "1.0")

        Dim sigBaseString = CombineQueryParams(parameters)

        Dim signatureBaseString = $"{method}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(sigBaseString)}"

        Dim compositeKey = $"{Uri.EscapeDataString(consumerSecret)}&{Uri.EscapeDataString(tokenSecret)}"

        Using hasher = New HMACSHA1(Encoding.ASCII.GetBytes(compositeKey))
            Return Convert.ToBase64String(hasher.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString)))
        End Using
    End Function

    Private Function CreateTimestamp() As String
        Dim totalSeconds = (DateTime.UtcNow - New DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds

        Return Convert.ToInt64(totalSeconds).ToString()
    End Function

    Private Function CreateNonce() As String
        Return Convert.ToBase64String(New ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()))
    End Function

    Public Function CombineQueryParams(parameters As Dictionary(Of String, String)) As String
        Dim sb = New StringBuilder()
        Dim first = True

        For Each param In parameters
            If Not first Then
                sb.Append("&")
            End If

            sb.Append(param.Key)
            sb.Append("=")
            sb.Append(Uri.EscapeDataString(param.Value))

            first = False
        Next

        Return sb.ToString()
    End Function
End Class

OpenAI und PDF

Rambo sagte einmal „Das ist eine lange Geschichte“ und war damit eine kurze Zusammenfassung der ganzen Rambo Filme.

Mit Open AI soll das nun möglich sein.
Also los aus nostalgischen Gründen werde ich ein Ajax mit einem Webservice machen – quisi quasi fast ein Micoservice 🙂
Also machen wir mal das Upload.

using Google.Protobuf.WellKnownTypes;
using Org.BouncyCastle.Asn1.Ocsp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.Services.Description;
using System.Web.UI.WebControls;

namespace GPT3PDF
{
    /// <summary>
    /// Zusammenfassungsbeschreibung für AnaylseService
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // Wenn der Aufruf dieses Webdiensts aus einem Skript zulässig sein soll, heben Sie mithilfe von ASP.NET AJAX die Kommentarmarkierung für die folgende Zeile auf. 
    [System.Web.Script.Services.ScriptService]
    public class AnaylseService : System.Web.Services.WebService
    {

        [WebMethod]
        public string HelloWorld()
        {
            return "Hello World";
        }

        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
        public PDFResponse Upload()
        {

            HttpFileCollection Files = HttpContext.Current.Request.Files;


            HttpPostedFile File = Files[0];
            byte[] pdfBytes;


            using (var binaryReader = new BinaryReader(File.InputStream))
            {
                pdfBytes = binaryReader.ReadBytes(File.ContentLength);
            }


            try
            {
                PDFAnalysis pDFAnalysis = new PDFAnalysis();
                string extract = pDFAnalysis.AnalyzePDF(pdfBytes);
              
                return new PDFResponse() { Message = extract };
                
            }
            catch (Exception ex)
            {
              //Error do something with it or leave it :)  

            }

            return new PDFResponse() { Message = "Error! " };

        }
    }

    public class PDFResponse
    {
        public string Message { get; set; } 
        
    }
}


Nun müssen wir nur noch den Text aus dem PDF bekommen. Zum Glück müssen wir das Rad nicht neu erfinden und können auf zum Beispiel PdfPig | Read and extract text and other content from PDFs in C# (port of PDFBox) (uglytoad.github.io) verwenden.
Das extrahieren geht ganz einfach:

  private string ExtractTextFromPDF(byte[] file)
        {

            string extractedText;
            using (var pdf = PdfDocument.Open(file))
            {
                var sb = new StringBuilder();
                for (var i = 0; i < pdf.NumberOfPages; i++)
                {
                    var page = pdf.GetPage(i + 1);
                    sb.Append(page.Text);
                }
                extractedText = sb.ToString();

            }
            return extractedText;
        }

Das File übergeben wir als Byte[] damit wir es nicht irgendwo auf dem Server zwischen speichern müssen.


Nostalgisches Upload und Text extrahieren ist doch einfach oder?

So als nächstes kommt der lustige Teil. Der Aufruf des OpenAI Api.

  private string AnalyzeTextWithChatGpt(string extractedText)
        {
            string responseContent = string.Empty;
            try
            {
                List<MessageObj> roles = new List<MessageObj>();
                roles.Add(new MessageObj() { role = "system", content = "Summarize the text" });
                roles.Add(new MessageObj() { role = "user", content = extractedText });
                //MessageObj[] msg;

                // Set up the OpenAI API client
                var content = new StringContent(
            JsonConvert.SerializeObject(new
            {
                model = "gpt-3.5-turbo",
                messages = roles


            }),
            Encoding.UTF8,
            "application/json");

                client.DefaultRequestHeaders.Clear();
                client.DefaultRequestHeaders.Add("Authorization", "Bearer " + chatGptApiKey);
                //var response = client.PostAsync("https://api.openai.com/v1/engines/davinci-codex/completions", content).Result;

                var response = client.PostAsync("https://api.openai.com/v1/chat/completions", content).Result;

                responseContent = response.Content.ReadAsStringAsync().Result;

                var explanation = JsonConvert.DeserializeObject<dynamic>(responseContent).choices[0].message.content;
                return explanation;
            }
            catch (Exception ex)
            {

                Exception exception = new Exception(responseContent, ex);
                throw exception;
            }


        }

Der ganze Source ist unter gpiwonka/GPT3PDF (github.com). Es fehlen natürlich eine gescheite Fehlerbehandlung und auch ein Verfahren, wenn das PDF zu groß ist. Hierzu müsste das PDF gesplittet werden und dann jeder einzelne Teil zusammengefasst werden. Live anzuschauen unter: gpt3pdf20230526121655.azurewebsites.net

.Net Maui Stacklayout und der Scrollviewer

In der aktuellen Version von .Net Maui gibt es einen lustigen Fehler. Dieser tritt bei Android und wenn ein Stacklayout scrollen lassen will. Das funktioniert beim ersten Mal super, jedoch passiert es das beim zweiten Mal die Seite einfach leer bleibt. Am besten ein Grid verwenden.

using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace MyApp
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            // Create the ScrollView
            var scrollView = new ScrollView();

            // Create the StackLayout
            var stackLayout = new StackLayout();

            // Add some content to the StackLayout
            for (int i = 1; i <= 20; i++)
            {
                var label = new Label
                {
                    Text = $"Item {i}",
                    FontSize = 20,
                    HorizontalOptions = LayoutOptions.Center
                };

                stackLayout.Children.Add(label);
            }

            // Set the StackLayout as the content of the ScrollView
            scrollView.Content = stackLayout;

            // Set the ScrollView as the content of the page
            Content = scrollView;
        }
    }
}

OpenAI und Visual Studio Extension

Als kleine Fingerübung dachte ich mir, dass eine Visual Studio Extension gut wäre die Code erklärt – dank OpenAI sollte das ja kein Problem sein.

War es auch nicht.

Zuerst installierte ich das Extensibilty Template Pack 2022 für Visual Studio 2022.
Danach konnte es beginnen:

Als erstes fügte ich ein Tool Window hinzu. Ein Toolwindow ist im Prinzip nichts anderes als eine XAML User control und eine Klasse die von BaseToolWindow ableitet

  public class ExplicareToolWindow : BaseToolWindow<ExplicareToolWindow>
    {
        public override string GetTitle(int toolWindowId) => "Explicare";

        public override Type PaneType => typeof(Pane);

        public override async Task<FrameworkElement> CreateAsync(int toolWindowId, CancellationToken cancellationToken)
        {

           var dte = await Package.GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2;

            return new ExplicareToolWindowControl(dte,Package);
        }

        [Guid("b8381bcb-59d1-4b5c-9951-fdbfb44990c9")]
        internal class Pane : ToolWindowPane
        {
            public Pane()
            {
                BitmapImageMoniker = KnownMonikers.ToolWindow;
            }
        }
    }

ExplicareToolWindowControl(dte,Package); ist die eigentliche UserControl – in der wir beliebige UI Elemente einbauen können.
Ich habe jetzt nur einen Button eingebaut den ich „Explain to me“ genannt habe.
Schnell und Schmutzig im Code-Behind ein Button Click Event gemacht.
Nun den Text aus dem Visual Studio Editor auslesen dies geschieht mit:

 var textSelection = (TextSelection)MyDTE.ActiveDocument.Selection;

                if (String.IsNullOrEmpty(textSelection.Text))
                {
                    TextDocument doc = (TextDocument)(MyDTE.ActiveDocument.Object("TextDocument"));

                    var p = doc.StartPoint.CreateEditPoint();
                    TextToExplain = p.GetText(doc.EndPoint);

                }
                else
                {
                    TextToExplain = textSelection.Text;

                }

So das schwierigste habe ich hinter mir nun noch einen Aufruf des OpenAI API aufrufen. Das geht eigentlich ganz einfach

  var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");


                var response = client.PostAsync("https://api.openai.com/v1/completions", content).Result;
                if (response.IsSuccessStatusCode)
                {
                    string responseStr = await response.Content.ReadAsStringAsync();

                    var result = JsonConvert.DeserializeObject<OpenAIResponse>(responseStr);
                    explained = result.choices[0].text;
                }
                else
                {
                    throw new BadRequestExecption();
                }

Den ganzen Source ist auf Git Hub gpiwonka/Explicare: A small extension for Visual Studio to better understand code using OpenAI. (github.com)

Gibt es auch als Extension: Explicare – Visual Studio Marketplace

Code Schnipsel: Service Provider

Dependency Injection macht das Leben einfacher auch in .NET MAUI. Hin und wieder ist es notwendig ein Service zu referenzieren das nicht über den Konstruktor kommt. Hier kommt ein Code Schnipsel für einen Service Provider:

public static class ServiceProvider
{
    public static TService GetService<TService>()
        => Current.GetService<TService>();

    public static IServiceProvider Current
        =>
#if WINDOWS10_0_17763_0_OR_GREATER
			MauiWinUIApplication.Current.Services;
#elif ANDROID
            MauiApplication.Current.Services;
#elif IOS || MACCATALYST
			MauiUIApplicationDelegate.Current.Services;
#else
			null;
#endif
}

Erläuterung:

Methode GetService():

Diese Methode ist eine Abkürzung, um einen Dienst vom Typ TService zu erhalten.
Sie delegiert den Aufruf an die Methode GetService() des aktuellen IServiceProviders.
Eigenschaft Current: Diese Eigenschaft gibt den aktuellen IServiceProvider auf der Basis der Plattform zurück.
Sie verwendet Präprozessor-Direktiven (#if, #elif, #else, #endif) zur bedingten Kompilierung unterschiedlichen Codes auf der Grundlage der Plattform.
Die plattformspezifische Eigenschaft Current wird auf der Grundlage der Kompilierungskonstanten ausgewählt:
Für Windows (UWP) mit Version 17763.0 oder höher wird MauiWinUIApplication.Current.Services verwendet.
Für Android wird MauiApplication.Current.Services verwendet.
Für iOS und Mac Catalyst verwendet es MauiUIApplicationDelegate.Current.Services.
Für andere Plattformen wird die Eigenschaft als Fallback auf null gesetzt.
Diese ServiceProvider-Klasse eine einfache und konsistente Möglichkeit bietet, Dienste in einer plattformübergreifenden MAUI-Anwendung abzurufen, wobei plattformspezifische Details, der Einfachheit halber, weggelassen werden. Das Code Schnipsel Service Provider verwendet die bedingte Kompilierung, um die Unterschiede bei der Abfrage von Diensten auf der Grundlage des zugrunde liegenden Betriebssystems zu behandeln.

Alle CodeSchnipsel für MAUI