aissist: add button to fpdoc editor describe a function

This commit is contained in:
mattias 2025-01-21 18:21:42 +01:00
parent 372fc3b29c
commit f4c0443b5d
12 changed files with 263 additions and 99 deletions

View File

@ -16,12 +16,21 @@ unit AIssistController;
interface
uses
Classes, SysUtils, AIClient, JanAIProtocol, SrcEditorIntf, IDEOptionsIntf, IDEOptEditorIntf, BaseIDEIntf;
Classes, SysUtils, Math, Forms, Dialogs,
// ide
SrcEditorIntf, IDEOptionsIntf, IDEOptEditorIntf, BaseIDEIntf, LazIDEintf, IDEHelpIntf, IDEDialogs,
LazConfigStorage, LazLoggerBase, CodeCache, CodeTree, CodeToolManager, PascalParserTool,
PascalReaderTool,
// aissist
AIClient, JanAIProtocol,
// laz_aissist
StrAIssist, FrmAixplain, FrmAIssistFPDocEdit;
Type
TAIState = (csDisconnected,csConnected,csWaiting,csAIThinking);
{ TAIssistController }
TAIssistController = Class(TComponent)
private
FConfigFrame: TAbstractIDEOptionsEditorClass;
@ -32,14 +41,15 @@ Type
class destructor done;
Class property Instance : TAIssistController Read _Instance;
Public
constructor create(aOwner : TComponent); override;
Destructor destroy; override;
Constructor Create(aOwner : TComponent); override;
Destructor Destroy; override;
procedure HandleShowConfig(Sender : TObject);
function ShowConfig: Boolean;
Function ShowConfig: Boolean;
Procedure LoadConfig;
Procedure SaveConfig;
Function CreateAIClient : TAIClient;
Function ExplainCurrentSelection (aEdit : TSourceEditorInterface): Boolean;
Procedure FPDocEditorInsertTextClick(var Params: TFPDocEditorTxtBtnParams);
Function Configured : Boolean;
Property Settings : TAIServerSettings Read FSettings;
Property ConfigFrame : TAbstractIDEOptionsEditorClass Read FConfigFrame Write FConfigFrame;
@ -49,8 +59,6 @@ Function AIController : TAIssistController;
implementation
uses LazIDEintf, StrAIssist, LazConfigStorage, forms, frmaixplain;
const
DefaultMaxLength = 2048;
@ -71,14 +79,14 @@ begin
FreeAndNil(_Instance);
end;
constructor TAIssistController.create(aOwner: TComponent);
constructor TAIssistController.Create(aOwner: TComponent);
begin
inherited create(aOwner);
FSettings:=TAIServerSettings.Create;
FSettings.Protocol:=TJanAIServerProtocol.protocolname;
end;
destructor TAIssistController.destroy;
destructor TAIssistController.Destroy;
begin
FreeAndNil(FSettings);
inherited destroy;
@ -146,16 +154,74 @@ begin
if Not Assigned(aEdit) then exit(False);
if not Configured then exit(False);
frm:=TAIxplainForm.Create(Application);
frm:=TAIxplainForm.Create(LazarusIDE.OwningComponent);
Caret:=aEdit.CursorScreenXY;
lPos:=aEdit.EditorControl.ClientToScreen(aEdit.ScreenToPixelPosition(Caret));
Frm.Top:=lPos.Y;
Frm.Left:=lPos.X;
Frm.SetBounds(lPos.X,lPos.Y,Frm.Width,Frm.Height);
Clnt:=CreateAIClient;
Frm.Explain(aEdit,Clnt);
frm.Show;
end;
procedure TAIssistController.FPDocEditorInsertTextClick(var Params: TFPDocEditorTxtBtnParams);
procedure ShowNotAtAProc;
begin
IDEMessageDialog('Unsupported','Source cursor is not at a function declaration.',mtInformation,[mbOk]);
end;
var
Tool: TCodeTool;
Node, ProcNode: TCodeTreeNode;
Src, NewTxt: String;
aForm: TAIssistFPDocEditDlg;
aClient: TAIClient;
begin
Params.Success:=false;
Tool:=TCodeTool(Params.CodeTool);
Node:=TCodeTreeNode(Params.CodeNode);
DebugLn(['TAIssistController.FPDocEditorInsertTextClick START ',Params.Filename,'(',Params.Line,',',Params.Col,') Part=',ord(Params.Part)]);
if Params.Selection>'' then
DebugLn([' Selection="',LeftStr(Params.Selection,100),'{...}',copy(Params.Selection,Max(101,length(Params.Selection)-99),100),'"']);
if (Tool=nil) or (Node=nil) then
begin
debugln(['Error: TAIssistController.FPDocEditorInsertTextClick Node=nil']);
ShowNotAtAProc;
exit;
end;
//debugln(['TAIssistController.FPDocEditorInsertTextClick Node: Start=',Tool.CleanPosToStr(Node.StartPos),' [',NodePathAsString(Node),'] {',LeftStr(Tool.ExtractNode(Node,[]),200),'}']);
// find proc body
ProcNode:=Node;
while (ProcNode<>nil) and (ProcNode.Desc<>ctnProcedure) do
ProcNode:=ProcNode.Parent;
if ProcNode=nil then
begin
debugln(['Error: TAIssistController.FPDocEditorInsertTextClick Node=',NodePathAsString(Node)]);
ShowNotAtAProc;
exit;
end;
Node:=Tool.FindCorrespondingProcNode(ProcNode,[phpInUpperCase],cpsDown);
if Node<>nil then
ProcNode:=Node;
Src:=Tool.ExtractNode(ProcNode,[]);
debugln(['TAIssistController.FPDocEditorInsertTextClick ProcNode: Start=',Tool.CleanPosToStr(ProcNode.StartPos),' [',NodePathAsString(ProcNode),'] Src={',Src,'}']);
aForm:=TAissistFPDocEditDlg.Create(nil);
try
aClient:=CreateAIClient;
if aForm.Describe(aClient,Src,NewTxt) then
begin
Params.Selection:=NewTxt;
Params.Success:=true;
end;
finally
aForm.Free;
end;
end;
function TAIssistController.Configured: Boolean;
begin
Result:=(Settings.BaseURL<>'');

