348 lines
10 KiB
ObjectPascal
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.
|