Files
MycLib/GeminiTest/MainUnit.pas
T
Michael Schimmel a62ba89bca Gemini Test
2026-01-08 14:13:41 +01:00

348 lines
10 KiB
ObjectPascal

unit MainUnit;
interface
uses
System.SysUtils,
System.Types,
System.UITypes,
System.Classes,
System.Variants,
FMX.Types,
FMX.Controls,
FMX.Forms,
FMX.Graphics,
FMX.Dialogs,
FMX.Memo.Types,
FMX.ScrollBox,
FMX.Memo,
FMX.Controls.Presentation,
FMX.StdCtrls,
FMX.Edit,
FMX.ListBox,
Myc.Api.Gemini,
FMX.TabControl;
type
TForm1 = class(TForm)
AskButton: TButton;
Edit1: TEdit;
ModelsComboBox: TComboBox;
QuotaLabel: TLabel;
AskMemo: TMemo;
ResetChatButton: TButton;
TabControl1: TTabControl;
ChatTabItem: TTabItem;
CacheTabItem: TTabItem;
SysInstrTabItem: TTabItem;
ChatMemo: TMemo;
CacheMemo: TMemo;
SystemInstructionMemo: TMemo;
UploadCacheButton: TButton;
ThinkingCheckBox: TCheckBox;
GlobalQuoteLabel: TLabel;
procedure FormCreate(Sender: TObject);
procedure AskButtonClick(Sender: TObject);
procedure Edit1Exit(Sender: TObject);
procedure ResetChatButtonClick(Sender: TObject);
procedure UploadCacheButtonClick(Sender: TObject);
private
FChat: IGeminiChat;
// Holds the client configured with the active cache
FCachedClient: IGeminiClient;
procedure InitChat;
procedure UpdateQuotaDisplay;
public
procedure GetAvailableModels;
end;
var
Form1: TForm1;
implementation
uses
System.Threading,
Myc.System.Quota; // Einbindung für den globalen Zähler
{$R *.fmx}
const
FallbackModel = 'gemini-3-flash-preview';
procedure TForm1.FormCreate(Sender: TObject);
begin
// Load default instructions if file exists
if FileExists('T:\Myc\KI\gemini.md') then
SystemInstructionMemo.Lines.LoadFromFile('T:\Myc\KI\gemini.md', TEncoding.UTF8);
// Initialen Global-Stand anzeigen
UpdateQuotaDisplay;
GetAvailableModels;
InitChat;
end;
procedure TForm1.InitChat;
begin
if Assigned(FCachedClient) then
begin
FChat := FCachedClient.StartChat;
ChatMemo.Lines.Add('--- New Chat Session (Cached Content Active) ---');
end
else
begin
FChat := nil; // Lazy init in AskButton
ChatMemo.Lines.Add('--- New Chat Session ---');
end;
end;
procedure TForm1.ResetChatButtonClick(Sender: TObject);
begin
InitChat;
end;
procedure TForm1.UpdateQuotaDisplay;
begin
GlobalQuoteLabel.Text := 'Total: ' + TGlobalQuota.GetTotal.ToString + ', Today: ' + TGlobalQuota.GetDailyTotal.ToString;
end;
procedure TForm1.AskButtonClick(Sender: TObject);
var
apiKey: string;
modelName: string;
prompt: string;
begin
apiKey := Edit1.Text;
prompt := AskMemo.Text;
if ModelsComboBox.ItemIndex > -1 then
modelName := ModelsComboBox.Items[ModelsComboBox.ItemIndex]
else
modelName := FallbackModel;
// --- LAZY INIT ---
if FChat = nil then
begin
var client: IGeminiClient := TGeminiClient.Create(apiKey, modelName);
// 1. System Instructions (Ohne Hack, purer Text)
if not SystemInstructionMemo.Text.Trim.IsEmpty then
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;
FChat := client.StartChat;
end;
AskButton.Enabled := False;
ChatMemo.Lines.Add(sLineBreak + 'User: "' + prompt + '"');
ChatMemo.Lines.Add('Gemini: ');
AskMemo.Text := '';
TTask.Run(
procedure
var
CurrentChat: IGeminiChat;
resultData: TGeminiResult;
begin
CurrentChat := FChat;
if CurrentChat = nil then
exit;
// --- STREAMING ---
resultData :=
CurrentChat.SendMessageStream(
prompt,
procedure(const TextChunk: string; IsThought: Boolean)
begin
TThread.Queue(
nil,
procedure
begin
if IsThought then
begin
// Visuelle Kennzeichnung für Gedanken
// (Hier einfach in eckigen Klammern, besser wäre Farbe)
ChatMemo.Text := ChatMemo.Text + ' [Think: ' + TextChunk + '] ';
end
else
begin
// Normale Antwort
ChatMemo.Text := ChatMemo.Text + TextChunk;
end;
ChatMemo.GoToTextEnd;
end
);
end
);
// Abschluss im Main Thread
TThread.Queue(
nil,
procedure
begin
if resultData.Text.StartsWith('Error') then
ChatMemo.Lines.Add(resultData.Text);
// Update Labels (Session & Global Registry)
QuotaLabel.Text := Format('Input: %d | Output: %d', [resultData.Usage.PromptTokens, resultData.Usage.CandidatesTokens]);
UpdateQuotaDisplay;
AskButton.Enabled := True;
end
);
end
);
end;
procedure TForm1.Edit1Exit(Sender: TObject);
begin
GetAvailableModels;
end;
procedure TForm1.GetAvailableModels;
var
apiKey: string;
begin
apiKey := Edit1.Text.Trim;
if apiKey.IsEmpty then
exit;
ChatMemo.Lines.Add('Fetching available models...');
TTask.Run(
procedure
var
client: IGeminiClient;
modelList: TArray<string>;
begin
client := TGeminiClient.Create(apiKey);
modelList := client.ListModels;
TThread.Queue(
nil,
procedure
var
mName: string;
begin
if Length(modelList) = 0 then
begin
ChatMemo.Lines.Add('No models found.');
exit;
end;
ModelsComboBox.BeginUpdate;
try
ModelsComboBox.Items.Clear;
for mName in modelList do
ModelsComboBox.Items.Add(mName);
// Versuche Fallback zu finden (ggf. ohne Emoji suffix match)
// Vereinfacht: Wähle ersten Index
if ModelsComboBox.Items.Count > 0 then
begin
ModelsComboBox.ItemIndex := ModelsComboBox.Items.IndexOf(FallbackModel);
if ModelsComboBox.ItemIndex < 0 then
ModelsComboBox.ItemIndex := 0;
end;
ChatMemo.Lines.Add(Format('Loaded %d models.', [Length(modelList)]));
finally
ModelsComboBox.EndUpdate;
end;
end
);
end
);
end;
procedure TForm1.UploadCacheButtonClick(Sender: TObject);
var
apiKey: string;
content: string;
sysInstr: string;
modelName: string;
begin
apiKey := Edit1.Text;
content := CacheMemo.Text;
// Keine künstlichen Prompts mehr, nur die reinen Instruktionen
sysInstr := SystemInstructionMemo.Text;
if content.Trim.IsEmpty then
begin
ChatMemo.Lines.Add('Error: Cache content is empty.');
exit;
end;
// Remove Emoji suffix if present from ComboBox for API call
if ModelsComboBox.ItemIndex > -1 then
modelName := ModelsComboBox.Items[ModelsComboBox.ItemIndex].Split([' '])[0]
else
modelName := FallbackModel;
UploadCacheButton.Enabled := False;
ChatMemo.Lines.Add('Creating Cache (this may take a moment)...');
TTask.Run(
procedure
var
client: IGeminiClient;
cacheName: string;
errMsg: string;
begin
try
// 1. Client erstellen
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);
// 3. Cache aktivieren
client.ActiveCacheName := cacheName;
// 4. Client speichern
// Wenn der User später "Ask" klickt, wird dort EnableThinking gesetzt
FCachedClient := client;
TThread.Queue(
nil,
procedure
begin
ChatMemo.Lines.Add('Success: Cache created!');
ChatMemo.Lines.Add('ID: ' + cacheName);
InitChat;
UploadCacheButton.Enabled := True;
end
);
except
on E: Exception do
begin
errMsg := E.Message;
TThread.Queue(
nil,
procedure
begin
ChatMemo.Lines.Add('Cache Upload Failed:');
ChatMemo.Lines.Add(errMsg);
UploadCacheButton.Enabled := True;
end
);
end;
end;
end
);
end;
end.