View File

@ -17,7 +17,7 @@ interface
uses
Classes, SysUtils, Forms, Controls, StdCtrls, AIClient,
IDEOptionsIntf, IDEOptEditorIntf, IDEUtils, IDEDialogs, LazNumEdit;
IDEOptionsIntf, IDEOptEditorIntf, LazNumEdit;
type

View File

@ -9,14 +9,14 @@
Abstract: AI Assistant conversation window
}
unit frmaissistchat;
unit FrmAIssistChat;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ChatControl, TypingIndicator,
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ChatControl,
LCLType, StdCtrls, ExtCtrls, Menus, AIClient;
type

View File

@ -0,0 +1,2 @@
inherited AIssistFPDocEditDlg: TAIssistFPDocEditDlg
end

View File

@ -0,0 +1,72 @@
unit FrmAIssistFPDocEdit;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
// aissist
AIClient, StrAIssist, FrmAixplain;
const
SDescribeProcPrompt = 'Explain the following function in one sentence:';
type
{ TAIssistFPDocEditDlg }
TAIssistFPDocEditDlg = class(TAIxplainForm)
protected
FSource: string;
procedure CreatePrompt; override;
public
function Describe(aAIClient: TAIClient; const Src: string; out aDescription: string): boolean; virtual;
end;
var
AIssistFPDocEditDlg: TAIssistFPDocEditDlg;
implementation
{$R *.lfm}
{ TAIssistFPDocEditDlg }
procedure TAIssistFPDocEditDlg.CreatePrompt;
var
Src: TStringList;
begin
Src:=TStringList.Create;
try
Src.Text:=FSource;
MPrompt.Lines.Add(SDescribeProcPrompt);
MPrompt.Lines.Add('');
MPrompt.Lines.AddStrings(Src);
finally
Src.Free;
end;
end;
function TAIssistFPDocEditDlg.Describe(aAIClient: TAIClient; const Src: string; out
aDescription: string): boolean;
begin
Result:=false;
FSource:=Src;
aDescription:='';
FAIClient:=aAIClient;
FAIClient.OnError:=@HandleAIError;
FAIClient.SynchronizeCallBacks:=True;
CreatePrompt;
SendPrompt;
if ShowModal=mrOk then
begin
aDescription:='';
Result:=true;
end;
end;
end.

