unit uGeminiDlg;
{
This code is part of the TiCodEd Application, designed to develop for the
TI-99/4a Homecomputer in a modern, structured way.

BSD License
Copyright (c) 2026, Stefan Bauch, TiCodEd <at> lizardware <dot> de

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

}

{$mode ObjFPC}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Buttons,
  ExtCtrls;

type

  { TFormGemini }

  TFormGemini = class(TForm)
    bbGemini: TBitBtn;
    bbok: TBitBtn;
    bbCancel: TBitBtn;
    cbModel: TComboBox;
    cbGMode: TComboBox;
    gbGeminiOptions: TGroupBox;
    gbPrompt: TGroupBox;
    gbCode: TGroupBox;
    imProcessing: TImage;
    MemoCode: TMemo;
    MemoPrompt: TMemo;
    SpeedButton1: TSpeedButton;
    StaticText1: TStaticText;
    StaticText2: TStaticText;
    procedure bbGeminiClick(Sender: TObject);
    procedure bbokClick(Sender: TObject);
    procedure cbGModeChange(Sender: TObject);
    procedure cbModelChange(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
  private

  public

  end;

var
  FormGemini: TFormGemini;
  GeminiURL, GeminiAPIKey, GeminiModel : String;
  Snippet, History : TStringList;
  GeminiMode : Integer;

implementation

{$R *.lfm}

{ TFormGemini }

uses Clipbrd,uMain,uGemini;

procedure TFormGemini.FormShow(Sender: TObject);
begin
  // load valid models
  if FileExists('./lib/ai/models.txt') then begin
    cbModel.Items.LoadFromFile('./lib/ai/models.txt');
    WriteLogLine(3,'Read '+inttostr(cbModel.Items.Count)+' Gemini Models.');
  end else WriteLogLine(1,'Gemini Models File not found ("lib/ai/models.txt")');

  MemoCode.Text := Form1.SynEdit1.SelText;
  if MemoCode.Lines.Count=0 then bbok.Caption:='Insert'
                            else bbok.Caption:='Update';
  cbModel.Text:=GeminiModel;
  cbGMode.ItemIndex:=GeminiMode;
  MemoPrompt.SetFocus;
  Snippet := TStringList.Create;
  Snippet.AddStrings(MemoCode.Lines);
end;

procedure TFormGemini.SpeedButton1Click(Sender: TObject);
begin
  History.Clear;
  ShowMessage('Chat history cleared.');
end;

procedure TFormGemini.FormCreate(Sender: TObject);
begin
  History := TStringList.Create;
end;

procedure TFormGemini.FormDestroy(Sender: TObject);
begin
  History.free;
end;

procedure TFormGemini.cbModelChange(Sender: TObject);
begin
  GeminiModel:=cbModel.Text;
end;

procedure TFormGemini.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  Snippet.free;
end;

procedure TFormGemini.bbGeminiClick(Sender: TObject);
var context,code,thread : TStringList;
    rc,i : Integer;
    s : string;
    OldClipboard: string;
begin
  WriteLogLine(3,'Initialize Gemini.');
  SetupGemini(GeminiAPIKey, GeminiURL, GeminiModel);

  context := TStringList.Create;
  thread  := TStringList.Create;

  context.Add('intro.txt');
  context.Add('XB-11.txt');
  context.Add('sxb.txt');
  if Form1.cbStandardLib.Checked then begin
    context.Add('TiCodEd-StdLib.txt');
    WriteLogLine(5,'  Gemini context added: Standard-Library');
  end;

  if GeminiMode>0 then begin
     context.Add('XB29-Compiler.txt');
     WriteLogLine(5,'  Gemini context added: XB 2.9 Compiler');
  end;

  if GeminiMode=2 then begin
     context.Add('ASM.txt');
     WriteLogLine(5,'  Gemini context added: Embedded ASM');
  end;

  for i:=0 to form1.clbLib.Count-1 do if form1.clbLib.Checked[i]=true then begin
     s:=form1.clbLib.Items.Strings[i];
     s:=ChangeFileExt(s,'.txt');
     if fileexists('./lib/ai/'+s) then begin
        context.Add(s);
        WriteLogLine(5,'  Gemini context added: '+s);
     end else WriteLogLine(3,'  Gemini no context for: '+form1.clbLib.Items.Strings[i]);
  end;

  // whole program for context
  thread.add('<Program>');
  thread.AddStrings(form1.SynEdit1.Lines);
  thread.add('</Program>');

  // Original Snippet to be replaced
  if snippet.Count>0 then begin
    thread.add('<Snippet>');
    thread.AddStrings(Snippet);
    thread.add('</Snippet>');
  end;

  // add thread history
  if History.Count>0 then begin
    thread.add('<History>');
    thread.AddStrings(History);
    thread.add('</History>');
  end;


  code := TStringlist.Create;
  code.AddStrings(MemoCode.Lines);

  imProcessing.show;
  imProcessing.Repaint;

  try
    rc := AskGemini(MemoPrompt.Lines, context, Thread, Code);
  except
     ShowMessage(code.Text);
     rc:=-2
  end;

  imProcessing.Hide;

  if rc<>-2 then begin
     // use the clipboard to update the Code-Field to enable "undo" Ctrl-Z
     OldClipboard := Clipboard.AsText;
     MemoCode.SetFocus;
     MemoCode.SelectAll;

     Clipboard.AsText := code.Text;
     MemoCode.PasteFromClipboard;
     Clipboard.AsText := OldClipboard;
     // Add current Prompt to History
     thread.add('<User>');
     thread.AddStrings(MemoPrompt.Lines);
     thread.add('</User>');

     thread.add('<Model>');
     thread.AddStrings(Code);
     thread.add('</Model>');
  end;

  CloseGemini;
  context.Free;
  code.Free;
end;

procedure TFormGemini.bbokClick(Sender: TObject);
var s: string;
    i,s1,s2: integer;
    res : TModalResult;
begin
  s1 := 0; s2 := 0;
  for i:=0 to MemoCode.Lines.Count-1 do
    if leftstr(MemoCode.Lines.Strings[i],3)='```' then
      if s1=0 then s1:=i else s2:=i;
  s := MemoCode.Text;
  if s1>0 then begin
    res:=QuestionDlg('Copy Result','Copy only the part between <'
             +MemoCode.Lines.Strings[s1]+'> and <'
             +MemoCode.Lines.Strings[s2]+'> to your code?', mtConfirmation,
             [mrOK, '&Copy section', mrCancel, '&Everything'], 0);
    if res=mrOK then begin
      s := '';
    for i:=s1+1 to s2-1 do s:=s+MemoCode.Lines.Strings[i]+chr($0D)+chr($0A);
    end;
  end;
  Form1.SynEdit1.SelText := s;
end;

procedure TFormGemini.cbGModeChange(Sender: TObject);
begin
  GeminiMode := cbGMode.ItemIndex;
end;

end.

