From f35dafeac22fc1964be049dd15aec1e861454c57 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Thu, 8 Jan 2026 17:50:29 +0100 Subject: [PATCH] Gemini Test --- GeminiTest/Myc.Api.MarkdownStream.pas | 359 ++++++++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 GeminiTest/Myc.Api.MarkdownStream.pas diff --git a/GeminiTest/Myc.Api.MarkdownStream.pas b/GeminiTest/Myc.Api.MarkdownStream.pas new file mode 100644 index 0000000..9d77d3b --- /dev/null +++ b/GeminiTest/Myc.Api.MarkdownStream.pas @@ -0,0 +1,359 @@ +unit Myc.Api.MarkdownStream; + +interface + +uses + System.SysUtils, + System.Classes, + System.IOUtils, + System.Threading, + System.Generics.Collections, + System.JSON, + FMX.WebBrowser; + +type + TBlockType = (btMarkdown, btRawText, btHtml); + + /// + /// Manages an incremental Markdown/HTML stream within a TWebBrowser. + /// Supports block-based rendering and collapsible raw text sections. + /// + IMarkdownStream = interface + ['{A1B2C3D4-E5F6-4788-9900-112233445566}'] + procedure InitializeBrowser(const Browser: TWebBrowser); + procedure BeginBlock(const AType: TBlockType; const ATitle: string = ''); + procedure Append(const Content: string); + procedure Clear; + end; + + TMarkdownStream = class(TInterfacedObject, IMarkdownStream) + strict private + type + TCmd = record + Func: string; + Arg: string; + constructor Create(const F, A: string); + end; + strict private + FBrowser: TWebBrowser; + FIsReady: Boolean; + FCommandQueue: TList; + FTempFileName: string; + + procedure OnDidFinishLoad(Sender: TObject); + function GetBaseHtml: string; + procedure ExecuteJS(const FunctionName, Data: string); + procedure FlushQueue; + function BlockTypeToString(const AType: TBlockType): string; + public + constructor Create; + destructor Destroy; override; + procedure InitializeBrowser(const Browser: TWebBrowser); + + procedure BeginBlock(const AType: TBlockType; const ATitle: string = ''); + procedure Append(const Content: string); + procedure Clear; + end; + +implementation + +{ TMarkdownStream.TCmd } + +constructor TMarkdownStream.TCmd.Create(const F, A: string); +begin + Func := F; + Arg := A; +end; + +{ TMarkdownStream } + +constructor TMarkdownStream.Create; +begin + inherited; + FCommandQueue := TList.Create; + FIsReady := False; + FTempFileName := TPath.Combine(TPath.GetTempPath, 'myc_md_stream.html'); +end; + +destructor TMarkdownStream.Destroy; +begin + if Assigned(FBrowser) then + FBrowser.OnDidFinishLoad := nil; + FCommandQueue.Free; + if FileExists(FTempFileName) then + try + TFile.Delete(FTempFileName); + except + end; + inherited; +end; + +procedure TMarkdownStream.InitializeBrowser(const Browser: TWebBrowser); +begin + FBrowser := Browser; + FIsReady := False; + FCommandQueue.Clear; + + if Assigned(FBrowser) then + begin + FBrowser.OnDidFinishLoad := OnDidFinishLoad; + try + TFile.WriteAllText(FTempFileName, GetBaseHtml, TEncoding.UTF8); + FBrowser.Navigate('file://' + FTempFileName); + except + on E: Exception do + ; + end; + end; +end; + +procedure TMarkdownStream.OnDidFinishLoad(Sender: TObject); +begin + TThread.ForceQueue( + nil, + procedure + begin + FIsReady := True; + FlushQueue; + end + ); +end; + +procedure TMarkdownStream.FlushQueue; +var + cmd: TCmd; +begin + if not Assigned(FBrowser) then + exit; + for cmd in FCommandQueue do + ExecuteJS(cmd.Func, cmd.Arg); + FCommandQueue.Clear; +end; + +function TMarkdownStream.BlockTypeToString(const AType: TBlockType): string; +begin + case AType of + btMarkdown: Result := 'markdown'; + btRawText: Result := 'text'; + btHtml: Result := 'html'; + end; +end; + +procedure TMarkdownStream.BeginBlock(const AType: TBlockType; const ATitle: string); +var + payload: string; +begin + payload := BlockTypeToString(AType) + '|' + ATitle; + if FIsReady then + ExecuteJS('startBlock', payload) + else + FCommandQueue.Add(TCmd.Create('startBlock', payload)); +end; + +procedure TMarkdownStream.Append(const Content: string); +begin + if Content.IsEmpty then + exit; + if FIsReady then + ExecuteJS('appendData', Content) + else + FCommandQueue.Add(TCmd.Create('appendData', Content)); +end; + +procedure TMarkdownStream.Clear; +begin + FCommandQueue.Clear; + if FIsReady then + ExecuteJS('clearAll', '') + else + FCommandQueue.Add(TCmd.Create('clearAll', '')); +end; + +procedure TMarkdownStream.ExecuteJS(const FunctionName, Data: string); +var + jsCommand: string; + jsonVal: TJSONValue; +begin + if not Assigned(FBrowser) then + exit; + + // Sicherer Umgang mit leeren Daten und korrektes Escaping + jsonVal := TJSONString.Create(Data); + try + jsCommand := Format('window.%s(%s);', [FunctionName, jsonVal.ToJSON]); + try + FBrowser.EvaluateJavaScript(jsCommand); + except + // Browser-Context evtl. während der Zerstörung nicht mehr valide + end; + finally + jsonVal.Free; + end; +end; + +function TMarkdownStream.GetBaseHtml: string; +begin + Result := + ''' + + + + + + + + + + + + +
+
+ + + + + '''; +end; + +end.