Gemini Test

This commit is contained in:
Michael Schimmel
2026-01-08 17:50:11 +01:00
parent a62ba89bca
commit 0efce47972
4 changed files with 85 additions and 75 deletions
+2 -1
View File
@@ -5,7 +5,8 @@ uses
FMX.Forms, FMX.Forms,
MainUnit in 'MainUnit.pas' {Form1}, MainUnit in 'MainUnit.pas' {Form1},
Myc.Api.Gemini in 'Myc.Api.Gemini.pas', Myc.Api.Gemini in 'Myc.Api.Gemini.pas',
Myc.System.Quota in 'Myc.System.Quota.pas'; Myc.System.Quota in 'Myc.System.Quota.pas',
Myc.Api.MarkdownStream in 'Myc.Api.MarkdownStream.pas';
{$R *.res} {$R *.res}
+7 -8
View File
@@ -132,6 +132,7 @@
</DCCReference> </DCCReference>
<DCCReference Include="Myc.Api.Gemini.pas"/> <DCCReference Include="Myc.Api.Gemini.pas"/>
<DCCReference Include="Myc.System.Quota.pas"/> <DCCReference Include="Myc.System.Quota.pas"/>
<DCCReference Include="Myc.Api.MarkdownStream.pas"/>
<BuildConfiguration Include="Base"> <BuildConfiguration Include="Base">
<Key>Base</Key> <Key>Base</Key>
</BuildConfiguration> </BuildConfiguration>
@@ -175,6 +176,12 @@
<Overwrite>true</Overwrite> <Overwrite>true</Overwrite>
</Platform> </Platform>
</DeployFile> </DeployFile>
<DeployFile LocalName="Win32\Debug\GeminiTest.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>GeminiTest.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="Win64\Debug\GeminiTest.exe" Configuration="Debug" Class="ProjectOutput"> <DeployFile LocalName="Win64\Debug\GeminiTest.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win64"> <Platform Name="Win64">
<RemoteName>GeminiTest.exe</RemoteName> <RemoteName>GeminiTest.exe</RemoteName>
@@ -1139,14 +1146,6 @@
<ProjectRoot Platform="Win64x" Name="$(PROJECTNAME)"/> <ProjectRoot Platform="Win64x" Name="$(PROJECTNAME)"/>
</Deployment> </Deployment>
<Platforms> <Platforms>
<Platform value="Android">False</Platform>
<Platform value="Android64">False</Platform>
<Platform value="iOSDevice32">False</Platform>
<Platform value="iOSDevice64">False</Platform>
<Platform value="iOSSimARM64">False</Platform>
<Platform value="OSX32">False</Platform>
<Platform value="OSX64">False</Platform>
<Platform value="OSXARM64">False</Platform>
<Platform value="Win32">False</Platform> <Platform value="Win32">False</Platform>
<Platform value="Win64">True</Platform> <Platform value="Win64">True</Platform>
</Platforms> </Platforms>
+9 -6
View File
@@ -11,6 +11,7 @@ object Form1: TForm1
DesignerMasterStyle = 0 DesignerMasterStyle = 0
object AskButton: TButton object AskButton: TButton
Anchors = [akRight, akBottom] Anchors = [akRight, akBottom]
Default = True
Position.X = 800.000000000000000000 Position.X = 800.000000000000000000
Position.Y = 577.000000000000000000 Position.Y = 577.000000000000000000
TabOrder = 1 TabOrder = 1
@@ -85,16 +86,14 @@ object Form1: TForm1
StyleLookup = '' StyleLookup = ''
TabOrder = 0 TabOrder = 0
Text = 'Chat' Text = 'Chat'
object ChatMemo: TMemo ExplicitSize.cx = 69.000000000000000000
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap] ExplicitSize.cy = 26.000000000000000000
DataDetectorTypes = [] object ChatBrowser: TWebBrowser
Align = Client Align = Client
Size.Width = 906.000000000000000000 Size.Width = 906.000000000000000000
Size.Height = 535.000000000000000000 Size.Height = 535.000000000000000000
Size.PlatformDefault = False Size.PlatformDefault = False
TabOrder = 1 WindowsEngine = EdgeOnly
Viewport.Width = 902.000000000000000000
Viewport.Height = 531.000000000000000000
end end
end end
object CacheTabItem: TTabItem object CacheTabItem: TTabItem
@@ -108,6 +107,8 @@ object Form1: TForm1
StyleLookup = '' StyleLookup = ''
TabOrder = 0 TabOrder = 0
Text = 'Cache' Text = 'Cache'
ExplicitSize.cx = 53.000000000000000000
ExplicitSize.cy = 26.000000000000000000
object CacheMemo: TMemo object CacheMemo: TMemo
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap] Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
DataDetectorTypes = [] DataDetectorTypes = []
@@ -131,6 +132,8 @@ object Form1: TForm1
StyleLookup = '' StyleLookup = ''
TabOrder = 0 TabOrder = 0
Text = 'SysInstr' Text = 'SysInstr'
ExplicitSize.cx = 61.000000000000000000
ExplicitSize.cy = 26.000000000000000000
object SystemInstructionMemo: TMemo object SystemInstructionMemo: TMemo
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap] Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
DataDetectorTypes = [] DataDetectorTypes = []
+67 -60
View File
@@ -20,8 +20,10 @@ uses
FMX.StdCtrls, FMX.StdCtrls,
FMX.Edit, FMX.Edit,
FMX.ListBox, FMX.ListBox,
FMX.TabControl,
FMX.WebBrowser,
Myc.Api.Gemini, Myc.Api.Gemini,
FMX.TabControl; Myc.Api.MarkdownStream;
type type
TForm1 = class(TForm) TForm1 = class(TForm)
@@ -32,15 +34,15 @@ type
AskMemo: TMemo; AskMemo: TMemo;
ResetChatButton: TButton; ResetChatButton: TButton;
TabControl1: TTabControl; TabControl1: TTabControl;
ChatTabItem: TTabItem;
CacheTabItem: TTabItem; CacheTabItem: TTabItem;
SysInstrTabItem: TTabItem; SysInstrTabItem: TTabItem;
ChatMemo: TMemo;
CacheMemo: TMemo; CacheMemo: TMemo;
SystemInstructionMemo: TMemo; SystemInstructionMemo: TMemo;
UploadCacheButton: TButton; UploadCacheButton: TButton;
ThinkingCheckBox: TCheckBox; ThinkingCheckBox: TCheckBox;
GlobalQuoteLabel: TLabel; GlobalQuoteLabel: TLabel;
ChatTabItem: TTabItem;
ChatBrowser: TWebBrowser;
procedure FormCreate(Sender: TObject); procedure FormCreate(Sender: TObject);
procedure AskButtonClick(Sender: TObject); procedure AskButtonClick(Sender: TObject);
procedure Edit1Exit(Sender: TObject); procedure Edit1Exit(Sender: TObject);
@@ -48,8 +50,8 @@ type
procedure UploadCacheButtonClick(Sender: TObject); procedure UploadCacheButtonClick(Sender: TObject);
private private
FChat: IGeminiChat; FChat: IGeminiChat;
// Holds the client configured with the active cache
FCachedClient: IGeminiClient; FCachedClient: IGeminiClient;
FMarkdown: IMarkdownStream;
procedure InitChat; procedure InitChat;
procedure UpdateQuotaDisplay; procedure UpdateQuotaDisplay;
public public
@@ -63,37 +65,44 @@ implementation
uses uses
System.Threading, System.Threading,
Myc.System.Quota; // Einbindung für den globalen Zähler Myc.System.Quota;
{$R *.fmx} {$R *.fmx}
const const
FallbackModel = 'gemini-3-flash-preview'; FallbackModel = 'gemini-2.5-flash-lite';
procedure TForm1.FormCreate(Sender: TObject); procedure TForm1.FormCreate(Sender: TObject);
begin begin
// 1. Markdown Stream initialisieren und mit Browser verbinden
FMarkdown := TMarkdownStream.Create;
FMarkdown.InitializeBrowser(ChatBrowser);
// Load default instructions if file exists // Load default instructions if file exists
if FileExists('T:\Myc\KI\gemini.md') then if FileExists('T:\Myc\KI\gemini.md') then
SystemInstructionMemo.Lines.LoadFromFile('T:\Myc\KI\gemini.md', TEncoding.UTF8); SystemInstructionMemo.Lines.LoadFromFile('T:\Myc\KI\gemini.md', TEncoding.UTF8);
// Initialen Global-Stand anzeigen
UpdateQuotaDisplay; UpdateQuotaDisplay;
GetAvailableModels; GetAvailableModels;
InitChat; InitChat;
end; end;
procedure TForm1.InitChat; procedure TForm1.InitChat;
begin begin
// Browser komplett leeren
if Assigned(FMarkdown) then
FMarkdown.Clear;
FMarkdown.BeginBlock(btMarkdown);
if Assigned(FCachedClient) then if Assigned(FCachedClient) then
begin begin
FChat := FCachedClient.StartChat; FChat := FCachedClient.StartChat;
ChatMemo.Lines.Add('--- New Chat Session (Cached Content Active) ---'); FMarkdown.Append('*--- New Chat Session ---* (Cached Content Active)');
end end
else else
begin begin
FChat := nil; // Lazy init in AskButton FMarkdown.Append('*--- New Chat Session ---*');
ChatMemo.Lines.Add('--- New Chat Session ---'); FChat := nil;
end; end;
end; end;
@@ -126,33 +135,39 @@ begin
begin begin
var client: IGeminiClient := TGeminiClient.Create(apiKey, modelName); var client: IGeminiClient := TGeminiClient.Create(apiKey, modelName);
// 1. System Instructions (Ohne Hack, purer Text)
if not SystemInstructionMemo.Text.Trim.IsEmpty then if not SystemInstructionMemo.Text.Trim.IsEmpty then
client.SystemInstruction := SystemInstructionMemo.Text; client.SystemInstruction := SystemInstructionMemo.Text;
// 2. Native Thinking aktivieren
// Die API-Unit entscheidet, ob das Modell es unterstützt.
// Falls ja, wird der 'thinking' Parameter gesendet.
client.EnableThinking := ThinkingCheckBox.IsChecked; client.EnableThinking := ThinkingCheckBox.IsChecked;
FChat := client.StartChat; FChat := client.StartChat;
end; end;
AskButton.Enabled := False; AskButton.Enabled := False;
ChatMemo.Lines.Add(sLineBreak + 'User: "' + prompt + '"');
ChatMemo.Lines.Add('Gemini: ');
AskMemo.Text := ''; AskMemo.Text := '';
// --- USER BLOCK ---
FMarkdown.BeginBlock(btRawText, 'Prompt');
// Hier nutzen wir RawText für den Prompt selbst, um Markdown-Injection des Users zu verhindern,
// oder wir bleiben im Markdown Block für schöneres Rendering (Code-Blöcke des Users).
// Da User Markdown oft erwarten:
FMarkdown.Append(prompt);
TTask.Run( TTask.Run(
procedure procedure
var var
CurrentChat: IGeminiChat; CurrentChat: IGeminiChat;
resultData: TGeminiResult; resultData: TGeminiResult;
// State Tracking für den Stream
LastWasThought: Boolean;
FirstChunk: Boolean;
begin begin
CurrentChat := FChat; CurrentChat := FChat;
if CurrentChat = nil then if CurrentChat = nil then
exit; exit;
LastWasThought := False;
FirstChunk := True;
// --- STREAMING --- // --- STREAMING ---
resultData := resultData :=
CurrentChat.SendMessageStream( CurrentChat.SendMessageStream(
@@ -163,19 +178,30 @@ begin
nil, nil,
procedure procedure
begin begin
if IsThought then // Statuswechsel oder allererster Chunk -> Neuer Block
if FirstChunk or (IsThought <> LastWasThought) then
begin begin
// Visuelle Kennzeichnung für Gedanken if IsThought then
// (Hier einfach in eckigen Klammern, besser wäre Farbe) begin
ChatMemo.Text := ChatMemo.Text + ' [Think: ' + TextChunk + '] '; FMarkdown.BeginBlock(btMarkdown, 'Thinking...');
end // Optional: Kursiver Block für Gedanken
else // Wir hängen Gedanken oft als Zitat oder kursiv an
begin end
// Normale Antwort else
ChatMemo.Text := ChatMemo.Text + TextChunk; begin
// Wenn wir aus einem Gedanken kommen oder starten
FMarkdown.BeginBlock(btMarkdown, 'Gemini');
end;
LastWasThought := IsThought;
FirstChunk := False;
end; end;
ChatMemo.GoToTextEnd; // Inhalt anhängen
if IsThought then
FMarkdown.Append(TextChunk) // Wird im selben Markdown-Block gerendert
else
FMarkdown.Append(TextChunk);
end end
); );
end end
@@ -187,12 +213,13 @@ begin
procedure procedure
begin begin
if resultData.Text.StartsWith('Error') then if resultData.Text.StartsWith('Error') then
ChatMemo.Lines.Add(resultData.Text); begin
FMarkdown.BeginBlock(btMarkdown, 'Error');
FMarkdown.Append('> ' + resultData.Text);
end;
// Update Labels (Session & Global Registry)
QuotaLabel.Text := Format('Input: %d | Output: %d', [resultData.Usage.PromptTokens, resultData.Usage.CandidatesTokens]); QuotaLabel.Text := Format('Input: %d | Output: %d', [resultData.Usage.PromptTokens, resultData.Usage.CandidatesTokens]);
UpdateQuotaDisplay; UpdateQuotaDisplay;
AskButton.Enabled := True; AskButton.Enabled := True;
end end
); );
@@ -202,7 +229,8 @@ end;
procedure TForm1.Edit1Exit(Sender: TObject); procedure TForm1.Edit1Exit(Sender: TObject);
begin begin
GetAvailableModels; if ModelsComboBox.ItemIndex < 0 then
GetAvailableModels;
end; end;
procedure TForm1.GetAvailableModels; procedure TForm1.GetAvailableModels;
@@ -213,8 +241,6 @@ begin
if apiKey.IsEmpty then if apiKey.IsEmpty then
exit; exit;
ChatMemo.Lines.Add('Fetching available models...');
TTask.Run( TTask.Run(
procedure procedure
var var
@@ -232,27 +258,22 @@ begin
begin begin
if Length(modelList) = 0 then if Length(modelList) = 0 then
begin begin
ChatMemo.Lines.Add('No models found.'); FMarkdown.Append(sLineBreak + '*System: No models found.*' + sLineBreak);
exit; exit;
end; end;
ModelsComboBox.BeginUpdate; ModelsComboBox.BeginUpdate;
try try
ModelsComboBox.Items.Clear; ModelsComboBox.Items.Clear;
for mName in modelList do for mName in modelList do
ModelsComboBox.Items.Add(mName); ModelsComboBox.Items.Add(mName);
// Versuche Fallback zu finden (ggf. ohne Emoji suffix match)
// Vereinfacht: Wähle ersten Index
if ModelsComboBox.Items.Count > 0 then if ModelsComboBox.Items.Count > 0 then
begin begin
ModelsComboBox.ItemIndex := ModelsComboBox.Items.IndexOf(FallbackModel); ModelsComboBox.ItemIndex := ModelsComboBox.Items.IndexOf(FallbackModel);
if ModelsComboBox.ItemIndex < 0 then if ModelsComboBox.ItemIndex < 0 then
ModelsComboBox.ItemIndex := 0; ModelsComboBox.ItemIndex := 0;
end; end;
ChatMemo.Lines.Add(Format('Loaded %d models.', [Length(modelList)]));
finally finally
ModelsComboBox.EndUpdate; ModelsComboBox.EndUpdate;
end; end;
@@ -271,24 +292,22 @@ var
begin begin
apiKey := Edit1.Text; apiKey := Edit1.Text;
content := CacheMemo.Text; content := CacheMemo.Text;
// Keine künstlichen Prompts mehr, nur die reinen Instruktionen
sysInstr := SystemInstructionMemo.Text; sysInstr := SystemInstructionMemo.Text;
if content.Trim.IsEmpty then if content.Trim.IsEmpty then
begin begin
ChatMemo.Lines.Add('Error: Cache content is empty.'); FMarkdown.Append(sLineBreak + '**Error:** Cache content is empty.' + sLineBreak);
exit; exit;
end; end;
// Remove Emoji suffix if present from ComboBox for API call
if ModelsComboBox.ItemIndex > -1 then if ModelsComboBox.ItemIndex > -1 then
modelName := ModelsComboBox.Items[ModelsComboBox.ItemIndex].Split([' '])[0] modelName := ModelsComboBox.Items[ModelsComboBox.ItemIndex].Split([' '])[0]
else else
modelName := FallbackModel; modelName := FallbackModel;
UploadCacheButton.Enabled := False; UploadCacheButton.Enabled := False;
ChatMemo.Lines.Add('Creating Cache (this may take a moment)...'); // Hinweis im Browser anzeigen
FMarkdown.Append(sLineBreak + '*Creating Cache...*' + sLineBreak);
TTask.Run( TTask.Run(
procedure procedure
@@ -298,33 +317,22 @@ begin
errMsg: string; errMsg: string;
begin begin
try try
// 1. Client erstellen
client := TGeminiClient.Create(apiKey, modelName); client := TGeminiClient.Create(apiKey, modelName);
// 2. Cache erstellen (Content + SysInstr)
// Hinweis: Native Thinking ist ein Parameter der GENERIERUNG, nicht des Caches.
// Daher laden wir den Cache "neutral" hoch.
cacheName := client.CreateCache(content, sysInstr, 300); cacheName := client.CreateCache(content, sysInstr, 300);
// 3. Cache aktivieren
client.ActiveCacheName := cacheName; client.ActiveCacheName := cacheName;
// 4. Client speichern
// Wenn der User später "Ask" klickt, wird dort EnableThinking gesetzt
FCachedClient := client; FCachedClient := client;
TThread.Queue( TThread.Queue(
nil, nil,
procedure procedure
begin begin
ChatMemo.Lines.Add('Success: Cache created!'); FMarkdown.Append('**Success: Cache created!**' + sLineBreak);
ChatMemo.Lines.Add('ID: ' + cacheName); FMarkdown.Append('ID: `' + cacheName + '`' + sLineBreak);
InitChat; InitChat;
UploadCacheButton.Enabled := True; UploadCacheButton.Enabled := True;
end end
); );
except except
on E: Exception do on E: Exception do
begin begin
@@ -333,8 +341,7 @@ begin
nil, nil,
procedure procedure
begin begin
ChatMemo.Lines.Add('Cache Upload Failed:'); FMarkdown.Append('**Cache Upload Failed:** ' + errMsg + sLineBreak);
ChatMemo.Lines.Add(errMsg);
UploadCacheButton.Enabled := True; UploadCacheButton.Enabled := True;
end end
); );