View File

@ -1,11 +1,12 @@
unit frmaixplain;
unit FrmAixplain;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, ButtonPanel, ComCtrls, Buttons,
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, ButtonPanel,
ComCtrls, Buttons,
typingindicator, SrcEditorIntf, AIClient;
type
@ -21,23 +22,23 @@ type
PReply: TPage;
pnlThinking: TPanel;
SBRefresh: TSpeedButton;
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure pnlThinkingClick(Sender: TObject);
procedure SBRefreshClick(Sender: TObject);
private
FTyping : TTypingIndicator;
FEditor: TSourceEditorInterface;
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction); virtual;
procedure FormCreate(Sender: TObject); virtual;
procedure pnlThinkingClick(Sender: TObject); virtual;
procedure SBRefreshClick(Sender: TObject); virtual;
protected
FAIClient: TAIClient;
FBusy : Boolean;
procedure ActivateResponse;
procedure CreatePrompt;
function GetPrompt: string;
procedure HandleAIError(Sender: TObject; aErrorData: TAIRequestErrorData);
procedure HandleAIResponse(Sender: TObject; aResponses: TPromptResponseArray);
procedure SendPrompt;
FEditor: TSourceEditorInterface;
FTyping : TTypingIndicator;
procedure ActivateResponse; virtual;
procedure CreatePrompt; virtual;
function GetPrompt: string; virtual;
procedure HandleAIError(Sender: TObject; aErrorData: TAIRequestErrorData); virtual;
procedure HandleAIResponse(Sender: TObject; aResponses: TPromptResponseArray); virtual;
procedure SendPrompt; virtual;
public
procedure Explain(aEditor: TSourceEditorInterface; aAIClient: TAIClient);
procedure Explain(aEditor: TSourceEditorInterface; aAIClient: TAIClient); virtual;
end;
var
@ -182,9 +183,9 @@ var
begin
Src:=TStringList.Create;
try
S:=Feditor.GetText(True);
S:=FEditor.GetText(True);
if S='' then
S:=Feditor.GetText(False);
S:=FEditor.GetText(False);
Src.Text:=S;
MPrompt.Lines.Add(SExplainPrompt);
MPrompt.Lines.Add('');

View File

@ -39,8 +39,15 @@
<Filename Value="frmaixplain.pas"/>
<UnitName Value="frmaixplain"/>
</Item>
<Item>
<Filename Value="frmaissistfpdocedit.pas"/>
<UnitName Value="frmaissistfpdocedit"/>
</Item>
</Files>
<RequiredPkgs>
<Item>
<PackageName Value="CodeTools"/>
</Item>
<Item>
<PackageName Value="lazchatctrl"/>
</Item>
@ -53,12 +60,6 @@
<Item>
<PackageName Value="aissist"/>
</Item>
<Item>
<PackageName Value="IDEIntf"/>
</Item>
<Item>
<PackageName Value="FCL"/>
</Item>
</RequiredPkgs>
<UsageOptions>
<UnitPath Value="$(PkgOutDir)"/>

View File

@ -8,7 +8,8 @@ unit laz_aissist;
interface
uses
frmaissistchat, RegLazAIssist, AIssistController, StrAIssist, fraAIssistConfig, frmaixplain, LazarusPackageIntf;
FrmAissistChat, RegLazAIssist, AIssistController, StrAIssist, fraAIssistConfig, FrmAixplain,
FrmAissistFPDocEdit, LazarusPackageIntf;
implementation

View File

@ -16,7 +16,10 @@ unit RegLazAIssist;
interface
uses
Classes, SysUtils, LazLoggerBase, FileUtil, IDECommands, IDEWindowIntf, LazIDEIntf, MenuIntf, frmaissistchat;
Classes, SysUtils,
LazLoggerBase, FileUtil,
IDECommands, IDEWindowIntf, LazIDEIntf, MenuIntf, IDEHelpIntf,
FrmAIssistChat;
var
AIssistChatForm: TAIssistChatForm;
@ -110,12 +113,19 @@ begin
SAIExplainSelectedCodeCaption, nil, nil, AIExplainSelectionCommand);
end;
procedure RegisterFPDocDescribe;
begin
LazarusHelp.RegisterFPDocEditorTextButton('AI','Ask aissist for a description',
@AIController.FPDocEditorInsertTextClick);
end;
procedure Register;
begin
AIController.LoadConfig;
AIController.ConfigFrame:=TAIAssistentConfigFrame;
RegisterAIChatWindow;
RegisterExplainCommand;
RegisterFPDocDescribe;
end;
end.

View File

@ -13,14 +13,14 @@
*****************************************************************************
}
unit typingindicator;
unit TypingIndicator;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, graphics, controls, extctrls;
Classes, SysUtils, Graphics, Controls, ExtCtrls;
const
DefaultTypingCycle = 2000;

View File

@ -62,7 +62,7 @@ type
Part: TFPDocEditorPart;
CodeBuf: Pointer; // TCodeBuffer
CodeTool: Pointer; // TCodeTool
CodeNode: Pointer; // TCodeNode
CodeNode: Pointer; // TCodeTreeNode
Filename: string;
Line, Col: integer;
Selection: string; // on Success is changed to new value

View File

@ -995,6 +995,7 @@ var
HasEdit: Boolean;
i: Integer;
Btn: TSpeedButton;
Cnt: SizeInt;
begin
if fUpdateLock>0 then begin
Include(FFlags,fpdefButtonsNeedUpdate);
@ -1022,22 +1023,30 @@ begin
InsertPrintShortSpeedButton.Enabled:=HasEdit;
InsertURLTagSpeedButton.Enabled:=HasEdit;
for i:=0 to length(LazarusHelp.FPDocEditorTextBtnHandlers)-1 do
Cnt:=length(LazarusHelp.FPDocEditorTextBtnHandlers);
for i:=0 to Cnt-1 do
begin
//debugln(['TFPDocEditor.UpdateButtons ',i,' ',LazarusHelp.FPDocEditorTextBtnHandlers[i].Caption]);
if i<length(FCustomSpeedButtons) then
Btn:=FCustomSpeedButtons[i]
else begin
Btn:=TSpeedButton.Create(Self);
Btn.Name:='CustomButton'+IntToStr(i);
Btn.Parent:=LeftBtnPanel;
FCustomSpeedButtons[i]:=Btn;
Btn.ShowHint:=true;
Btn.Tag:=i;
System.Insert(Btn,FCustomSpeedButtons,i);
Btn.OnClick:=@OnCustomButtonClick;
Btn.Constraints.MaxHeight:=23;
Btn.Constraints.MaxWidth:=20;
Btn.Parent:=LeftBtnPanel;
end;
Btn.Tag:=i;
Btn.Caption:=LazarusHelp.FPDocEditorTextBtnHandlers[i].Caption;
Btn.Hint:=LazarusHelp.FPDocEditorTextBtnHandlers[i].Hint;
Btn.ShowHint:=Btn.Hint>'';
end;
for i:=length(FCustomSpeedButtons)-1 downto Cnt do
FCustomSpeedButtons[i].Free;
SetLength(FCustomSpeedButtons,Cnt);
//debugln(['TFPDocEditor.UpdateButtons END']);
end;
function TFPDocEditor.GetCurrentUnitName: string;
@ -1438,7 +1447,6 @@ end;
procedure TFPDocEditor.Loaded;
begin
inherited Loaded;
DescrSynEdit.ControlStyle:=DescrSynEdit.ControlStyle+[];
UpdateButtons;
end;
@ -1593,62 +1601,65 @@ begin
Btn:=TSpeedButton(Sender);
if Btn.Tag>=length(LazarusHelp.FPDocEditorTextBtnHandlers) then exit;
OnExec:=LazarusHelp.FPDocEditorTextBtnHandlers[Btn.Tag].OnExecute;
if Assigned(OnExec) then
begin
Params:=Default(TFPDocEditorTxtBtnParams);
if (fChain=nil) or (fChain.Count=0) then exit;
Element:=fChain[0];
Tool:=Element.CodeContext.Tool;
Node:=Element.CodeContext.Node;
Params.CodeTool:=Tool;
Params.CodeNode:=Node;
if (Tool<>nil) and (Node<>nil) then
begin
Element.CodeContext.Tool.CleanPosToCaret(Node.StartPos,Caret);
Params.CodeTool:=Caret.Code;
Params.Filename:=Caret.Code.Filename;
Params.Line:=Caret.Y;
Params.Col:=Caret.X;
end;
if not Assigned(OnExec) then exit;
if PageControl.ActivePage = ShortTabSheet then begin
Params.Part:=fpdepShortDesc;
Params.Selection:=ShortEdit.SelText;
end else if PageControl.ActivePage = DescrTabSheet then begin
Params.Part:=fpdepDescription;
Params.Selection:=DescrSynEdit.SelText;
end else if PageControl.ActivePage = ErrorsTabSheet then begin
Params.Part:=fpdepErrors;
Params.Selection:=ErrorsSynEdit.SelText;
end
else if PageControl.ActivePage = TopicSheet then begin
if (FLastTopicControl = TopicShort) then begin
Params.Part:=fpdepTopicShort;
Params.Selection:=TopicShort.SelText;
end else if (FLastTopicControl = TopicDescrSynEdit) then begin
Params.Part:=fpdepTopicDesc;
Params.Selection:=TopicDescrSynEdit.SelText;
end else
exit;
UpdateCodeCache;
UpdateChain;
if (fChain=nil) or (fChain.Count=0) then exit;
Params:=Default(TFPDocEditorTxtBtnParams);
Element:=fChain[0];
Tool:=Element.CodeContext.Tool;
Node:=Element.CodeContext.Node;
Params.CodeTool:=Tool;
Params.CodeNode:=Node;
if (Tool<>nil) and (Node<>nil) then
begin
Element.CodeContext.Tool.CleanPosToCaret(Node.StartPos,Caret);
Params.CodeBuf:=Caret.Code;
Params.Filename:=Caret.Code.Filename;
Params.Line:=Caret.Y;
Params.Col:=Caret.X;
end;
if PageControl.ActivePage = ShortTabSheet then begin
Params.Part:=fpdepShortDesc;
Params.Selection:=ShortEdit.SelText;
end else if PageControl.ActivePage = DescrTabSheet then begin
Params.Part:=fpdepDescription;
Params.Selection:=DescrSynEdit.SelText;
end else if PageControl.ActivePage = ErrorsTabSheet then begin
Params.Part:=fpdepErrors;
Params.Selection:=ErrorsSynEdit.SelText;
end
else if PageControl.ActivePage = TopicSheet then begin
if (FLastTopicControl = TopicShort) then begin
Params.Part:=fpdepTopicShort;
Params.Selection:=TopicShort.SelText;
end else if (FLastTopicControl = TopicDescrSynEdit) then begin
Params.Part:=fpdepTopicDesc;
Params.Selection:=TopicDescrSynEdit.SelText;
end else
exit;
end else
exit;
OnExec(Params);
if not Params.Success then exit;
OnExec(Params);
if not Params.Success then exit;
case Params.Part of
fpdepShortDesc:
begin
ShortEdit.SelText := Params.Selection;
DescrShortEdit.Text:=ShortEdit.Text;
end;
fpdepDescription: DescrSynEdit.SelText := Params.Selection;
fpdepErrors: ErrorsSynEdit.SelText := Params.Selection;
fpdepTopicShort: TopicShort.SelText := Params.Selection;
fpdepTopicDesc: TopicDescrSynEdit.SelText := Params.Selection;
end;
Modified:=true;
case Params.Part of
fpdepShortDesc:
begin
ShortEdit.SelText := Params.Selection;
DescrShortEdit.Text:=ShortEdit.Text;
end;
fpdepDescription: DescrSynEdit.SelText := Params.Selection;
fpdepErrors: ErrorsSynEdit.SelText := Params.Selection;
fpdepTopicShort: TopicShort.SelText := Params.Selection;
fpdepTopicDesc: TopicDescrSynEdit.SelText := Params.Selection;
end;
Modified:=true;
end;
procedure TFPDocEditor.AddLinkToInheritedButtonClick(Sender: TObject);