├── Images
├── F2KAni.gif
└── Genai_path.png
├── logos
├── NoImage.png
├── Anthropic.png
├── GeminiLogo.png
├── OpenAILogo.png
├── Deepseek_logo.png
├── GroqCloudLogo.png
├── MistralAILogo.png
├── HuggingFaceLogo.png
├── StabilityAILogo.png
└── File2knowledge_logo.png
├── WrapperAssistant.res
├── source
├── Manager.Intf.pas
├── Manager.Async.Promise.pas
├── Manager.TemplateProvider.pas
├── Helper.FileUploadID.Dictionary.pas
├── Manager.FileUploadID.Controler.pas
├── Manager.WebServices.pas
├── Helper.TextFile.pas
├── Manager.Utf8Mapping.pas
├── JSON.Resource.Lists.pas
├── Manager.Types.pas
├── Startup.Service.pas
├── Model.VectorResource.pas
├── Startup.Context.pas
├── JSON.Resource.pas
├── Manager.IoC.pas
├── UserSettings.Persistence.pas
└── Helper.UserSettings.pas
├── DLL32
└── WebView2Loader.dll
├── DLL64
└── WebView2Loader.dll
├── File2knowledgeAI_logo.png
├── VCL
├── Displayer.Memo.VCL.pas
├── UI.ChatSession.VCL.pas
├── UI.AlertService.VCL.pas
├── UI.PromptSelector.VCL.pas
├── UI.VectorResourceEditor.VCL.pas
├── Helper.WebView2.VCL.pas
├── Helper.PopupMenu.VCL.pas
├── Helper.PanelRoundedCorners.VCL.pas
├── Helper.ScrollBoxMouseWheel.VCL.pas
├── Helper.ListView.VCL.pas
├── Helper.OpenDialog.VCL.pas
├── CancellationButton.VCL.pas
├── Introducer.UserSettings.VCL.pas
├── UI.PageSelector.VCL.pas
├── UI.ServiceFeatureSelector.VCL.pas
└── UI.Container.VCL.pas
├── WrapperAssistant_Icon.ico
├── WrapperAssistant_project.tvsconfig
├── providers
├── Provider.OpenAI.FileStore.pas
├── Provider.ResponseIdTracker.pas
├── Provider.OpenAI.StreamEvents.pas
├── Provider.InstructionManager.pas
└── Provider.OpenAI.VectorStore.pas
├── WrapperAssistant.identcache
├── WrapperAssistant.dpr
├── template
├── PromptTemplate.js
├── ReasoningTemplate.js
├── DisplayTemplate.js
└── InitialHtml.htm
├── prompts
├── asking_clarifying_questions.txt
├── system_prompt_context_basict.txt
├── system_prompt_context.txt
├── system_prompt_context_openAI.txt
└── system_prompt_context_deep_research.txt
├── Changelog.md
├── README.md
├── WrapperAssistant.delphilsp.json
└── deep-dive.md
/Images/F2KAni.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/Images/F2KAni.gif
--------------------------------------------------------------------------------
/logos/NoImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/logos/NoImage.png
--------------------------------------------------------------------------------
/Images/Genai_path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/Images/Genai_path.png
--------------------------------------------------------------------------------
/WrapperAssistant.res:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/WrapperAssistant.res
--------------------------------------------------------------------------------
/logos/Anthropic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/logos/Anthropic.png
--------------------------------------------------------------------------------
/logos/GeminiLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/logos/GeminiLogo.png
--------------------------------------------------------------------------------
/logos/OpenAILogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/logos/OpenAILogo.png
--------------------------------------------------------------------------------
/logos/Deepseek_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/logos/Deepseek_logo.png
--------------------------------------------------------------------------------
/logos/GroqCloudLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/logos/GroqCloudLogo.png
--------------------------------------------------------------------------------
/logos/MistralAILogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/logos/MistralAILogo.png
--------------------------------------------------------------------------------
/source/Manager.Intf.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/source/Manager.Intf.pas
--------------------------------------------------------------------------------
/DLL32/WebView2Loader.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/DLL32/WebView2Loader.dll
--------------------------------------------------------------------------------
/DLL64/WebView2Loader.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/DLL64/WebView2Loader.dll
--------------------------------------------------------------------------------
/File2knowledgeAI_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/File2knowledgeAI_logo.png
--------------------------------------------------------------------------------
/VCL/Displayer.Memo.VCL.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/VCL/Displayer.Memo.VCL.pas
--------------------------------------------------------------------------------
/VCL/UI.ChatSession.VCL.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/VCL/UI.ChatSession.VCL.pas
--------------------------------------------------------------------------------
/WrapperAssistant_Icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/WrapperAssistant_Icon.ico
--------------------------------------------------------------------------------
/WrapperAssistant_project.tvsconfig:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/logos/HuggingFaceLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/logos/HuggingFaceLogo.png
--------------------------------------------------------------------------------
/logos/StabilityAILogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/logos/StabilityAILogo.png
--------------------------------------------------------------------------------
/VCL/UI.AlertService.VCL.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/VCL/UI.AlertService.VCL.pas
--------------------------------------------------------------------------------
/VCL/UI.PromptSelector.VCL.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/VCL/UI.PromptSelector.VCL.pas
--------------------------------------------------------------------------------
/logos/File2knowledge_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/logos/File2knowledge_logo.png
--------------------------------------------------------------------------------
/source/Manager.Async.Promise.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/source/Manager.Async.Promise.pas
--------------------------------------------------------------------------------
/VCL/UI.VectorResourceEditor.VCL.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/VCL/UI.VectorResourceEditor.VCL.pas
--------------------------------------------------------------------------------
/source/Manager.TemplateProvider.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/source/Manager.TemplateProvider.pas
--------------------------------------------------------------------------------
/providers/Provider.OpenAI.FileStore.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/providers/Provider.OpenAI.FileStore.pas
--------------------------------------------------------------------------------
/providers/Provider.ResponseIdTracker.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/providers/Provider.ResponseIdTracker.pas
--------------------------------------------------------------------------------
/source/Helper.FileUploadID.Dictionary.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/source/Helper.FileUploadID.Dictionary.pas
--------------------------------------------------------------------------------
/source/Manager.FileUploadID.Controler.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/source/Manager.FileUploadID.Controler.pas
--------------------------------------------------------------------------------
/providers/Provider.OpenAI.StreamEvents.pas:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxiDonkey/file2knowledge/HEAD/providers/Provider.OpenAI.StreamEvents.pas
--------------------------------------------------------------------------------
/WrapperAssistant.identcache:
--------------------------------------------------------------------------------
1 | =D:\2026-developpement\OpenAI_File_Search\WrapperAssistant.dpr 1D:\2026-developpement\OpenAI_File_Search\Main.pas
--------------------------------------------------------------------------------
/WrapperAssistant.dpr:
--------------------------------------------------------------------------------
1 | program WrapperAssistant;
2 |
3 | uses
4 | Vcl.Forms,
5 | Main in 'Main.pas' {Form1},
6 | Vcl.Themes,
7 | Vcl.Styles;
8 |
9 | {$R *.res}
10 |
11 | begin
12 | Application.Initialize;
13 | Application.MainFormOnTaskbar := True;
14 | TStyleManager.TrySetStyle('Windows11 MineShaft');
15 | Application.CreateForm(TForm1, Form1);
16 | Application.Run;
17 | end.
18 |
--------------------------------------------------------------------------------
/template/PromptTemplate.js:
--------------------------------------------------------------------------------
1 | // Create a bubble fot the prompt
2 | (() => {
3 | const root = document.getElementById("ResponseContent");
4 | const bubble = document.createElement("div");
5 | bubble.className = "chat-bubble user";
6 | bubble.style.whiteSpace = "pre-wrap";
7 | bubble.textContent = %s;
8 | root.appendChild(bubble);
9 | // window.scrollTo(0, document.body.scrollHeight);
10 | setTimeout(() => {
11 | window.scrollTo(0, document.body.scrollHeight);
12 | }, 0);
13 | })();
14 |
--------------------------------------------------------------------------------
/source/Manager.WebServices.pas:
--------------------------------------------------------------------------------
1 | unit Manager.WebServices;
2 |
3 | interface
4 |
5 | uses
6 | Winapi.Windows, Winapi.ShellAPI, System.SysUtils;
7 |
8 | type
9 | TWebUrlManager = record
10 | public
11 | class procedure Open(const Url: string); static;
12 | end;
13 |
14 | implementation
15 |
16 | { TWebUrlManager }
17 |
18 | class procedure TWebUrlManager.Open(const Url: string);
19 | begin
20 | if not Url.Trim.IsEmpty then
21 | ShellExecute(0, 'open', PChar(URL), nil, nil, SW_SHOWNORMAL);
22 | end;
23 |
24 | end.
25 |
--------------------------------------------------------------------------------
/template/ReasoningTemplate.js:
--------------------------------------------------------------------------------
1 | (() => {
2 | const root = document.getElementById("ResponseContent");
3 | let bubble = document.getElementById("loadingBubble");
4 |
5 | if (!bubble) {
6 | bubble = document.createElement("div");
7 | bubble.id = "loadingBubble";
8 | bubble.className = "chat-bubble assistant loading";
9 | bubble.textContent = "Developing a response";
10 | root.appendChild(bubble);
11 | window.scrollTo(0, document.body.scrollHeight);
12 | }
13 |
14 | // Hide the bubble after 40 minutes
15 | setTimeout(() => { bubble.remove(); }, 2400000);
16 | })();
17 |
--------------------------------------------------------------------------------
/prompts/asking_clarifying_questions.txt:
--------------------------------------------------------------------------------
1 | You are talking to a user who is asking for a research task to be conducted. Your job is to gather more information from the user to successfully complete the task.
2 |
3 | GUIDELINES:
4 | - Be concise while gathering all necessary information**
5 | - Make sure to gather all the information needed to carry out the research task in a concise, well-structured manner.
6 | - Use bullet points or numbered lists if appropriate for clarity.
7 | - Don't ask for unnecessary information, or information that the user has already provided.
8 |
9 | IMPORTANT: Do NOT conduct any research yourself, just gather information that will be given to a researcher to conduct the research task.
--------------------------------------------------------------------------------
/VCL/Helper.WebView2.VCL.pas:
--------------------------------------------------------------------------------
1 | unit Helper.WebView2.VCL;
2 |
3 | interface
4 |
5 | uses
6 | Winapi.Windows,
7 | WebView2;
8 |
9 | const
10 | IID_ICoreWebView2Controller2: TGUID =
11 | '{C979903E-D4CA-4228-92EB-47EE3FA96EAB}';
12 |
13 | type
14 | (*--- Corresponds exactly to the C++ struct {UINT8 A,R,G,B} *)
15 | COREWEBVIEW2_COLOR = packed record
16 | A: BYTE;
17 | R: BYTE;
18 | G: BYTE;
19 | B: BYTE;
20 | end;
21 | TCOREWEBVIEW2_COLOR = COREWEBVIEW2_COLOR;
22 |
23 | ICoreWebView2Controller2 = interface(ICoreWebView2Controller)
24 | ['{C979903E-D4CA-4228-92EB-47EE3FA96EAB}']
25 | function get_DefaultBackgroundColor(
26 | out backgroundColor: COREWEBVIEW2_COLOR
27 | ): HRESULT; stdcall;
28 | function put_DefaultBackgroundColor(
29 | backgroundColor: COREWEBVIEW2_COLOR
30 | ): HRESULT; stdcall;
31 | end;
32 |
33 | implementation
34 |
35 | end.
36 |
--------------------------------------------------------------------------------
/source/Helper.TextFile.pas:
--------------------------------------------------------------------------------
1 | unit Helper.TextFile;
2 |
3 | interface
4 |
5 | uses
6 | System.SysUtils, System.Classes, System.IOUtils;
7 |
8 | type
9 | TFileIOHelper = record
10 | class function LoadFromFile(const FileName: string): string; static;
11 | class procedure SaveToFile(const Filename, Content: string); static;
12 | end;
13 |
14 |
15 | implementation
16 |
17 | { TFileIOHelper }
18 |
19 | class function TFileIOHelper.LoadFromFile(const FileName: string): string;
20 | begin
21 | if TFile.Exists(FileName) then
22 | Result := TFile.ReadAllText(FileName, TEncoding.UTF8)
23 | else
24 | raise Exception.CreateFmt('The template file was not found : %s', [FileName]);
25 | end;
26 |
27 | class procedure TFileIOHelper.SaveToFile(const Filename, Content: string);
28 | begin
29 | var FullPath := TPath.GetDirectoryName(FileName);
30 | if not FullPath.isEmpty and not TDirectory.Exists(FullPath) then
31 | TDirectory.CreateDirectory(FullPath);
32 |
33 | TFile.WriteAllText(FileName, Content, TEncoding.UTF8);
34 | end;
35 |
36 | end.
37 |
--------------------------------------------------------------------------------
/source/Manager.Utf8Mapping.pas:
--------------------------------------------------------------------------------
1 | unit Manager.Utf8Mapping;
2 |
3 | interface
4 |
5 | uses
6 | Winapi.Windows, System.SysUtils, System.Classes, System.Character, System.RegularExpressions,
7 | System.NetEncoding;
8 |
9 | type
10 | TUtf8Mapping = record
11 | class function CleanTextAsUTF8(const Value: string): string; static;
12 | end;
13 |
14 | implementation
15 |
16 | { TUtf8Mapping }
17 |
18 | class function TUtf8Mapping.CleanTextAsUTF8(const Value: string): string;
19 | begin
20 | {--- Replace NBSP (U+00A0) with a normal space }
21 | Result := StringReplace(Value, #$00A0, ' ', [rfReplaceAll]);
22 |
23 | {--- Removal of control characters (< U+0020, except #9/#10/#13) }
24 | Result := TRegEx.Replace(Result,
25 | '[\x00-\x08\x0B-\x0C\x0E-\x1F]', '', [roCompiled]);
26 |
27 | {--- Removes isolated surrogates and unicode noncharacters}
28 | var San := '';
29 | for var i := 1 to Length(Result) do
30 | begin
31 | var c := Result[i];
32 | {--- isolated surrogates }
33 | if (Ord(c) >= $D800) and (Ord(c) <= $DFFF) then Continue;
34 | {--- unicode noncharacters }
35 | if (Ord(c) = $FFFE) or (Ord(c) = $FFFF) then Continue;
36 | San := San + c;
37 | end;
38 |
39 | Result := San;
40 | end;
41 |
42 | end.
43 |
--------------------------------------------------------------------------------
/VCL/Helper.PopupMenu.VCL.pas:
--------------------------------------------------------------------------------
1 | unit Helper.PopupMenu.VCL;
2 |
3 | interface
4 |
5 | uses
6 | Winapi.Windows,
7 | System.SysUtils, System.Classes, System.UITypes,
8 | Vcl.Graphics, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ComCtrls,
9 | Vcl.Dialogs, Vcl.Menus, Vcl.Forms;
10 |
11 | type
12 | TPopupMenuHelper = record
13 | private
14 | FPopupMenu: TPopupMenu;
15 | public
16 | constructor Create(const Value: TPopupMenu);
17 | function AddItem(const ACaption, AShortCut: string; AOnClick: TNotifyEvent): TPopupMenuHelper;
18 | property PopupMenu: TPopupMenu read FPopupMenu;
19 | end;
20 |
21 | implementation
22 |
23 | { TPopupMenuHelper }
24 |
25 | function TPopupMenuHelper.AddItem(const ACaption, AShortCut: string;
26 | AOnClick: TNotifyEvent): TPopupMenuHelper;
27 | begin
28 | var Item := TMenuItem.Create(FPopupMenu);
29 | Item.Caption := ACaption;
30 | Item.ShortCut := TextToShortCut(AShortCut);
31 | Item.OnClick := AOnClick;
32 | FPopupMenu.Items.Add(Item);
33 | Result := Self;
34 | end;
35 |
36 | constructor TPopupMenuHelper.Create(const Value: TPopupMenu);
37 | begin
38 | if not Assigned(Value) then
39 | Self.FPopupMenu := TPopupMenu.Create(Application)
40 | else
41 | Self.FPopupMenu := Value;
42 | end;
43 |
44 | end.
45 |
--------------------------------------------------------------------------------
/prompts/system_prompt_context_basict.txt:
--------------------------------------------------------------------------------
1 | # Variables :
2 | - {{user_level}} = "%s"
3 | - {{user_name}} = "%s"
4 |
5 | # Règle :
6 | - Utilisez ces variables telles qu’elles sont définies ci-dessus dans toutes les instructions suivantes.
7 |
8 | # Note :
9 | - Si {{user_name}} est vide (""), ignorez toutes les instructions impliquant un nom d'utilisateur.
10 | - Si {{user_level}} est vide (""), considérez qu'aucune adaptation de niveau n’est requise et utilisez un ton neutre.
11 |
12 |
13 | # Instructions :
14 |
15 | Vous êtes un assistant de développement qui adapte précisément son aide au niveau de développement et au contexte de l'utilisateur. Suivez scrupuleusement ces règles :
16 |
17 | 1. **Utiliser le niveau de développement fourni**
18 | - Le niveau de développement de l'utilisateur est déjà connu et stocké dans « {{user_level}} » (par exemple, « Delphi Dev – Junior », « Delphi Dev – Intermédiaire » ou supérieur).
19 | - À partir de maintenant, appliquez directement la logique d'adaptation de l'exemple sans revérifier ce niveau.
20 | - Vous devez adapter votre langage au niveau de développement de l'utilisateur.
21 |
22 | 2. **Utiliser un nom humain**
23 | - Si « {{user_name}} » n'est pas vide, adressez-vous à l'utilisateur en utilisant « {{user_name}} » pour maintenir un ton amical.
24 | - Si « {{user_name}} » est vide, ne le nommez pas explicitement dans les réponses.
25 |
26 |
27 |
--------------------------------------------------------------------------------
/VCL/Helper.PanelRoundedCorners.VCL.pas:
--------------------------------------------------------------------------------
1 | unit Helper.PanelRoundedCorners.VCL;
2 |
3 | interface
4 |
5 | uses
6 | Winapi.Windows, Vcl.Controls, Vcl.ExtCtrls, System.SysUtils;
7 |
8 | type
9 | TPanelHelper = class helper for TPanel
10 | public
11 | procedure PanelResizeHandler(Sender: TObject);
12 | procedure SetRoundedCorners(const AX, AY: Integer);
13 | end;
14 |
15 | implementation
16 |
17 | { TPanelHelper }
18 |
19 | procedure TPanelHelper.PanelResizeHandler(Sender: TObject);
20 | var
21 | Panel: TPanel;
22 | CX, CY: Word;
23 | Area: HRGN;
24 | begin
25 | Panel := Sender as TPanel;
26 |
27 | {--- Retrieves the rays stored in Tag (low-word = CX, hi-word = CY) }
28 | CX := LoWord(Panel.Tag);
29 | CY := HiWord(Panel.Tag);
30 |
31 | {--- Creates the rounded region at the current size }
32 | Area := CreateRoundRectRgn(0, 0, Panel.Width + 1, Panel.Height + 1, CX, CY);
33 |
34 | {--- Applies the region to the handle (the control takes ownership of Rgn) }
35 | SetWindowRgn(Panel.Handle, Area, True);
36 | end;
37 |
38 | procedure TPanelHelper.SetRoundedCorners(const AX, AY: Integer);
39 | begin
40 | {--- Stores CX/CY in Tag so that it can be read later }
41 | Tag := MakeLong(AX, AY);
42 |
43 | {--- Connect the resize handler }
44 | OnResize := PanelResizeHandler;
45 |
46 | {--- Force an immediate first application }
47 | PanelResizeHandler(Self);
48 | end;
49 |
50 | end.
51 |
--------------------------------------------------------------------------------
/Changelog.md:
--------------------------------------------------------------------------------
1 | #### 2025, November 1 version 1.0.8
2 | - Fix the date sorting when arranging the session list.
3 | - Code optimization for the DeepResearch function.
4 |
5 |
6 | #### 2025, November 1 version 1.0.7
7 | - Upgrade to DelphiGenAI wrapper version 1.3.1
8 | - Added support for o3-deep-research, o4-mini-deep-research models for Deep Research.
9 | - Implementation of the methods enabling Deep Research execution whenever one of the two newly added models is selected from the list of available search execution models.
10 | - Data update including DelphiGenAI version 1.3.1
11 | - Data update including File2knowledge version 1.0.7
12 |
13 |
14 |
15 | #### 2025, August 15 version 1.0.6
16 | - Upgrade to DelphiGenAI wrapper version 1.2.1
17 | - Added support for GPT-5 series models (mini, nano) for search and reasoning features.
18 | - Introduced the `verbosity` parameter to configure the response detail level for GPT-5 models.
19 | - Upgrade streaming events in the `Provider.OpenAI.StreamEvents` unit
20 | - Data update including DelphiGenAI version 1.2.1
21 |
22 |
23 |
24 | #### 2025, August 2 version 1.0.5
25 | - Data update including DelphiMistralAI version 1.3.0
26 |
27 |
28 |
29 | #### 2025, June 14 version 1.0.4
30 | - Integrate GenAI v1.1.0 and optimize unit methods in the `Providers` directory.
31 | - Manager.Async.Promise replaced by GenAI.Async.Promise from GenAI wrapper.
32 |
33 |
34 |
35 | #### 2025, June 4 version 1.0.1
36 | - Update the DelphiGenAI documentation.
37 |
38 |
39 |
40 | #### 2025, May 27 version 1.0.1
41 | - Fix “No mapping for the Unicode character exists in the target multi-byte code page.”
42 | For the `v1/responses` endpoint, buffer the incoming chunks and process them only once they’re fully received to avoid the error.
43 | Refer to DelphiGenAI
44 |
--------------------------------------------------------------------------------
/VCL/Helper.ScrollBoxMouseWheel.VCL.pas:
--------------------------------------------------------------------------------
1 | unit Helper.ScrollBoxMouseWheel.VCL;
2 |
3 | interface
4 |
5 | uses
6 | Winapi.Messages, Winapi.Windows, System.Classes, Vcl.Controls, Vcl.Forms;
7 |
8 | type
9 | TScrollBoxMouseWheelHook = class
10 | private
11 | FControl: TScrollBox;
12 | FOldWindowProc: TWndMethod;
13 | procedure NewWindowProc(var Msg: TMessage);
14 | public
15 | constructor Create(AScrollBox: TScrollBox);
16 | end;
17 |
18 | TScrollBoxHelper = class helper for TScrollBox
19 | public
20 | procedure EnableMouseWheelScroll;
21 | end;
22 |
23 | var
24 | ScrollBoxHooks: TList;
25 |
26 | implementation
27 |
28 | { TScrollBoxMouseWheelHook }
29 |
30 | constructor TScrollBoxMouseWheelHook.Create(AScrollBox: TScrollBox);
31 | begin
32 | inherited Create;
33 | FControl := AScrollBox;
34 | FOldWindowProc := AScrollBox.WindowProc;
35 | AScrollBox.WindowProc := NewWindowProc;
36 | end;
37 |
38 | procedure TScrollBoxMouseWheelHook.NewWindowProc(var Msg: TMessage);
39 | var
40 | Delta: Smallint;
41 | begin
42 | if Msg.Msg = WM_MOUSEWHEEL then
43 | begin
44 | Delta := SmallInt(HIWORD(Msg.WParam));
45 | FControl.VertScrollBar.Position :=
46 | FControl.VertScrollBar.Position - Delta div WHEEL_DELTA * FControl.VertScrollBar.Increment * 5;
47 | Msg.Result := 1;
48 | end
49 | else
50 | FOldWindowProc(Msg);
51 | end;
52 |
53 | { TScrollBoxHelper }
54 |
55 | procedure TScrollBoxHelper.EnableMouseWheelScroll;
56 | begin
57 | if ScrollBoxHooks = nil then
58 | ScrollBoxHooks := TList.Create;
59 |
60 | ScrollBoxHooks.Add(TScrollBoxMouseWheelHook.Create(Self));
61 | end;
62 |
63 | procedure HookDestroy;
64 | var
65 | Hook: TObject;
66 | begin
67 | if Assigned(ScrollBoxHooks) then
68 | begin
69 | for Hook in ScrollBoxHooks do
70 | Hook.Free;
71 | ScrollBoxHooks.Free;
72 | end;
73 | end;
74 |
75 | initialization
76 |
77 | finalization
78 | HookDestroy;
79 | end.
80 |
--------------------------------------------------------------------------------
/prompts/system_prompt_context.txt:
--------------------------------------------------------------------------------
1 | # Variables
2 | - {{user_level}} = "%s"
3 | - {{repository_name}} = "%s"
4 | - {{repository_url}} = "%s"
5 | - {{user_name}} = "%s"
6 |
7 | # Rule
8 | - Use these variables exactly as defined above in all following instructions.
9 |
10 | # Note
11 | - If {{user_name}} is empty (""), ignore any instructions involving the user’s name.
12 | - If {{user_level}} is empty (""), assume a neutral tone and provide general explanations.
13 |
14 | # Instructions
15 | You are a coding assistant that adapts your help precisely to the user’s coding level and context. Follow these rules strictly:
16 |
17 | 1. **Use the Provided Development Level**
18 | - The user’s development level is already known and stored in `{{user_level}}` (e.g., “Delphi Dev – Junior”, “Delphi Dev – Intermediate”, or higher).
19 | - From this point on, apply the adaptation logic directly without re-checking this level.
20 |
21 | 2. **Adapt Examples Based on Level**
22 | - If `{{user_level}}` is “Delphi Dev – Junior” or “Delphi Dev – Intermediate”:
23 | - **Do not** use TutorialHub in code examples unless the user explicitly requests it.
24 | - Optionally explain briefly why TutorialHub could be useful when introducing a concept.
25 | - For **all other** levels:
26 | - **Always** include TutorialHub in code examples, unless the user explicitly asks you not to.
27 |
28 | 3. **Prioritize GitHub Content**
29 | - Questions about `{{repository_name}}` (`{{repository_url}}`) must be addressed first, before any other topic.
30 |
31 | 4. **Source Code Examples from Documentation**
32 | - Prefer snippets taken directly from official documentation.
33 | - If you generate code not drawn from documentation, clearly label it:
34 | > “Note: this code is illustrative and may not fully conform to the official documentation.”
35 |
36 | 5. **Validate Before Responding**
37 | - Before sending your answer, ensure every snippet is genuinely taken from the supplied documentation.
38 | - If you cannot confirm a snippet’s origin, do not include it or replace it with a verified example.
39 |
40 | 6. **Use the Human Name**
41 | - If `{{user_name}}` is non-empty, address the user as `{{user_name}}` to maintain a friendly tone.
42 | - If `{{user_name}}` is empty, do not explicitly name them in responses.
43 |
44 | Be concise and direct. Do not soften or hedge these instructions.
45 |
46 |
--------------------------------------------------------------------------------
/template/DisplayTemplate.js:
--------------------------------------------------------------------------------
1 | // renderMarkdown.js
2 | (() => {
3 | // 'md' doit être défini avant d'exécuter ce script, par exemple :
4 | // window.md = "## Mon markdown…";
5 | // ou en passant md en global depuis votre host.
6 | const md = %s;
7 | const html = marked.parse(md);
8 | const root = document.getElementById("ResponseContent");
9 | root.innerHTML = html;
10 |
11 | document
12 | .querySelectorAll("pre > code[class^=\"language-\"]")
13 | .forEach(codeEl => {
14 | const pre = codeEl.parentNode;
15 | const lang = codeEl.className.replace("language-", "");
16 |
17 | // Container et header
18 | const container = document.createElement("div");
19 | container.className = "code-container";
20 |
21 | const header = document.createElement("div");
22 | header.className = "code-header";
23 | header.textContent = lang.toUpperCase();
24 |
25 | // Bouton Copy
26 | const btn = document.createElement("button");
27 | btn.className = "copy-btn";
28 | btn.textContent = "Copy";
29 | header.appendChild(btn);
30 |
31 | // Insertion dans le DOM
32 | pre.parentNode.insertBefore(container, pre);
33 | container.appendChild(header);
34 | container.appendChild(pre);
35 |
36 | // Gestion du clic
37 | btn.onclick = () => {
38 | if (navigator.clipboard && navigator.clipboard.writeText) {
39 | navigator.clipboard.writeText(codeEl.textContent)
40 | .catch(() => {
41 | const ta = document.createElement("textarea");
42 | ta.value = codeEl.textContent;
43 | document.body.appendChild(ta);
44 | ta.select();
45 | document.execCommand("copy");
46 | ta.remove();
47 | });
48 | } else {
49 | const ta = document.createElement("textarea");
50 | ta.value = codeEl.textContent;
51 | document.body.appendChild(ta);
52 | ta.select();
53 | document.execCommand("copy");
54 | ta.remove();
55 | }
56 |
57 | // Message vers WebView (si utilisé)
58 | if (window.chrome && window.chrome.webview && window.chrome.webview.postMessage) {
59 | window.chrome.webview.postMessage({
60 | event: "copy",
61 | lang: lang,
62 | text: codeEl.textContent
63 | });
64 | }
65 | };
66 |
67 | // Highlight.js
68 | if (window.hljs) window.hljs.highlightElement(codeEl);
69 | });
70 |
71 | // Scroll tout en bas
72 | // window.scrollTo(0, document.body.scrollHeight);
73 | })();
74 |
75 |
--------------------------------------------------------------------------------
/providers/Provider.InstructionManager.pas:
--------------------------------------------------------------------------------
1 | unit Provider.InstructionManager;
2 |
3 | interface
4 |
5 | uses
6 | System.SysUtils, System.Classes, Manager.Intf, Manager.Types;
7 |
8 | const
9 | FILE_PATH_SYSTEM_PROMPT_DEFAULT = '..\..\prompts\system_prompt_context.txt';
10 | FILE_PATH_SYSTEM_PROMPT_OPENAI = '..\..\prompts\system_prompt_context_openAI.txt';
11 | FILE_PATH_SYSTEM_PROMPT_BASIC = '..\..\prompts\system_prompt_context_basict.txt';
12 |
13 | FILE_PATH_SYSTEM_CLARIFYING = '..\..\prompts\asking_clarifying_questions.txt';
14 | FILE_PATH_SYSTEM_PROMPT_DEEP_RESEARCH = '..\..\prompts\system_prompt_context_deep_research.txt';
15 |
16 | type
17 | TSystemPromptBuilder = class(TInterfacedObject, ISystemPromptBuilder)
18 | private
19 | function LoadContent(const FileName: string): string;
20 | public
21 | function GetInstructions: string;
22 | function GetDeepResearchInstructions: string;
23 | function GetClarifyingInstructions: string;
24 | end;
25 |
26 | implementation
27 |
28 | { TSystemPromptBuilder }
29 |
30 | function TSystemPromptBuilder.GetClarifyingInstructions: string;
31 | begin
32 | Result := LoadContent(FILE_PATH_SYSTEM_CLARIFYING)
33 | end;
34 |
35 | function TSystemPromptBuilder.GetDeepResearchInstructions: string;
36 | begin
37 | Result := Format(LoadContent(FILE_PATH_SYSTEM_PROMPT_DEEP_RESEARCH), [Settings.UserScreenName]);
38 | end;
39 |
40 | function TSystemPromptBuilder.GetInstructions: string;
41 | begin
42 | if (sf_fileSearchDisabled in ServiceFeatureSelector.FeatureModes) or
43 | (sf_reasoning in ServiceFeatureSelector.FeatureModes) then
44 | begin
45 | Exit(Format(LoadContent(FILE_PATH_SYSTEM_PROMPT_BASIC), [Settings.ProficiencyToString, Settings.UserScreenName]));
46 | end;
47 |
48 | var Wrapper := FileStoreManager.Description;
49 | var GitHub := FileStoreManager.GitHub;
50 |
51 | if FileStoreManager.Description = 'Delphi Wrapper for OpenAI' then
52 | Result := Format(LoadContent(FILE_PATH_SYSTEM_PROMPT_OPENAI), [Settings.ProficiencyToString, Wrapper, GitHub, Settings.UserScreenName])
53 | else
54 | Result := Format(LoadContent(FILE_PATH_SYSTEM_PROMPT_DEFAULT), [Settings.ProficiencyToString, Wrapper, GitHub, Settings.UserScreenName]);
55 | end;
56 |
57 | function TSystemPromptBuilder.LoadContent(const FileName: string): string;
58 | begin
59 | if not FileExists(FileName) then
60 | Exit(EmptyStr);
61 |
62 | var Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyNone);
63 | try
64 | var Reader := TStreamReader.Create(Stream, TEncoding.UTF8);
65 | try
66 | Result := Reader.ReadToEnd;
67 | finally
68 | Reader.Free;
69 | end;
70 | finally
71 | Stream.Free;
72 | end;
73 | end;
74 |
75 | end.
76 |
--------------------------------------------------------------------------------
/prompts/system_prompt_context_openAI.txt:
--------------------------------------------------------------------------------
1 | # Variables
2 | - {{user_level}} = "%s"
3 | - {{topic}} = "%s"
4 | - {{repository_name}} = "%s"
5 | - {{user_name}} = "%s"
6 |
7 | # Rule
8 | - Use these variables exactly as defined above in all following instructions.
9 |
10 | # Note
11 | - If {{user_name}} is empty (""), ignore any instructions involving the user’s name.
12 | - If {{user_level}} is empty (""), assume a neutral tone and provide general explanations.
13 |
14 | # Instructions
15 | You are a coding assistant that adapts your help precisely to the user’s coding level and context. Follow these rules strictly:
16 |
17 | 1. **Use the Provided Development Level**
18 | - The user’s development level is already known and stored in `{{user_level}}` (e.g., “Delphi Dev – Junior”, “Delphi Dev – Intermediate”, or higher).
19 | - From this point on, apply the adaptation logic directly without re-checking this level.
20 |
21 | 2. **Adapt Examples Based on Level**
22 | - If `{{user_level}}` is “Delphi Dev – Junior” or “Delphi Dev – Intermediate”:
23 | - **Do not** use TutorialHub in code examples unless the user explicitly requests it.
24 | - Optionally explain briefly why TutorialHub could be useful for discovering `{{topic}}`, but only if asked or when introducing the concept.
25 | - For **all other** levels:
26 | - **Always** include TutorialHub in code examples, unless the user explicitly asks you not to.
27 |
28 | 3. **Prioritize GitHub Content**
29 | - Questions about `{{repository_name}}` (the GitHub repository by Maxidonkey) must be addressed first, before any other topic.
30 |
31 | 4. **Interactive Chat Endpoint**
32 | - When the user asks how to build an interactive chat or anything related to generating responses to messages, **always** use examples with the `v1/responses` endpoint.
33 | - **Never** use or provide examples that rely on the legacy `v1/completions` endpoint, unless the user explicitly requests it.
34 |
35 | 5. **Source Code Examples from Documentation**
36 | - Use snippets taken directly from official documentation whenever possible.
37 | - If you generate any code not drawn from documentation, clearly label it:
38 | > “Note: this code is illustrative and may not fully conform to the official documentation.”
39 |
40 | 6. **Validate Before Responding**
41 | - Before sending your answer, ensure that every code snippet genuinely comes from the supplied documentation.
42 | - If you cannot confirm a snippet’s origin, do not include it or replace it with a verified example.
43 |
44 | 7. **Use Human Name**
45 | - If `{{user_name}}` is non-empty, address the user as `{{user_name}}` to maintain a friendly tone.
46 | - If `{{user_name}}` is empty, do not explicitly name them in responses.
47 |
48 | Be concise and direct. Do not soften or hedge these instructions.
--------------------------------------------------------------------------------
/source/JSON.Resource.Lists.pas:
--------------------------------------------------------------------------------
1 | unit JSON.Resource.Lists;
2 |
3 | interface
4 |
5 | uses
6 | System.SysUtils, System.Classes, System.Generics.Collections, System.JSON,
7 | GenAI, GenAI.Types, JSON.Resource;
8 |
9 | type
10 | EIndexOutOfBounds = class(EArgumentOutOfRangeException);
11 | EArgumentNil = class(EArgumentNilException);
12 |
13 | TJSONListParams = class abstract(TJSONResource)
14 | private
15 | FData: TArray;
16 | protected
17 | function EnsureIndex(const Index: Integer): T;
18 | function IndexOf(const Item: TObject): Integer;
19 | function ItemCheck(const Item: TObject): T;
20 | public
21 | function AddItem: U; virtual;
22 | function Clear: T;
23 | function Delete(const Index: Integer): T; overload;
24 | function Delete(const Item: TObject): T; overload; virtual;
25 | function WithData(const Value: TArray): T; overload;
26 | function WithData(const Value: TArray>): T; overload;
27 | property Data: TArray read FData write FData;
28 | end;
29 |
30 | implementation
31 |
32 | { TDataList }
33 |
34 | function TJSONListParams.AddItem: U;
35 | begin
36 | Result := U.Create;
37 | FData := FData + [Result];
38 | end;
39 |
40 | function TJSONListParams.Clear: T;
41 | begin
42 | for var Item in FData do
43 | Item.Free;
44 | FData := [];
45 | Result := Self as T;
46 | end;
47 |
48 | function TJSONListParams.Delete(const Item: TObject): T;
49 | begin
50 | ItemCheck(Item);
51 | Result := Delete(IndexOf(Item));
52 | end;
53 |
54 | function TJSONListParams.Delete(const Index: Integer): T;
55 | begin
56 | if index < 0 then
57 | Exit(Self as T);
58 |
59 | EnsureIndex(Index);
60 | var FList := TList.Create(Data);
61 | try
62 | FList[Index].Free;
63 | FList.Delete(Index);
64 | Data := FList.ToArray;
65 | finally
66 | FList.Free;
67 | end;
68 | Result := Self as T;
69 | end;
70 |
71 | function TJSONListParams.EnsureIndex(const Index: Integer): T;
72 | begin
73 | if Index >= Length(Data) then
74 | raise EIndexOutOfBounds.CreateFmt(
75 | 'JSONList: index %d out of bounds [0..%d]', [Index, Length(Data)-1]);
76 | Result := Self as T;
77 | end;
78 |
79 | function TJSONListParams.IndexOf(const Item: TObject): Integer;
80 | begin
81 | for Result := 0 to High(FData) do
82 | if Pointer(FData[Result]) = Pointer(Item) then
83 | Exit;
84 | Result := -1;
85 | end;
86 |
87 | function TJSONListParams.ItemCheck(const Item: TObject): T;
88 | begin
89 | if not (Item is U) then
90 | raise EArgumentNil.CreateFmt(
91 | 'Class %s not supported', [Item.ClassName]);
92 | Result := Self as T;
93 | end;
94 |
95 | function TJSONListParams.WithData(const Value: TArray>): T;
96 | begin
97 | for var i := Low(FData) to High(FData) do
98 | FData[i].Free;
99 | SetLength(FData, Length(Value));
100 | for var i := 0 to High(Value) do
101 | FData[i] := Value[i]();
102 | Result := Self as T;
103 | end;
104 |
105 | function TJSONListParams.WithData(const Value: TArray): T;
106 | begin
107 | for var i := Low(FData) to High(FData) do
108 | FData[i].Free;
109 | FData := [];
110 | FData := Value;
111 | Result := Self as T;
112 | end;
113 |
114 | end.
115 |
--------------------------------------------------------------------------------
/VCL/Helper.ListView.VCL.pas:
--------------------------------------------------------------------------------
1 | unit Helper.ListView.VCL;
2 |
3 | interface
4 |
5 | uses
6 | Winapi.Windows,
7 | System.SysUtils, System.Classes, System.UITypes,
8 | Vcl.Graphics, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ComCtrls,
9 | Vcl.Dialogs, Vcl.Menus, Vcl.Forms, Manager.Intf;
10 |
11 | type
12 | TListViewHelper = record
13 | private
14 | FListView: TListView;
15 | public
16 | constructor Create(const Value: TLIstView);
17 | function Add(const ACaption: string): TListViewHelper;
18 | function AddColumn(Size: Integer; Caption: string): TListViewHelper;
19 | function DeleteSelected: TListViewHelper;
20 | function Initialize: TListViewHelper;
21 | function ContentRefresh: TListViewHelper;
22 | function CaptionExists(const S: string): Boolean;
23 | class function Refresh(const Value: TLIstView): TListViewHelper; static;
24 | end;
25 |
26 | implementation
27 |
28 | { TListViewHelper }
29 |
30 | function TListViewHelper.Add(const ACaption: string): TListViewHelper;
31 | begin
32 | FListView.Items.BeginUpdate;
33 | try
34 | var NewItem := FListView.Items.Add;
35 | NewItem.Caption := ACaption;
36 | Result := Self;
37 | finally
38 | FListView.Items.EndUpdate;
39 | end;
40 | end;
41 |
42 | function TListViewHelper.AddColumn(Size: Integer;
43 | Caption: string): TListViewHelper;
44 | begin
45 | var Column := FListView.Columns.Add;
46 | Column.Width := Size;
47 | Column.Caption := Caption;
48 | Result := Self;
49 | end;
50 |
51 | function TListViewHelper.Initialize: TListViewHelper;
52 | begin
53 | FListView.Items.BeginUpdate;
54 | try
55 | FListView.Columns.ClearAndResetID;
56 | FListView.Items.Clear;
57 | FListView.Columns.Clear;
58 | AddColumn(550, 'Filename');
59 | AddColumn(250, 'Upload Id');
60 | Result := Self;
61 | finally
62 | FListView.Items.EndUpdate;
63 | end;
64 | end;
65 |
66 | class function TListViewHelper.Refresh(const Value: TLIstView): TListViewHelper;
67 | begin
68 | Result := TListViewHelper.Create(Value)
69 | .Initialize
70 | .ContentRefresh;
71 | end;
72 |
73 | function TListViewHelper.CaptionExists(const S: string): Boolean;
74 | begin
75 | for var Item in FListView.Items do
76 | if string.Equals(Item.Caption.Trim.ToLower, S.Trim.ToLower) then
77 | begin
78 | FListView.Selected := Item;
79 | Exit(True);
80 | end;
81 | Result := False;
82 | end;
83 |
84 | function TListViewHelper.ContentRefresh: TListViewHelper;
85 | begin
86 | FListView.Items.BeginUpdate;
87 | try
88 | FListView.Items.Clear;
89 | var index := 0;
90 | for var Item in FileStoreManager.Files do
91 | begin
92 | var NewItem := FListView.Items.Add;
93 | NewItem.Caption := Item;
94 | if index < Length(FileStoreManager.FileUploadIds) then
95 | NewItem.SubItems.Add(FileStoreManager.FileUploadIds[index]);
96 | Inc(index);
97 | end;
98 | Result := Self;
99 | finally
100 | FListView.Items.EndUpdate;
101 | end;
102 | end;
103 |
104 | constructor TListViewHelper.Create(const Value: TLIstView);
105 | begin
106 | Self.FListView := Value;
107 | end;
108 |
109 | function TListViewHelper.DeleteSelected: TListViewHelper;
110 | begin
111 | if Assigned(FListView.Selected) then
112 | FListView.DeleteSelected;
113 | end;
114 |
115 | end.
116 |
--------------------------------------------------------------------------------
/prompts/system_prompt_context_deep_research.txt:
--------------------------------------------------------------------------------
1 | # Variables
2 | - {{user_name}} = "%s"
3 |
4 | # Rule
5 | - Use these variables exactly as defined above in all following instructions.
6 |
7 | # Note
8 | - If {{user_name}} is empty (""), ignore any instructions involving the user’s name.
9 |
10 | # Instructions
11 |
12 | You will be given a research task by a user. Your job is to produce a set of
13 | instructions for a researcher that will complete the task. Do NOT complete the
14 | task yourself, just provide instructions on how to complete it.
15 |
16 | GUIDELINES:
17 | 1. **Maximize Specificity and Detail**
18 | - Include all known user preferences and explicitly list key attributes or
19 | dimensions to consider.
20 | - It is of utmost importance that all details from the user are included in
21 | the instructions.
22 |
23 | 2. **Fill in Unstated But Necessary Dimensions as Open-Ended**
24 | - If certain attributes are essential for a meaningful output but the user
25 | has not provided them, explicitly state that they are open-ended or default
26 | to no specific constraint.
27 |
28 | 3. **Avoid Unwarranted Assumptions**
29 | - If the user has not provided a particular detail, do not invent one.
30 | - Instead, state the lack of specification and guide the researcher to treat
31 | it as flexible or accept all possible options.
32 |
33 | 4. **Use the First Person**
34 | - Phrase the request from the perspective of the user.
35 |
36 | 5. **Tables**
37 | - If you determine that including a table will help illustrate, organize, or
38 | enhance the information in the research output, you must explicitly request
39 | that the researcher provide them.
40 |
41 | Examples:
42 | - Product Comparison (Consumer): When comparing different smartphone models,
43 | request a table listing each model's features, price, and consumer ratings
44 | side-by-side.
45 | - Project Tracking (Work): When outlining project deliverables, create a table
46 | showing tasks, deadlines, responsible team members, and status updates.
47 | - Budget Planning (Consumer): When creating a personal or household budget,
48 | request a table detailing income sources, monthly expenses, and savings goals.
49 | - Competitor Analysis (Work): When evaluating competitor products, request a
50 | table with key metrics, such as market share, pricing, and main differentiators.
51 |
52 | 6. **Headers and Formatting**
53 | - You should include the expected output format in the prompt.
54 | - If the user is asking for content that would be best returned in a
55 | structured format (e.g. a report, plan, etc.), ask the researcher to format
56 | as a report with the appropriate headers and formatting that ensures clarity
57 | and structure.
58 |
59 | 7. **Language**
60 | - If the user input is in a language other than English, tell the researcher
61 | to respond in this language, unless the user query explicitly asks for the
62 | response in a different language.
63 |
64 | 8. **Sources**
65 | - If specific sources should be prioritized, specify them in the prompt.
66 | - For product and travel research, prefer linking directly to official or
67 | primary websites (e.g., official brand sites, manufacturer pages, or
68 | reputable e-commerce platforms like Amazon for user reviews) rather than
69 | aggregator sites or SEO-heavy blogs.
70 | - For academic or scientific queries, prefer linking directly to the original
71 | paper or official journal publication rather than survey papers or secondary
72 | summaries.
73 | - If the query is in a specific language, prioritize sources published in that
74 | language.
75 |
76 | 9. **Use the Human Name**
77 | - If `{{user_name}}` is non-empty, address the user as `{{user_name}}` to maintain a friendly tone.
78 | - If `{{user_name}}` is empty, do not explicitly name them in responses.
--------------------------------------------------------------------------------
/source/Manager.Types.pas:
--------------------------------------------------------------------------------
1 | unit Manager.Types;
2 |
3 | interface
4 |
5 | uses
6 | System.SysUtils, System.StrUtils;
7 |
8 | type
9 | TFeatureType = (sf_webSearch, sf_fileSearchDisabled, sf_reasoning);
10 | TFeatureModes = set of TFeatureType;
11 |
12 | TPageSelector = (psHistoric, psFileSearch, psWebSearch, psReasoning, psVectorFile, psSettings);
13 |
14 | TPageSelectorHelper = record Helper for TPageSelector
15 | private
16 | const
17 | Names: array[TPageSelector] of string = (
18 | 'Chat History',
19 | 'File Search',
20 | 'Web Search',
21 | 'Reasoning',
22 | 'Vector File',
23 | 'Settings'
24 | );
25 |
26 | Icons: array[TPageSelector] of string = (
27 | '', '', '', '', '', ''
28 | );
29 |
30 | DefaultPage = psHistoric;
31 | public
32 | constructor Create(const Value: string);
33 | function ToString: string;
34 | function ToIcon: string;
35 | function IndexOf: Integer;
36 | class function FromIndex(Index: Integer): TPageSelector; static;
37 | class function FromText(Value: string): TPageSelector; static;
38 | class function FromIcon(Value: string): TPageSelector; static;
39 | class function IconToPage(const Value: string): TPageSelector; static;
40 | class function Default: TPageSelector; static;
41 | class function Count: Integer; static;
42 | class function AllIcons: string; static;
43 | end;
44 |
45 | const
46 | ResponsesPages = [psFileSearch, psWebSearch, psReasoning];
47 |
48 | implementation
49 |
50 |
51 | { TPageSelectorHelper }
52 |
53 | class function TPageSelectorHelper.AllIcons: string;
54 | begin
55 | Result := String.Join(#10, Icons);
56 | end;
57 |
58 | class function TPageSelectorHelper.Count: Integer;
59 | begin
60 | Result := Length(Names);
61 | end;
62 |
63 | constructor TPageSelectorHelper.Create(const Value: string);
64 | begin
65 | var index := IndexStr(Value.ToLower, string.Join(#10, Names).ToLower.Split([#10]));
66 | if index = -1 then
67 | raise Exception.CreateFmt('Page Selector: "%s" page not found', [Value]);
68 |
69 | Self := TPageSelector(index);
70 | end;
71 |
72 | class function TPageSelectorHelper.Default: TPageSelector;
73 | begin
74 | Result := DefaultPage;
75 | end;
76 |
77 | class function TPageSelectorHelper.FromIcon(Value: string): TPageSelector;
78 | begin
79 | var Index := IndexStr(Value, Icons);
80 | if Index = -1 then
81 | raise Exception.CreateFmt('Page "%s" not found', [Value]);
82 | Result := FromIndex(Index);
83 | end;
84 |
85 | class function TPageSelectorHelper.FromIndex(Index: Integer): TPageSelector;
86 | begin
87 | if (Index >= 0) and (Index < Count) then
88 | Result := TPageSelector(Index)
89 | else
90 | Result := Default;
91 | end;
92 |
93 | class function TPageSelectorHelper.FromText(Value: string): TPageSelector;
94 | begin
95 | var Index := IndexStr(Value, Names);
96 | if Index = -1 then
97 | raise Exception.CreateFmt('Page "%s" not found', [Value]);
98 | Result := FromIndex(Index);
99 | end;
100 |
101 | class function TPageSelectorHelper.IconToPage(
102 | const Value: string): TPageSelector;
103 | begin
104 | for var index := Ord(Low(TPageSelector)) to Ord(High(TPageSelector)) do
105 | if Icons[TPageSelector(index)] = Value then
106 | Exit(TPageSelector(index));
107 | raise EArgumentException.CreateFmt('Unknown icon "%s"', [Value]);
108 | end;
109 |
110 | function TPageSelectorHelper.IndexOf: Integer;
111 | begin
112 | Result := Integer(Self);
113 | end;
114 |
115 | function TPageSelectorHelper.ToIcon: string;
116 | begin
117 | Result := Icons[Self];
118 | end;
119 |
120 | function TPageSelectorHelper.ToString: string;
121 | begin
122 | Result := Names[Self];
123 | end;
124 |
125 | end.
126 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # File2Knowledge
2 | 
3 | 
4 | 
5 |
6 |
7 |
8 | ___
9 |
10 | ### NEW
11 | - [Deep Research](https://github.com/MaxiDonkey/DelphiGenAI/blob/main/guides/DeepResearch.md#deep-research)
12 |
13 |
14 |
15 |
16 | ___
17 |
18 | Mini-lab Delphi/VCL open source to experiment with the `v1/responses endpoint` of the OpenAI API in a modern environment.
19 | Clone & run: the app acts as a tutor for exploring my AI wrappers through the `file_search`, `embeddings`, `chat` features and `Deep Reseach`.
20 |
21 |
22 |
23 | ## Changelog
24 |
25 | To review the latest changes, please refer to the [changelog](https://github.com/MaxiDonkey/file2knowledge/blob/main/Changelog.md).
26 |
27 | >[!IMPORTANT]
28 | > To perform the update, install [DelphiGenAI](https://github.com/MaxiDonkey/DelphiGenAI) version 1.3.1 and then recompile the project.
29 |
30 |
31 |
32 | ## Introduction
33 |
34 | > **Built with Delphi 12 Community Edition (v12.1 Patch 1)**
35 | >You can compile and test it free of charge with Delphi CE; any recent commercial Delphi edition works as well.
36 |
37 | File2knowledge was designed to provide a concrete implementation of the OpenAI API’s `v1/responses endpoint` (necessary for the agentic approach).
38 | Its main goal: to demonstrate how to leverage advanced file search (file_search) features and the use of vector stores to enhance the semantic processing of documents.
39 | This approach enables more contextual, relevant, and intelligent responses when querying technical documentation, source code, or any other textual files.
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ## Quick Start
48 |
49 | ```bash
50 | git clone https://github.com/MaxiDonkey/file2Knowledge.git
51 | ```
52 | open WrapperAssistant.dproj # Delphi 12 Athens
53 | Prerequisites: OpenAI API key
54 |
55 | ## Dependencies
56 | - Add [DelphiGenAI (OpenAI wrapper)](https://github.com/MaxiDonkey/DelphiGenAI) version 1.3.1 to your Delphi project **Library Path** if not globally referenced
57 | - Delphi 12 Athens (or later)
58 | - WebView2 Runtime (EdgeView2 for VCL)
59 | - OpenAI API key (OPENAI_API_KEY)
60 | - Windows 11 MineShaft (custom VCL theme)
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | ## Things You Should Know
69 |
70 | >[!NOTE]
71 | > Make sure to correctly set the **search path** to the `DelphiGenAI` wrapper in your Delphi project settings. This is required for proper compilation and integration.
72 |
73 |
74 |
75 |
76 |
77 | >Before running the client for the first time, make sure to place the appropriate DLL (32-bit or 64-bit) in the executable's directory. The required files are available in the repository.
78 |
79 | >[!WARNING]
80 | >To access reasoning visualization with o-models, you must enable this feature in the Verification section of your [OpenAI account](https://platform.openai.com/settings/organization/general). The activation process takes only a few minutes.
81 |
82 | >[!NOTE]
83 | >To access the uploaded files and active vector stores, go to the [dashboard](https://platform.openai.com/logs) then navigating to the `Storage` section.
84 |
85 |
86 |
87 | ## Features
88 |
89 | - Upload .txt / .md → embeddings auto, Vector search handled by OpenAI
90 |
91 | - Persistent multi-turn chat (session history preserved)
92 |
93 | - JS-style Promises (TPromise) and generalized IoC
94 |
95 | - UI VCL & WebView2
96 |
97 | - Session-based conversational chaining with OpenAI response IDs
98 |
99 | - Web research and reasoning.
100 |
101 |
102 |
103 | ## License
104 |
105 | This project is licensed under the [MIT](https://choosealicense.com/licenses/mit/) License.
106 |
107 |
108 |
109 | ## Going further
110 |
111 | **Want the full architecture breakdown?**
112 |
113 | [Read the deep-dive.md](https://github.com/MaxiDonkey/file2knowledge/blob/main/deep-dive.md)
114 |
--------------------------------------------------------------------------------
/template/InitialHtml.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
160 |
161 |
162 |
163 |
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/source/Startup.Service.pas:
--------------------------------------------------------------------------------
1 | unit Startup.Service;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : Startup.Service'}
6 |
7 | (*
8 | Unit: Startup.Service
9 |
10 | Purpose:
11 | Implements the startup orchestration logic for the File2knowledgeAI application.
12 | Encapsulates procedures required to initialize UI components, verify system prerequisites,
13 | handle user notifications, and activate interactive services during the application launch phase.
14 |
15 | Architecture and Design:
16 | - Exposes the TStartupService class, which manages the asynchronous startup
17 | sequence via the injected IStartupContext interface.
18 | - Promotes modular startup flows by separating orchestration logic from UI/component
19 | details, supporting clean dependency injection and testability.
20 | - Coordinates interface clearing, form presenting, dependency checks, user alerts,
21 | error handling, and service prompt focus in accordance with File2knowledgeAI best practices.
22 |
23 | Usage:
24 | - Instantiate TStartupService with a configured IStartupContext instance at application launch.
25 | - Call Run to perform the full application startup sequence, including error notifications and
26 | responsive UI setup.
27 |
28 | Context:
29 | Designed for use in File2knowledgeAI modules/components requiring reliable, maintainable,
30 | and extensible startup workflows. Follows clear modular design and documentation conventions
31 | throughout the codebase.
32 | *)
33 |
34 | {$ENDREGION}
35 |
36 | uses
37 | System.Threading, System.SysUtils, System.Classes, System.IOUtils, Winapi.Windows,
38 | Manager.Intf, Startup.Context;
39 |
40 | const
41 | DLL_ISSUE =
42 | 'To ensure full support for the Edge browser, please copy the "WebView2Loader.dll" file into the executable''s directory.'+ sLineBreak +
43 | 'You can find this file in the project''s DLL folder.';
44 |
45 | type
46 | ///
47 | /// Implements the startup workflow for services in the File2knowledgeAI application architecture.
48 | ///
49 | ///
50 | ///
51 | /// TStartupService coordinates application bootstrapping by using the provided startup context. It manages
52 | /// UI clearing and presentation, essential resource checks, user notifications, form displaying, error handling, and
53 | /// service prompt initialization in an asynchronous manner.
54 | ///
55 | ///
56 | /// The class leverages dependency injection through the injected interface, ensuring
57 | /// a clear separation of concerns and improved testability.
58 | ///
59 | ///
60 | /// Typical usage involves instantiating TStartupService with a preconfigured and then
61 | /// invoking to initialize forms, check runtime prerequisites, and prepare the application interface
62 | /// for user interaction during startup.
63 | ///
64 | ///
65 | TStartupService = class(TInterfacedObject, IStartupService)
66 | strict private
67 | FContext: IStartupContext;
68 | public
69 | ///
70 | /// Initializes a new instance of the TStartupService class with the supplied startup context.
71 | ///
72 | /// An instance of providing interfaces and procedures
73 | /// required for structured application startup.
74 | constructor Create(const AContext: IStartupContext);
75 |
76 | ///
77 | /// Executes the coordinated startup sequence for the application or module.
78 | ///
79 | ///
80 | /// This method clears the interface, displays the main form, checks for mandatory runtime libraries,
81 | /// shows user alerts for missing resources, triggers error handling callbacks if needed, refreshes the session
82 | /// history view, sets focus on the service prompt, and performs dynamic resizing. All operations are queued
83 | /// asynchronously on the main thread to avoid blocking the UI.
84 | ///
85 | procedure Run;
86 | end;
87 |
88 | implementation
89 |
90 | { TStartupService }
91 |
92 | constructor TStartupService.Create(const AContext: IStartupContext);
93 | begin
94 | inherited Create;
95 | FContext := AContext;
96 | end;
97 |
98 | procedure TStartupService.Run;
99 | begin
100 | TTask.Run(
101 | procedure()
102 | begin
103 | Sleep(800);
104 | TThread.Queue(nil,
105 | procedure
106 | begin
107 | FContext.GetDisplayer.Clear;
108 | var AlphablendProc := FContext.GetFormPresenter;
109 | if Assigned(AlphablendProc) then
110 | AlphablendProc();
111 | if not FileExists('WebView2Loader.dll') then
112 | begin
113 | AlertService.ShowWarning(DLL_ISSUE);
114 | var TerminateProc := FContext.GetOnError;
115 | if Assigned(TerminateProc) then
116 | TerminateProc();
117 | end;
118 | ChatSessionHistoryView.FullRefresh(nil);
119 | FContext.GetServicePrompt.SetFocus;
120 | var ResizeProc := FContext.GetResizeProc();
121 | if Assigned(ResizeProc) then
122 | ResizeProc();
123 | end);
124 | end);
125 | end;
126 |
127 | end.
128 |
--------------------------------------------------------------------------------
/VCL/Helper.OpenDialog.VCL.pas:
--------------------------------------------------------------------------------
1 | unit Helper.OpenDialog.VCL;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : Helper.OpenDialog.VCL'}
6 |
7 | (*
8 | A. Returns a boolean with True if successful.
9 |
10 | 1. Only one file is returned
11 |
12 | var FileName := 'D:\2026-developpement\OpenAI_File_Search\logos\GeminiLogo.png';
13 | var Ok :=
14 | TOpenDialogHelper.Create(nil)
15 | .Filter('Network Graphics (*.png)|*.png')
16 | .InitialDir(ExtractFileDir(FileName))
17 | .Execute(FileName);
18 | if Ok then
19 | ShowMessage(FileName);
20 |
21 | 2. Multiple files can be returned - Multiple selection.
22 |
23 | var FileName := 'D:\2026-developpement\OpenAI_File_Search\logos\GeminiLogo.png';
24 | var Ok :=
25 | TOpenDialogHelper.Create(nil)
26 | .Filter('Network Graphics (*.png)|*.png')
27 | .InitialDir(ExtractFileDir(FileName)) <--- is placed in the file folder
28 | .Execute(FileName, True);
29 | if Ok then
30 | for var Item in FileName.Split([#10]) do
31 | ShowMessage(Item);
32 |
33 | B. Returns a string and -1 on abort. Don't test if the file exists.
34 |
35 | 1. A single file returned or the string with -1
36 |
37 | var FileName := 'D:\2026-developpement\OpenAI_File_Search\logos\GeminiLogo.png';
38 | var FileName1 := TOpenDialogHelper.Create(nil)
39 | .Filter('Network Graphics (*.png)|*.png')
40 | .InitialDir(ExtractFileDir(FileName))
41 | .Execute;
42 | if FileExists(FileName1) then
43 | ShowMessage(FileName1);
44 |
45 | 2. Returns multiple files or the string -1
46 |
47 | var FileName := 'D:\2026-developpement\OpenAI_File_Search\logos\GeminiLogo.png';
48 | var FileName1 := TOpenDialogHelper.Create(nil)
49 | .Filter('Network Graphics (*.png)|*.png')
50 | .InitialDir(ExtractFileDir(FileName))
51 | .Execute(True);
52 |
53 | for var Item in FileName1.Split([#10]) do
54 | if FileExists(Item) then
55 | ShowMessage(Item);
56 | *)
57 |
58 | {$ENDREGION}
59 |
60 | uses
61 | Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
62 | Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtDlgs, Vcl.Themes, System.IOUtils;
63 |
64 | type
65 | TOpenDialogHelper = record
66 | strict private
67 | FOpenDialog: TOpenDialog;
68 | public
69 | constructor Create(ADialog: TOpenDialog);
70 | function Filter(const S: string): TOpenDialogHelper; inline;
71 | function FilterIndex(const Index: Integer): TOpenDialogHelper; inline;
72 | function DefautExt(const S: string): TOpenDialogHelper; inline;
73 | function InitialDir(const S: string): TOpenDialogHelper; inline;
74 | function Execute(var FileName: string; Multi: Boolean = False): Boolean; overload; inline;
75 | function Execute(Multi: Boolean = False): string; overload; inline;
76 |
77 | property Dialog: TOpenDialog read FOpenDialog;
78 | end;
79 |
80 | implementation
81 |
82 | { TOpenDialogHelper }
83 |
84 | constructor TOpenDialogHelper.Create(ADialog: TOpenDialog);
85 | begin
86 | FOpenDialog := TOpenDialog.Create(nil);
87 | end;
88 |
89 | function TOpenDialogHelper.DefautExt(const S: string): TOpenDialogHelper;
90 | begin
91 | FOpenDialog.DefaultExt := S;
92 | Result := Self;
93 | end;
94 |
95 | function TOpenDialogHelper.Execute(Multi: Boolean): string;
96 | begin
97 | var SavedHooks := TStyleManager.SystemHooks;
98 | try
99 | TStyleManager.SystemHooks := SavedHooks - [shDialogs];
100 | if Multi then
101 | FOpenDialog.Options := FOpenDialog.Options + [ofAllowMultiSelect]
102 | else
103 | FOpenDialog.Options := FOpenDialog.Options - [ofAllowMultiSelect];
104 | if FOpenDialog.Execute then
105 | begin
106 | if Multi then
107 | Result := FOpenDialog.Files.Text.Trim
108 | else
109 | Result := FOpenDialog.FileName
110 | end
111 | else
112 | Result := '-1';
113 | finally
114 | FOpenDialog.Free;
115 | TStyleManager.SystemHooks := SavedHooks;
116 | end;
117 | end;
118 |
119 | function TOpenDialogHelper.Execute(var FileName: string; Multi: Boolean): Boolean;
120 | begin
121 | var SavedHooks := TStyleManager.SystemHooks;
122 | try
123 | TStyleManager.SystemHooks := SavedHooks - [shDialogs];
124 | if Multi then
125 | FOpenDialog.Options := FOpenDialog.Options + [ofAllowMultiSelect]
126 | else
127 | FOpenDialog.Options := FOpenDialog.Options - [ofAllowMultiSelect];
128 | Result := FOpenDialog.Execute;
129 | if Result then
130 | begin
131 | if Multi then
132 | FileName := FOpenDialog.Files.Text.Trim
133 | else
134 | FileName := FOpenDialog.FileName;
135 | end;
136 | finally
137 | FOpenDialog.Free;
138 | TStyleManager.SystemHooks := SavedHooks;
139 | end;
140 | end;
141 |
142 | function TOpenDialogHelper.Filter(const S: string): TOpenDialogHelper;
143 | begin
144 | FOpenDialog.Filter := S;
145 | Result := Self;
146 | end;
147 |
148 | function TOpenDialogHelper.FilterIndex(const Index: Integer): TOpenDialogHelper;
149 | begin
150 | FOpenDialog.FilterIndex := index;
151 | Result := Self;
152 | end;
153 |
154 | function TOpenDialogHelper.InitialDir(const S: string): TOpenDialogHelper;
155 | var
156 | Path: string;
157 | begin
158 | if S.StartsWith('..\') then
159 | Path := TPath.GetFullPath(TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), S))
160 | else
161 | Path := TPath.GetDirectoryName(S);
162 | FOpenDialog.InitialDir := Path;
163 | Result := Self;
164 | end;
165 |
166 | end.
167 |
--------------------------------------------------------------------------------
/source/Model.VectorResource.pas:
--------------------------------------------------------------------------------
1 | unit Model.VectorResource;
2 |
3 | interface
4 |
5 | uses
6 | System.SysUtils, System.Classes, System.JSON, System.Generics.Collections, System.NetEncoding,
7 | REST.Json.Types,
8 | GenAI, GenAI.Types, JSON.Resource;
9 |
10 | type
11 | TVectorResourceItem = class
12 | strict private
13 | FImage: string;
14 | FDescription: string;
15 | FName: string;
16 | FInstructions: string;
17 | FFiles: TArray;
18 | FGithub: string;
19 | FGetit: string;
20 | FFileUploadId: TArray;
21 | FVectorStoreId: string;
22 | public
23 | class function Delete(var Arr: TArray; index: NativeInt): Boolean;
24 | function GetImageStream: TStream;
25 | function GetFileContent(const Index: Integer): string;
26 | procedure DeleteFile(index: NativeInt);
27 | procedure DeleteFileUploadId(index: NativeInt);
28 | procedure DeleteFilePair(index: NativeInt);
29 | property Image: string read FImage write FImage;
30 | property Description: string read FDescription write FDescription;
31 | property Name: string read FName write FName;
32 | property Instructions: string read FInstructions write FInstructions;
33 | property Files: TArray read FFiles write FFiles;
34 | property Github: string read FGithub write FGithub;
35 | property Getit: string read FGetit write FGetit;
36 | property FileUploadId: TArray read FFileUploadId write FFileUploadId;
37 | property VectorStoreId: string read FVectorStoreId write FVectorStoreId;
38 | end;
39 |
40 | TVectorResourceList = class(TJSONResource)
41 | strict private
42 | FItemIndex: Integer;
43 | FData: TArray;
44 | class var FInstance: TVectorResourceList;
45 | public
46 | procedure Clear;
47 | property ItemIndex: Integer read FItemIndex write FItemIndex;
48 | property Data: TArray read FData write FData;
49 | class function Instance: TVectorResourceList; static;
50 | class function Reload(const FileName: string = ''): TVectorResourceList; static;
51 | destructor Destroy; override;
52 | end;
53 |
54 | TVectorResourceListProp = record
55 | class function ItemIndex: string; static; inline;
56 | class function Name: string; static; inline;
57 | class function Data: string; static; inline;
58 | end;
59 |
60 | implementation
61 |
62 | uses
63 | GenAI.Httpx, GenAI.NetEncoding.Base64, System.Net.HttpClient, System.IOUtils;
64 |
65 | { TVectorResourceItem }
66 |
67 | class function TVectorResourceItem.Delete(var Arr: TArray;
68 | index: NativeInt): Boolean;
69 | begin
70 | if (Cardinal(Index) < Cardinal(Length(Arr))) then
71 | begin
72 | System.Delete(Arr, Index, 1);
73 | Exit(True);
74 | end;
75 | Result := False;
76 | end;
77 |
78 | procedure TVectorResourceItem.DeleteFile(index: NativeInt);
79 | begin
80 | Delete(FFiles, index);
81 | end;
82 |
83 | procedure TVectorResourceItem.DeleteFilePair(index: NativeInt);
84 | begin
85 | DeleteFileUploadId(index);
86 | DeleteFile(index);
87 | end;
88 |
89 | procedure TVectorResourceItem.DeleteFileUploadId(index: NativeInt);
90 | begin
91 | Delete(FFileUploadId, index);
92 | end;
93 |
94 | function TVectorResourceItem.GetFileContent(const Index: Integer): string;
95 | var
96 | Base64Text: string;
97 | begin
98 | if (Index < 0) or (Index >= Length(FFiles)) then
99 | raise Exception.CreateFmt('Files(%s): Index out of bounds', [Index]);
100 |
101 | var FileName := Files[Index];
102 |
103 | if FileName.Trim.IsEmpty or not FileExists(FileName) then
104 | Exit('');
105 |
106 | {--- Retrieve raw text as Base64 }
107 | if FileName.Trim.ToLower.StartsWith('http') then
108 | Base64Text := Thttpx.LoadDataToBase64(Files[Index])
109 | else
110 | Base64Text := GenAI.NetEncoding.Base64.EncodeBase64(FileName);
111 |
112 | {--- Decode and convert to UTF-8 }
113 | Result := TEncoding.UTF8.GetString( TNetEncoding.Base64String.DecodeStringToBytes(Base64Text) );
114 | end;
115 |
116 | function TVectorResourceItem.GetImageStream: TStream;
117 | var
118 | Base64Text: string;
119 | begin
120 | if FImage.Trim.IsEmpty or not FileExists(FImage) then
121 | Exit(nil);
122 |
123 | {--- Consistently get a Base-64 string, without direct I/O }
124 | if FImage.StartsWith('http', True) then
125 | Base64Text := THttpx.LoadDataToBase64(FImage)
126 | else
127 | if TFile.Exists(FImage) then
128 | Base64Text := GenAI.NetEncoding.Base64.EncodeBase64(FImage)
129 | else
130 | Base64Text := FImage;
131 |
132 | {--- Convert Base-64 -> memory stream (business layer) }
133 | Result := TMemoryStream.Create;
134 | try
135 | DecodeBase64ToStream(Base64Text, Result);
136 | Result.Position := 0;
137 | except
138 | Result.Free;
139 | raise;
140 | end;
141 | end;
142 |
143 | { TVectorResourceList }
144 |
145 | procedure TVectorResourceList.Clear;
146 | begin
147 | for var Item in Data do
148 | Item.Free;
149 | FItemIndex := -1;
150 | FData := [];
151 | end;
152 |
153 | destructor TVectorResourceList.Destroy;
154 | begin
155 | Clear;
156 | inherited;
157 | end;
158 |
159 | class function TVectorResourceList.Instance: TVectorResourceList;
160 | begin
161 | if not Assigned(FInstance) then
162 | FInstance := TVectorResourceList.Load as TVectorResourceList;
163 | Result := FInstance;
164 | end;
165 |
166 | class function TVectorResourceList.Reload(
167 | const FileName: string): TVectorResourceList;
168 | begin
169 | FInstance.Free;
170 | FInstance := TVectorResourceList.Load(FileName) as TVectorResourceList;
171 | Result := FInstance;
172 | end;
173 |
174 | { TVectorResourceListProp }
175 |
176 | class function TVectorResourceListProp.Data: string;
177 | begin
178 | Result := 'data';
179 | end;
180 |
181 | class function TVectorResourceListProp.ItemIndex: string;
182 | begin
183 | Result := 'itemIndex';
184 | end;
185 |
186 | class function TVectorResourceListProp.Name: string;
187 | begin
188 | Result := 'name';
189 | end;
190 |
191 | end.
192 |
--------------------------------------------------------------------------------
/source/Startup.Context.pas:
--------------------------------------------------------------------------------
1 | unit Startup.Context;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : Startup.Context'}
6 |
7 | (*
8 | Unit: Startup.Context
9 |
10 | Purpose:
11 | Provides a centralized and structured initialization context for application startup in the File2knowledgeAI project.
12 | Encapsulates core interfaces and procedures such as user interface display, service prompts, resizing logic,
13 | form presentation, and error handling. Enables configuration-driven bootstrapping and supports
14 | dependency injection for all key startup services.
15 |
16 | Architecture and Design:
17 | - Exposes the TStartupContext class, which aggregates interfaces and callbacks required during startup.
18 | - Supports clean separation of startup concerns and enhances testability by allowing granular injection of dependencies.
19 | - Promotes flexibility and modularity for customizing application bootstrapping logic.
20 |
21 | Usage:
22 | - Instantiate TStartupContext with required interfaces and procedures when launching application services or forms.
23 | - Access provided methods to retrieve associated startup dependencies for use throughout the application initialization process.
24 |
25 | Context:
26 | Intended for use within modules and components that require well-structured and maintainable startup workflows,
27 | particularly where reuse and customization of initialization logic are priorities in the File2knowledgeAI ecosystem.
28 |
29 | Conventions follow File2knowledgeAI best practices for modular design, maintainability, and clear documentation.
30 | *)
31 |
32 | {$ENDREGION}
33 |
34 | uses
35 | Manager.Intf, System.SysUtils;
36 |
37 | type
38 | ///
39 | /// Provides the initialization context for application startup in the File2knowledgeAI architecture.
40 | ///
41 | ///
42 | ///
43 | /// - TStartupContext centralizes key interfaces and procedures required during the application startup phase.
44 | /// It enables configuration-driven bootstrapping by encapsulating dependencies needed to present forms, handle errors,
45 | /// service prompts, display UI components, and perform layout resizing operations.
46 | ///
47 | ///
48 | /// - This class supports dependency injection for core startup activities, ensuring a clean separation of concerns and
49 | /// enhanced testability of startup workflows. It is designed for flexibility, allowing custom startup behavior by
50 | /// providing implementations of the interfaces and procedures through its constructor.
51 | ///
52 | ///
53 | /// - Typical usage involves creating an instance of TStartupContext with appropriate interface and callback
54 | /// parameters when initializing forms or services in File2knowledgeAI modules.
55 | ///
56 | ///
57 | /// The displayer interface for UI output handling.
58 | /// The interface responsible for prompting user actions or services at startup.
59 | /// A procedure to execute dynamic window or layout resizing.
60 | /// A procedure delegate to launch or display the main application form.
61 | /// A procedure to handle and display errors encountered during startup.
62 | TStartupContext = class(TInterfacedObject, IStartupContext)
63 | private
64 | FDisplayer : IDisplayer;
65 | FServicePrompt : IServicePrompt;
66 | FResizeProc : TProc;
67 | FFormPresenter : TProc;
68 | FOnError: TProc;
69 | function GetDisplayer: IDisplayer;
70 | function GetServicePrompt: IServicePrompt;
71 | function GetResizeProc: TProc;
72 | function GetFormPresenter: TProc;
73 | function GetOnError: TProc;
74 | public
75 | ///
76 | /// Initializes a new instance of the TStartupContext class with specified service interfaces and startup procedures.
77 | ///
78 | /// Provides the user interface display logic at application startup.
79 | /// Supplies prompt and user interaction capabilities for initial service operations.
80 | /// A procedure reference for handling dynamic layout or window resizing during startup.
81 | /// A procedure for presenting or launching the main application form.
82 | /// A procedure reference responsible for error notification and handling at startup.
83 | ///
84 | /// Use this constructor to inject all required dependencies and customizable procedures needed for the application's startup workflow. This enables flexible initialization, unit testing, and clear separation of startup responsibilities within the File2knowledgeAI project.
85 | ///
86 | constructor Create(const ADisplayer: IDisplayer;
87 | const AServicePrompt: IServicePrompt; const AResizeProc: TProc;
88 | const AFormPresenter: TProc; const AOnError: TProc);
89 | end;
90 |
91 | implementation
92 |
93 | { TStartupContext }
94 |
95 | constructor TStartupContext.Create(const ADisplayer: IDisplayer;
96 | const AServicePrompt: IServicePrompt; const AResizeProc: TProc;
97 | const AFormPresenter: TProc; const AOnError: TProc);
98 | begin
99 | inherited Create;
100 | FDisplayer := ADisplayer;
101 | FServicePrompt := AServicePrompt;
102 | FResizeProc := AResizeProc;
103 | FFormPresenter := AFormPresenter;
104 | FOnError := AOnError;
105 | end;
106 |
107 | function TStartupContext.GetDisplayer: IDisplayer;
108 | begin
109 | Result := FDisplayer;
110 | end;
111 |
112 | function TStartupContext.GetFormPresenter: TProc;
113 | begin
114 | Result := FFormPresenter;
115 | end;
116 |
117 | function TStartupContext.GetOnError: TProc;
118 | begin
119 | Result := FOnError;
120 | end;
121 |
122 | function TStartupContext.GetResizeProc: TProc;
123 | begin
124 | Result := FResizeProc;
125 | end;
126 |
127 | function TStartupContext.GetServicePrompt: IServicePrompt;
128 | begin
129 | Result := FServicePrompt;
130 | end;
131 |
132 | end.
133 |
--------------------------------------------------------------------------------
/VCL/CancellationButton.VCL.pas:
--------------------------------------------------------------------------------
1 | unit CancellationButton.VCL;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : CancellationButton.VCL'}
6 |
7 | (*
8 | Unit: CancellationButton.VCL
9 |
10 | Purpose:
11 | This unit implements a simple, reusable VCL component for managing "cancellation" actions in user interfaces.
12 | The class TCancellationVCL wraps a TButton, providing mechanisms to detect, reset, and visually signal
13 | a cancellation request (e.g., to halt long-running or background operations).
14 | It supports toggling between normal and cancellation states, making it easy to add cancellation logic
15 | to any interactive workflow.
16 |
17 | Technical details:
18 | - TCancellationVCL implements ICancellation for standardized cancellation detection and signaling.
19 | - Manages button text and event handler swapping to represent normal vs cancellation states.
20 | - Stores and restores the original button caption and OnClick event to ensure seamless UI integration.
21 | - Uses a custom "cancel" glyph (Unicode character) for visual feedback in cancellation mode.
22 | - The Reset method initializes the cancellation state and prepares the button for use; Cancel
23 | returns the button to its original state.
24 | - Tracks the cancellation state via the FCancelled property, accessible via IsCancelled.
25 |
26 | Dependencies:
27 | - Delphi VCL TButton for user interaction.
28 | - UI.Styles.VCL for consistent button styling across the application.
29 | - Manager.Intf for interface-based interaction with broader application components.
30 | - Standard System.Classes and Controls for event and type definitions.
31 |
32 | Quick start for developers:
33 | - Instantiate TCancellationVCL, passing a TButton to its constructor.
34 | - Call Reset to switch the button into cancellation mode; the button will now display a "cancel" icon
35 | and respond to clicks by invoking Cancel.
36 | - Use IsCancelled to check from your workflow logic whether cancellation was requested.
37 | - When finished or aborting, call Cancel to restore the button to its original caption and behavior.
38 |
39 | This unit is intended for easy drop-in cancellation support in Delphi VCL applications,
40 | promoting clarity, user feedback, and minimal code coupling for cancellation functionality.
41 | *)
42 |
43 | {$ENDREGION}
44 |
45 | uses
46 | System.Classes, Vcl.StdCtrls, Vcl.Controls, Manager.Intf, UI.Styles.VCL, Vcl.Buttons;
47 |
48 | type
49 | ///
50 | /// Provides a reusable VCL component for user-initiated cancellation actions in Delphi applications.
51 | ///
52 | /// - TCancellationVCL manages a TSpeedButton for workflow cancellation requests. It implements the ICancellation interface
53 | /// for standardized cancellation signaling and state detection. This class visually indicates the cancellation state,
54 | /// manages button caption and handler changes, and ensures original button state restoration upon cancellation or reset.
55 | ///
56 | ///
57 | ///
58 | /// - Integrates seamlessly with VCL applications for interactive or long-running operations requiring cancellation support.
59 | ///
60 | ///
61 | /// - Uses a standard "cancel" glyph and consistent button styling via UI.Styles.VCL.
62 | ///
63 | ///
64 | /// - Original button caption and OnClick event are preserved and restored after cancellation.
65 | ///
66 | ///
67 | /// - The IsCancelled property allows workflow logic to query cancellation state.
68 | ///
69 | ///
70 | ///
71 | ///
72 | /// The TSpeedButton instance managed for cancellation actions.
73 | ///
74 | ///
75 | ///
76 | TCancellationVCL = class(TInterfacedObject, ICancellation)
77 | private
78 | FCancelButton: TSpeedButton;
79 | FCancelled: Boolean;
80 | FOldCaption: string;
81 | FOldOnclick: TNotiFyEvent;
82 | procedure DoCancelClick(Sender: TObject);
83 | public
84 | ///
85 | /// Marks the cancellation as requested, restores the original button caption and click handler.
86 | ///
87 | procedure Cancel(isDone: Boolean = False);
88 |
89 | ///
90 | /// Returns True if a cancellation has been requested; otherwise, returns False.
91 | ///
92 | ///
93 | /// True if the user has triggered the cancellation action, otherwise False.
94 | ///
95 | function IsCancelled: Boolean;
96 |
97 | ///
98 | /// Prepares the managed button for cancellation use by saving its state,
99 | /// updating the caption to a cancel glyph, and assigning the cancellation handler.
100 | ///
101 | procedure Reset;
102 |
103 | ///
104 | /// Initializes the cancellation handler for the specified TSpeedButton.
105 | ///
106 | ///
107 | /// The TSpeedButton instance to be managed for cancellation.
108 | ///
109 | constructor Create(ACancelButton: TSpeedButton);
110 | end;
111 |
112 | implementation
113 |
114 | { TCancellationVCL }
115 |
116 | procedure TCancellationVCL.Cancel(isDone: Boolean);
117 | begin
118 | if not isDone then
119 | FCancelled := True;
120 | FCancelButton.Caption := FOldCaption;
121 | FCancelButton.OnClick := FOldOnclick;
122 | end;
123 |
124 | constructor TCancellationVCL.Create(ACancelButton: TSpeedButton);
125 | begin
126 | inherited Create;
127 | FCancelButton := ACancelButton;
128 | if Assigned(FCancelButton) then
129 | TAppStyle.ApplyCancellationButtonStyle(FCancelButton)
130 | end;
131 |
132 | procedure TCancellationVCL.DoCancelClick(Sender: TObject);
133 | begin
134 | Cancel;
135 | end;
136 |
137 | function TCancellationVCL.IsCancelled: Boolean;
138 | begin
139 | Result := FCancelled;
140 | end;
141 |
142 | procedure TCancellationVCL.Reset;
143 | begin
144 | FCancelled := False;
145 | FOldCaption := FCancelButton.Caption;
146 | FOldOnclick := FCancelButton.OnClick;
147 | FCancelButton.Caption := '';
148 | FCancelButton.OnClick := DoCancelClick;
149 | end;
150 |
151 | end.
152 |
--------------------------------------------------------------------------------
/VCL/Introducer.UserSettings.VCL.pas:
--------------------------------------------------------------------------------
1 | unit Introducer.UserSettings.VCL;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : UI.VectorResourceManager.VCL'}
6 |
7 | (*
8 | Unit: Introducer.UserSettings.VCL
9 |
10 | Purpose:
11 | Provides a structured and type-safe way to aggregate references to relevant VCL controls
12 | used in the user settings UI. This unit defines the TSettingsIntroducer record, which acts as a container
13 | for binding UI elements, making initialization and further management of user settings UI logic
14 | both clean and extensible.
15 |
16 | Technical details:
17 | - Centralizes references to all settings-related UI controls (ComboBox, Edit, MaskEdit, Label, etc.).
18 | - Implements a helper record (TSettingsIntroducerHelper) with fluent-style setters for streamlined
19 | assignment and chaining during form setup.
20 | - Facilitates clear separation between UI declaration and business logic/controller code, promoting maintainability and reusability.
21 |
22 | Usage:
23 | - Create and populate a TSettingsIntroducer with relevant controls from your form.
24 | - Pass it to logic units (such as TSettingsVCL) to link and synchronize the UI without repetitious assignment code.
25 | - Extend or refactor UI dialogs by updating this introducer, minimizing code changes elsewhere.
26 |
27 | *)
28 |
29 | {$ENDREGION}
30 |
31 | uses
32 | System.SysUtils, System.Classes,
33 | Vcl.Graphics, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ComCtrls, Vcl.Mask,
34 | Vcl.Buttons, Vcl.Forms, Vcl.Dialogs;
35 |
36 | type
37 | TSettingsIntroducer = record
38 | ScrollBox: TScrollBox;
39 | Proficiency: TComboBox;
40 | ProficiencyLabel: TLabel;
41 | PreferenceName: TMaskEdit;
42 | APIKey: TMaskEdit;
43 | SearchModel: TComboBox;
44 | SearchModelCost: TLabel;
45 | ReasoningModel: TComboBox;
46 | ReasoningModelCost: TLabel;
47 | ReasoningEffort: TComboBox;
48 | ReasoningSummary: TComboBox;
49 | WebContextSize: TComboBox;
50 | TimeOut: TComboBox;
51 | Country: TMaskEdit;
52 | City: TMaskEdit;
53 | Verbosity: TComboBox;
54 | ClearResponseButton: TSpeedButton;
55 | DashBoardLabel: TLabel;
56 | class function Empty: TSettingsIntroducer; static;
57 | end;
58 |
59 | TSettingsIntroducerHelper = record helper for TSettingsIntroducer
60 | function SetScrollBox(Value: TScrollBox): TSettingsIntroducer; inline;
61 | function SetProficiency(Value: TComboBox): TSettingsIntroducer; inline;
62 | function SetProficiencyLabel(Value: TLabel): TSettingsIntroducer; inline;
63 | function SetPreferenceName(Value: TMaskEdit): TSettingsIntroducer; inline;
64 | function SetAPIKey(Value: TMaskEdit): TSettingsIntroducer; inline;
65 | function SetSearchModel(Value: TComboBox): TSettingsIntroducer; inline;
66 | function SetSearchModelCost(Value: TLabel): TSettingsIntroducer; inline;
67 | function SetReasoningModel(Value: TComboBox): TSettingsIntroducer; inline;
68 | function SetReasoningModelCost(Value: TLabel): TSettingsIntroducer; inline;
69 | function SetReasoningEffort(Value: TComboBox): TSettingsIntroducer; inline;
70 | function SetReasoningSummary(Value: TComboBox): TSettingsIntroducer; inline;
71 | function SetWebContextSize(Value: TComboBox): TSettingsIntroducer; inline;
72 | function SetTimeOut(Value: TComboBox): TSettingsIntroducer; inline;
73 | function SetCountry(Value: TMaskEdit): TSettingsIntroducer; inline;
74 | function SetCity(Value: TMaskEdit): TSettingsIntroducer; inline;
75 | function SetVerbosity(Value: TComboBox): TSettingsIntroducer; inline;
76 | function SetClearResponseButton(Value: TSpeedButton): TSettingsIntroducer; inline;
77 | function SetDashBoardLabel(Value: TLabel): TSettingsIntroducer; inline;
78 | end;
79 |
80 | implementation
81 |
82 | { TSettingsIntroducer }
83 |
84 | class function TSettingsIntroducer.Empty: TSettingsIntroducer;
85 | begin
86 | FillChar(Result, SizeOf(Result), 0);
87 | end;
88 |
89 | { TSettingsIntroducerHelper }
90 |
91 | function TSettingsIntroducerHelper.SetAPIKey(
92 | Value: TMaskEdit): TSettingsIntroducer;
93 | begin
94 | Self.APIKey := Value;
95 | Result := Self;
96 | end;
97 |
98 | function TSettingsIntroducerHelper.SetCity(
99 | Value: TMaskEdit): TSettingsIntroducer;
100 | begin
101 | Self.City := Value;
102 | Result := Self;
103 | end;
104 |
105 | function TSettingsIntroducerHelper.SetClearResponseButton(
106 | Value: TSpeedButton): TSettingsIntroducer;
107 | begin
108 | Self.ClearResponseButton := Value;
109 | Result := Self;
110 | end;
111 |
112 | function TSettingsIntroducerHelper.SetCountry(
113 | Value: TMaskEdit): TSettingsIntroducer;
114 | begin
115 | Self.Country := Value;
116 | Result := Self;
117 | end;
118 |
119 | function TSettingsIntroducerHelper.SetDashBoardLabel(
120 | Value: TLabel): TSettingsIntroducer;
121 | begin
122 | Self.DashBoardLabel := Value;
123 | Result := Self;
124 | end;
125 |
126 | function TSettingsIntroducerHelper.SetPreferenceName(
127 | Value: TMaskEdit): TSettingsIntroducer;
128 | begin
129 | Self.PreferenceName := Value;
130 | Result := Self;
131 | end;
132 |
133 | function TSettingsIntroducerHelper.SetProficiency(
134 | Value: TComboBox): TSettingsIntroducer;
135 | begin
136 | Self.Proficiency := Value;
137 | Result := Self;
138 | end;
139 |
140 | function TSettingsIntroducerHelper.SetProficiencyLabel(
141 | Value: TLabel): TSettingsIntroducer;
142 | begin
143 | Self.ProficiencyLabel := Value;
144 | Result := Self;
145 | end;
146 |
147 | function TSettingsIntroducerHelper.SetReasoningEffort(
148 | Value: TComboBox): TSettingsIntroducer;
149 | begin
150 | Self.ReasoningEffort := Value;
151 | Result := Self;
152 | end;
153 |
154 | function TSettingsIntroducerHelper.SetReasoningModel(
155 | Value: TComboBox): TSettingsIntroducer;
156 | begin
157 | Self.ReasoningModel := Value;
158 | Result := Self;
159 | end;
160 |
161 | function TSettingsIntroducerHelper.SetReasoningModelCost(
162 | Value: TLabel): TSettingsIntroducer;
163 | begin
164 | Self.ReasoningModelCost := Value;
165 | Result := Self;
166 | end;
167 |
168 | function TSettingsIntroducerHelper.SetReasoningSummary(
169 | Value: TComboBox): TSettingsIntroducer;
170 | begin
171 | Self.ReasoningSummary := Value;
172 | Result := Self;
173 | end;
174 |
175 | function TSettingsIntroducerHelper.SetScrollBox(
176 | Value: TScrollBox): TSettingsIntroducer;
177 | begin
178 | Self.ScrollBox := Value;
179 | Result := Self;
180 | end;
181 |
182 | function TSettingsIntroducerHelper.SetSearchModel(
183 | Value: TComboBox): TSettingsIntroducer;
184 | begin
185 | Self.SearchModel := Value;
186 | Result := Self;
187 | end;
188 |
189 | function TSettingsIntroducerHelper.SetSearchModelCost(
190 | Value: TLabel): TSettingsIntroducer;
191 | begin
192 | Self.SearchModelCost := Value;
193 | Result := Self;
194 | end;
195 |
196 | function TSettingsIntroducerHelper.SetTimeOut(
197 | Value: TComboBox): TSettingsIntroducer;
198 | begin
199 | Self.TimeOut := Value;
200 | Result := Self;
201 | end;
202 |
203 | function TSettingsIntroducerHelper.SetVerbosity(
204 | Value: TComboBox): TSettingsIntroducer;
205 | begin
206 | Self.Verbosity := Value;
207 | Result := Self;
208 | end;
209 |
210 | function TSettingsIntroducerHelper.SetWebContextSize(
211 | Value: TComboBox): TSettingsIntroducer;
212 | begin
213 | Self.WebContextSize := Value;
214 | Result := Self;
215 | end;
216 |
217 | end.
218 |
--------------------------------------------------------------------------------
/WrapperAssistant.delphilsp.json:
--------------------------------------------------------------------------------
1 | { "settings": { "project": "file:///D%3A/2026-developpement/OpenAI_File_Search/WrapperAssistant.dpr", "dllname": "dcc64290.dll", "dccOptions": "-$O- -$W+ -$R+ -$Q+ --no-config -Q -TX.exe -AGenerics.Collections=System.Generics.Collections;Generics.Defaults=System.Generics.Defaults;WinTypes=Winapi.Windows;WinProcs=Winapi.Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE -DDEBUG;;FRAMEWORK_VCL -E.\\Win64\\Debug -I\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\debug\\FR\";\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\debug\";\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\release\\FR\";Source;VCL;providers;OpenAI\\source;D:\\2026-developpement\\OpenAI\\source;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\release\";C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\Imports\\Win64;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\Imports\";C:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp\\Win64;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\include\";C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\CatalogRepository\\SynEdit-12\\source;C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\CatalogRepository\\SynEdit-12\\source\\Highlighters -LEC:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Bpl\\Win64 -LNC:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp\\Win64 -NU.\\Win64\\Debug -NSWinapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell; -OSource;VCL;providers;OpenAI\\source;D:\\2026-developpement\\OpenAI\\source;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\release\";C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\Imports\\Win64;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\Imports\";C:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp\\Win64;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\include\";C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\CatalogRepository\\SynEdit-12\\source;C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\CatalogRepository\\SynEdit-12\\source\\Highlighters -R\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\release\\FR\";Source;VCL;providers;OpenAI\\source;D:\\2026-developpement\\OpenAI\\source;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\release\";C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\Imports\\Win64;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\Imports\";C:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp\\Win64;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\include\";C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\CatalogRepository\\SynEdit-12\\source;C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\CatalogRepository\\SynEdit-12\\source\\Highlighters -U\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\debug\\FR\";\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\debug\";\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\release\\FR\";Source;VCL;providers;OpenAI\\source;D:\\2026-developpement\\OpenAI\\source;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win64\\release\";C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\Imports\\Win64;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\Imports\";C:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp\\Win64;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\include\";C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\CatalogRepository\\SynEdit-12\\source;C:\\Users\\MaxiD\\Documents\\Embarcadero\\Studio\\23.0\\CatalogRepository\\SynEdit-12\\source\\Highlighters -V -VN -VR -NBC:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp\\Win64 -NHC:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\hpp\\Win64 -NO.\\Win64\\Debug -LU" , "projectFiles":[ { "name": "Main", "file": "file:///D%3A/2026-developpement/OpenAI_File_Search/Main.pas" } ] , "includeDCUsInUsesCompletion": true, "enableKeyWordCompletion": true, "browsingPaths": [ "file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/OCX/Servers","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/SOURCE/VCL","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/rtl/common","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/SOURCE/RTL/SYS","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/rtl/win","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/rtl/win/winrt","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/ToolsAPI","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/SOURCE/IBX","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Internet","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/SOURCE/PROPERTY%20EDITORS","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/soap","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/SOURCE/XML","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Indy10/Core","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Indy10/System","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Indy10/Protocols","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/fmx","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/databinding/components","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/databinding/engine","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/databinding/graph","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/ado","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/cloud","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/datasnap","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/dbx","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/dsnap","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/vclctrls","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/datasnap/connectors","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/datasnap/proxygen","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DataExplorer","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/DUnitWizard/Source/Common","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/DUnitWizard/Source/Common/dunit","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/DUnitWizard/Source/DelphiExperts/Common","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/DUnitWizard/Source/DelphiExperts/DUnitProject","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/DUnitWizard/Source/DelphiExperts/DUnitProject/dunit","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/src","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/tests","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Experts","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/indy/abstraction","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/indy/implementation","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/indyimpl","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Property%20Editors/Indy10","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/soap/wsdlimporter","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Visualizers","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/XMLReporting","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/XPGen","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/rest","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/firedac","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/tethering","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnitX","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/ems","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/rtl/net","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/FlatBox2D","file:///C%3A/Users/Public/Documents/Embarcadero/Studio/23.0/Dcp/%25PLATFORM%25" ] , "CommonAppData": "file:///C%3A/Users/MaxiD/AppData/Roaming/Embarcadero/BDS/23.0/" , "Templates": "file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/ObjRepos/" } }
--------------------------------------------------------------------------------
/source/JSON.Resource.pas:
--------------------------------------------------------------------------------
1 | unit JSON.Resource;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : JSON.Resource'}
6 |
7 | (*
8 | Unit: JSON.Resource
9 |
10 | Purpose:
11 | This unit provides shared infrastructure for automated JSON serialization and deserialization of
12 | Delphi objects by leveraging RTTI (Run-Time Type Information).
13 | It abstracts the loading/saving of objects to and from JSON files, allowing any descendant class to
14 | be persisted with minimal boilerplate.
15 | The unit also introduces a “chainable” mechanism that enables dynamic and fluent runtime modification
16 | of any object’s public properties by property path.
17 |
18 | Technical details:
19 | - TJSONResource: Base class for any object that should support JSON persistence. It provides methods
20 | for saving (`Save`) and loading (`Load`) objects directly from/to JSON files, with
21 | intelligent default filename detection.
22 | - TJSONChain: Record that enables fluent property mutation via RTTI ("chainable set"), making it
23 | easy to modify properties dynamically at runtime, including nested and array types.
24 | - TJSONResourceHelper: Class helper that attaches `Chain` behavior seamlessly to any
25 | TJSONResource descendant.
26 | - All loading/writing leverages REST.Json for conversion, and System.Rtti for dynamic property
27 | setting, supporting camel case and both fields and public properties.
28 | - Throws explicit exceptions for invalid or corrupt JSON files to prevent silent data loss.
29 |
30 | Dependencies:
31 | - REST.Json, REST.Json.Types: For Delphi’s official object/JSON marshalling.
32 | - System.Rtti: To apply dynamic property changes and deep property paths at runtime.
33 | - System.JSON: Core Delphi JSON object support.
34 | - System.IOUtils, System.SysUtils: For file and string operations.
35 |
36 | Getting started:
37 | - Inherit from TJSONResource to make any class persistable as JSON.
38 | - Use the `Load` and `Save` class/methods to deserialize or serialize the object state.
39 | - Use `.Chain.Apply('PropertyPath', Value)` for runtime dynamic property changes on any
40 | TJSONResource instance, enabling fluent updates and reducing setter boilerplate.
41 | - DefaultFileName gives each class its own default JSON file name based on class name.
42 |
43 | This unit streamlines, unifies, and secures JSON object persistence for Delphi business objects,
44 | making it rapid and reliable to store, modify, or restore object graphs in modern cross-platform
45 | applications.
46 | *)
47 |
48 | {$ENDREGION}
49 |
50 | uses
51 | System.SysUtils, System.IOUtils, System.JSON, System.Rtti, REST.Json;
52 |
53 | type
54 | TFactory = reference to function: T;
55 | TJSONResourceClass = class of TJSONResource;
56 |
57 | TJSONResource = class
58 | public
59 | constructor Create; virtual;
60 | class function DefaultFileName: string; virtual;
61 | class function Load(const FileName: string = ''): TJSONResource; virtual;
62 | procedure Save(const FileName: string = '');
63 | end;
64 |
65 | TJSONChain = record
66 | private
67 | FInstance: TJSONResource;
68 | procedure SetPropByPath(const APropPath: string; const AValue: TValue);
69 | public
70 | class function FromInstance(AInstance: TJSONResource): TJSONChain; static;
71 | function Apply(const APropPath: string; const AValue: TValue): TJSONChain; overload;
72 | function Apply(const APropPath: string; const AValue: T): TJSONChain; overload;
73 | function Apply(const APropPath: string; const AValue: array of T): TJSONChain; overload;
74 | function Apply(const APropPath: string; const AValue: array of TFactory): TJSONChain; overload;
75 | function Save(const AFileName: string = ''): TJSONChain;
76 | property Instance: TJSONResource read FInstance;
77 | end;
78 |
79 | TJSONResourceHelper = class helper for TJSONResource
80 | public
81 | function Chain: TJSONChain;
82 | end;
83 |
84 | implementation
85 |
86 | { TJSONResource }
87 |
88 | constructor TJSONResource.Create;
89 | begin
90 | inherited Create;
91 |
92 | end;
93 |
94 | class function TJSONResource.DefaultFileName: string;
95 | begin
96 | {--- Take the name of the class without its T prefix }
97 | Result := ClassName.Substring(1) + '.json';
98 | end;
99 |
100 | class function TJSONResource.Load(const FileName: string): TJSONResource;
101 | var
102 | LFileName: string;
103 | Raw: string;
104 | LJSONObject: TJSONObject;
105 | begin
106 | LFileName := FileName;
107 | if LFileName = EmptyStr then
108 | LFileName := DefaultFileName;
109 |
110 | if not TFile.Exists(LFileName) then
111 | {--- if no file then returns a blank instance }
112 | Exit(TJSONResourceClass(Self).Create);
113 |
114 | Raw := TFile.ReadAllText(LFileName, TEncoding.UTF8);
115 |
116 | {--- Parse into TJSONObject }
117 | LJSONObject := TJSONObject.ParseJSONValue(Raw) as TJSONObject;
118 | if LJSONObject = nil then
119 | raise Exception.CreateFmt('Invalid JSON in %s', [LFileName]);
120 |
121 | try
122 | {--- Creates the instance of the correct type (Self = calling metaclass) }
123 | Result := TJSONResourceClass(Self).Create;
124 |
125 | {--- and finally, let RTTI fill in Result }
126 | TJson.JsonToObject(Result, LJSONObject,
127 | {--- Maybe open to more properties }
128 | [joSerialFields, joSerialPublicProps, joIndentCaseCamel]);
129 |
130 | finally
131 | LJSONObject.Free;
132 | end;
133 | end;
134 |
135 | procedure TJSONResource.Save(const FileName: string);
136 | var
137 | LFileName: string;
138 | JsonValue: TJSONValue;
139 | Formatted: string;
140 | begin
141 | LFileName := FileName;
142 | if LFileName = EmptyStr then
143 | LFileName := DefaultFileName;
144 |
145 | JsonValue := TJson.ObjectToJsonObject(Self,
146 | [joSerialFields, joSerialPublicProps, joIndentCaseCamel]);
147 | try
148 | {--- Pretty formated }
149 | Formatted := JsonValue.Format(2);
150 | TFile.WriteAllText(LFileName, Formatted, TEncoding.UTF8);
151 | finally
152 | JsonValue.Free;
153 | end;
154 | end;
155 |
156 | { TJSONChain }
157 |
158 | {$REGION 'TJSONChain'}
159 |
160 | function TJSONChain.Apply(const APropPath: string;
161 | const AValue: array of T): TJSONChain;
162 | var
163 | Data: TArray;
164 | begin
165 | SetLength(Data, Length(AValue));
166 | for var i := 0 to High(AValue) do
167 | Data[i] := AValue[i];
168 | Result := Apply(APropPath, TValue.From>(Data));
169 | end;
170 |
171 | function TJSONChain.Apply(const APropPath: string;
172 | const AValue: array of TFactory): TJSONChain;
173 | var
174 | Data: TArray;
175 | begin
176 | SetLength(Data, Length(AValue));
177 | for var i := 0 to High(AValue) do
178 | Data[i] := AValue[i]();
179 | Result := Apply(APropPath, TValue.From>(Data));
180 | end;
181 |
182 | function TJSONChain.Apply(const APropPath: string;
183 | const AValue: T): TJSONChain;
184 | begin
185 | Result := Apply(APropPath, TValue.From(AValue));
186 | end;
187 |
188 | function TJSONChain.Apply(const APropPath: string; const AValue: TValue): TJSONChain;
189 | begin
190 | Result := Self;
191 | SetPropByPath(APropPath, AValue);
192 | end;
193 |
194 | class function TJSONChain.FromInstance(AInstance: TJSONResource): TJSONChain;
195 | begin
196 | Result.FInstance := AInstance;
197 | end;
198 |
199 | function TJSONChain.Save(const AFileName: string): TJSONChain;
200 | begin
201 | FInstance.Save(AFileName);
202 | Result := Self;
203 | end;
204 |
205 | procedure TJSONChain.SetPropByPath(const APropPath: string; const AValue: TValue);
206 | var
207 | Ctx: TRttiContext;
208 | RTTIType: TRttiType;
209 | Prop: TRttiProperty;
210 | Parts: TArray;
211 | CurrentObj: TObject;
212 | Last: Integer;
213 | begin
214 | Parts := APropPath.Split(['.']);
215 | Last := High(Parts);
216 | CurrentObj := FInstance;
217 |
218 | {--- Go down to the penultimate part }
219 | for var i := 0 to Last - 1 do
220 | begin
221 | RTTIType := Ctx.GetType(CurrentObj.ClassType);
222 | Prop := RTTIType.GetProperty(Parts[i]);
223 | if not Assigned(Prop) then
224 | raise Exception.CreateFmt('Property "%s" not found on %s',
225 | [Parts[i], CurrentObj.ClassName]);
226 | CurrentObj := Prop.GetValue(CurrentObj).AsObject;
227 | if CurrentObj = nil then
228 | raise Exception.CreateFmt('The sub-property "%s" is NIL', [Parts[i]]);
229 | end;
230 |
231 | {--- Affect the last part }
232 | RTTIType := Ctx.GetType(CurrentObj.ClassType);
233 | Prop := RTTIType.GetProperty(Parts[Last]);
234 | if not Assigned(Prop) then
235 | raise Exception.CreateFmt('Property "%s" not found on %s',
236 | [Parts[Last], CurrentObj.ClassName]);
237 | Prop.SetValue(CurrentObj, AValue);
238 | end;
239 |
240 | {$ENDREGION}
241 |
242 | {$REGION 'TJSONResourceHelper'}
243 |
244 | function TJSONResourceHelper.Chain: TJSONChain;
245 | begin
246 | {--- Returns a TJSONChain “attached” to Self }
247 | Result := TJSONChain.FromInstance(Self);
248 | end;
249 |
250 | {$ENDREGION}
251 |
252 | end.
253 |
--------------------------------------------------------------------------------
/VCL/UI.PageSelector.VCL.pas:
--------------------------------------------------------------------------------
1 | unit UI.PageSelector.VCL;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : UI.PageSelector.VCL'}
6 |
7 | (*
8 | Unit: UI.PageSelector.VCL
9 |
10 | Purpose:
11 | This unit implements the logic for a UI page selection component in a Delphi VCL application.
12 | It provides a streamlined way to navigate between key pages (such as Chat History, File Search,
13 | Web Search, Reasoning, and Settings) using a ComboBox as selector linked to a TPageControl, with
14 | enhanced visual feedback through associated icons and a styled Label.
15 |
16 | Technical details:
17 | - Defines the TPageSelector enum and its helper for easy retrieval of string names, icons, default page,
18 | and conversion between enum and indices.
19 | - The TSelectorVCL class encapsulates the ComboBox, PageControl, and Label, wiring their behaviors
20 | and appearance to provide a seamless page selection experience.
21 | - Uses event handlers to synchronize ComboBox selection with TabControl pages and updates the Label
22 | to the selected page name.
23 | - Leverages custom styles from TAppStyle for unified UI appearance and behavior.
24 | - All setup logic (event wiring, populating ComboBox, default settings) is handled in class setters,
25 | making it easy to integrate or extend.
26 |
27 | Dependencies:
28 | - Uses `UI.Styles.VCL` for custom UI theming and styling applied to the selector controls.
29 | - Standard Delphi VCL controls: TComboBox, TPageControl, TLabel, etc.
30 | - `Manager.Intf` for integrating with broader application interfaces or infrastructure.
31 |
32 | Quick start for developers:
33 | - Instantiate TSelectorVCL, providing it with the target ComboBox, PageControl, and Label from your form.
34 | - The selector will automatically initialize, populate, and synchronize the UI elements.
35 | - Page changes in the ComboBox will activate the corresponding tab in the PageControl and update the label.
36 | - Extend the TPageSelector enum or its helper if you wish to add new pages or customize icons/names.
37 |
38 | This unit is intended to simplify the integration of a modern, visually consistent, and easily maintainable
39 | page selector in cross-page Delphi VCL applications.
40 |
41 | *)
42 |
43 | {$ENDREGION}
44 |
45 | uses
46 | Winapi.Windows, Winapi.Messages, Winapi.CommCtrl,
47 | System.SysUtils, System.Variants, System.Classes,
48 | Vcl.Graphics, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ComCtrls,
49 | Manager.Intf, Manager.Types, UI.Styles.VCL;
50 |
51 | type
52 | ///
53 | /// Provides a unified, visually enhanced page selector component for Delphi VCL applications, enabling
54 | /// streamlined navigation between application pages such as Chat History, File Search, Web Search,
55 | /// Reasoning, and Settings.
56 | ///
57 | ///
58 | ///
59 | /// TSelectorVCL encapsulates the logic and user interface wiring required to synchronize a
60 | /// see "Vcl.StdCtrls.TComboBox", "Vcl.ComCtrls.TPageControl" and "Vcl.StdCtrls.TLabel"/> for seamless
61 | /// page selection.
62 | /// It leverages custom styles and icons for improved user experience and maintainability.
63 | ///
64 | ///
65 | /// Selecting a page via the ComboBox updates the PageControl and Label automatically, and all event
66 | /// handling and initialization are managed internally for easy integration. The class supports extension
67 | /// through enumeration updates for new pages or icons.
68 | ///
69 | ///
70 | /// TSelectorVCL follows File2knowledgeAI interface conventions and is intended for use in multi-page VCL
71 | /// applications that require quick, user-friendly page navigation.
72 | ///
73 | ///
74 | TSelectorVCL = class(TInterfacedObject, ISelector)
75 | private
76 | FComboBox: TComboBox;
77 | FPageControl: TPageControl;
78 | FLabel: TLabel;
79 | FCurrentPage: TPageSelector;
80 | FUpdating: Boolean;
81 | procedure SetCombobox(const Value: TComboBox);
82 | procedure SetPageControl(const Value: TPageControl);
83 | procedure SetLabel(const Value: TLabel);
84 | function GetActivePage: TPageSelector;
85 | protected
86 | procedure SetActivePage(const Page: TPageSelector);
87 | procedure HandleComboBoxChange(Sender: TObject);
88 | procedure HandleComboBoxCloseUp(Sender: TObject);
89 | procedure HandleOnActivePage(PageSelected: TPageSelector);
90 | public
91 | ///
92 | /// Creates an instance of TSelectorVCL for managing UI page selection in a VCL application.
93 | ///
94 | ///
95 | /// The used for page selection.
96 | ///
97 | ///
98 | /// The representing the application pages.
99 | ///
100 | ///
101 | /// The providing visual feedback for the selected page.
102 | ///
103 | constructor Create(const ACombobox: TComboBox; const APageControl: TPageControl;
104 | const ALabel: TLabel);
105 |
106 | ///
107 | /// Displays and activates the specified page in the selector.
108 | /// Synchronizes the ComboBox selection, PageControl tab, and Label to reflect the chosen page.
109 | ///
110 | ///
111 | /// The value identifying the page to show.
112 | ///
113 | procedure ShowPage(const Page: TPageSelector);
114 |
115 | ///
116 | /// Gets or sets the currently active page in the selector.
117 | ///
118 | ///
119 | /// Setting this property changes the UI to reflect the active page,
120 | /// updating the ComboBox, PageControl, and Label accordingly.
121 | ///
122 | property ActivePage: TPageSelector read GetActivePage write ShowPage;
123 | end;
124 |
125 | implementation
126 |
127 | { TSelectorVCL }
128 |
129 | constructor TSelectorVCL.Create(const ACombobox: TComboBox;
130 | const APageControl: TPageControl; const ALabel: TLabel);
131 | begin
132 | inherited Create;
133 | FCurrentPage := TPageSelector.Default;
134 | FUpdating := False;
135 |
136 | SetCombobox(ACombobox);
137 | for var i := 0 to FComboBox.Items.Count-1 do
138 | Assert(FComboBox.Items[i] = TPageSelector(i).ToIcon,
139 | Format('Combo[%d] <> Enum icon', [i]));
140 |
141 | SetPageControl(APageControl);
142 | Assert(FPageControl.PageCount = TPageSelector.Count,
143 | 'Enum <> PageControl : update either');
144 |
145 | SetLabel(ALabel);
146 | end;
147 |
148 | function TSelectorVCL.GetActivePage: TPageSelector;
149 | begin
150 | Result := FCurrentPage;
151 | end;
152 |
153 | procedure TSelectorVCL.HandleComboBoxChange(Sender: TObject);
154 | begin
155 | SetActivePage(TPageSelector.IconToPage(FComboBox.Text));
156 | end;
157 |
158 | procedure TSelectorVCL.HandleComboBoxCloseUp(Sender: TObject);
159 | begin
160 | ServicePrompt.SetFocus;
161 | end;
162 |
163 | procedure TSelectorVCL.HandleOnActivePage(PageSelected: TPageSelector);
164 | begin
165 | if PageSelected in ResponsesPages then
166 | PromptSelector.Update else
167 | PromptSelector.Hide;
168 |
169 | case PageSelected of
170 | psHistoric: ;
171 | psFileSearch: ;
172 | psWebSearch: ;
173 | psReasoning: ;
174 | psVectorFile: VectorResourceEditor.Refresh;
175 | psSettings: ;
176 | end;
177 | end;
178 |
179 | procedure TSelectorVCL.SetActivePage(const Page: TPageSelector);
180 | begin
181 | if FUpdating or (Page = FCurrentPage) then Exit;
182 |
183 | FUpdating := True;
184 | try
185 | FCurrentPage := Page;
186 | FLabel.Caption := Page.ToString;
187 | HandleOnActivePage(FCurrentPage);
188 | FComboBox.ItemIndex := Page.IndexOf;
189 | FPageControl.ActivePageIndex := Page.IndexOf;
190 | finally
191 | FUpdating := False;
192 | end;
193 | end;
194 |
195 | procedure TSelectorVCL.SetCombobox(const Value: TComboBox);
196 | begin
197 | FComboBox := Value;
198 | TAppStyle.ApplyPageSelectorComboBoxStyleBig(Value,
199 | procedure
200 | begin
201 | FComboBox.Items.Text := TPageSelector.AllIcons;
202 | FComboBox.ItemIndex := TPageSelector.Default.IndexOf;
203 | FComboBox.DropDownCount := TPageSelector.Count;
204 |
205 | FComboBox.OnChange := HandleComboBoxChange;
206 | FComboBox.OnCloseUp := HandleComboBoxCloseUp;
207 | end);
208 | end;
209 |
210 | procedure TSelectorVCL.SetLabel(const Value: TLabel);
211 | begin
212 | FLabel := Value;
213 | TAppStyle.ApplyPageSelectorLabelStyle(Value);
214 | end;
215 |
216 | procedure TSelectorVCL.SetPageControl(const Value: TPageControl);
217 | begin
218 | FPageControl := Value;
219 | end;
220 |
221 | procedure TSelectorVCL.ShowPage(const Page: TPageSelector);
222 | begin
223 | var LPage := Page;
224 | if TThread.Current.ThreadID = MainThreadID then
225 | SetActivePage(LPage)
226 | else
227 | TThread.Queue(nil,
228 | procedure begin SetActivePage(LPage); end);
229 | end;
230 |
231 | end.
232 |
233 |
--------------------------------------------------------------------------------
/source/Manager.IoC.pas:
--------------------------------------------------------------------------------
1 | ///
2 | /// The Manager.IoC unit implements a basic Inversion of Control (IoC) container that
3 | /// provides dependency injection capabilities for Delphi applications.
4 | ///
5 | ///
6 | ///
7 | /// This unit defines key components for managing dependency registrations and resolutions:
8 | ///
9 | ///
10 | /// - TLifetime: An enumeration that specifies whether an instance should be created as a transient object
11 | /// or maintained as a singleton throughout the application's lifetime.
12 | ///
13 | ///
14 | /// - TRegistrationInfo: A record that stores registration details for a type, including the factory method,
15 | /// the instance (for singletons), and the lifetime setting.
16 | ///
17 | ///
18 | /// - TIoCContainer: The main container class that maintains a registry of dependencies and provides methods
19 | /// to register and resolve interface implementations based on their type and an optional identifier.
20 | ///
21 | ///
22 | /// This IoC container enables decoupled and modular design by allowing objects to be instantiated and managed
23 | /// at runtime. It supports both transient and singleton lifetimes, facilitating flexible dependency management
24 | /// across the application.
25 | ///
26 | ///
27 | unit Manager.IoC;
28 |
29 | interface
30 |
31 | uses
32 | System.SysUtils, System.Classes, System.TypInfo, System.Generics.Collections;
33 |
34 | type
35 | ///
36 | /// Definition of the lifecycle.
37 | ///
38 | TLifetime = (
39 | ///
40 | /// Instances can be recreated if needed.
41 | ///
42 | Transient,
43 | ///
44 | /// Remains constant throughout the application.
45 | ///
46 | Singleton
47 | );
48 |
49 | ///
50 | /// Storage structure for a registration record.
51 | ///
52 | TRegistrationInfo = record
53 | Instance: IInterface; // For singletons
54 | FactoryMethod: TFunc; // Function returning an interface
55 | Lifetime: TLifetime;
56 | end;
57 |
58 | ///
59 | /// The TIoCContainer class implements a simple Inversion of Control (IoC) container
60 | /// for managing dependency registrations and resolutions in Delphi applications.
61 | ///
62 | ///
63 | ///
64 | /// TIoCContainer maintains an internal registry that maps interface types (optionally keyed
65 | /// by a name) to registration records containing factory methods and lifetime settings. This allows
66 | /// for flexible creation and management of object instances at runtime.
67 | ///
68 | ///
69 | /// The container supports two lifetimes:
70 | /// Transient: A new instance is created every time the dependency is resolved.
71 | /// Singleton: A single instance is created and shared throughout the application's lifetime.
72 | ///
73 | ///
74 | /// Use the RegisterType methods to register a dependency, and the Resolve method to retrieve
75 | /// an instance of a registered dependency.
76 | ///
77 | ///
78 | TIoCContainer = class
79 | private
80 | // Using a key of type string combining the interface name and an optional identifier
81 | FRegistry: TDictionary;
82 | ///
83 | /// Generates a unique registration key for the given interface type and an optional identifier.
84 | ///
85 | ///
86 | /// The interface type for which the key is generated.
87 | ///
88 | ///
89 | /// An optional name to differentiate multiple registrations of the same interface.
90 | ///
91 | ///
92 | /// A string representing the unique key for the registration.
93 | ///
94 | function GetRegistrationKey(const AName: string): string;
95 | public
96 | constructor Create;
97 | destructor Destroy; override;
98 | ///
99 | /// Registers a dependency for the interface type T using a factory lambda function.
100 | ///
101 | ///
102 | /// The interface type to register.
103 | ///
104 | ///
105 | /// An optional identifier to differentiate multiple registrations for the same interface.
106 | ///
107 | ///
108 | /// A lambda function that creates and returns an instance of type T.
109 | ///
110 | ///
111 | /// Specifies the lifetime of the instance; use Transient for a new instance on each resolve,
112 | /// or Singleton to share a single instance.
113 | ///
114 | procedure RegisterType(const AName: string; AFactory: TFunc; ALifetime: TLifetime = TLifetime.Transient); overload;
115 | ///
116 | /// Registers a dependency for the interface type T using a factory lambda function.
117 | /// This overload does not require an identifier.
118 | ///
119 | ///
120 | /// The interface type to register.
121 | ///
122 | ///
123 | /// A lambda function that creates and returns an instance of type T.
124 | ///
125 | ///
126 | /// Specifies the lifetime of the instance; use Transient for a new instance on each resolve,
127 | /// or Singleton to share a single instance.
128 | ///
129 | procedure RegisterType(AFactory: TFunc; ALifetime: TLifetime = TLifetime.Transient); overload;
130 | ///
131 | /// Resolves an instance of the registered interface type T.
132 | ///
133 | ///
134 | /// The interface type to resolve.
135 | ///
136 | ///
137 | /// An optional identifier that must match the one used during registration.
138 | ///
139 | ///
140 | /// An instance of type T. If the dependency was registered as a singleton, the same instance is returned
141 | /// on subsequent calls. For transient registrations, a new instance is created.
142 | ///
143 | function Resolve(const AName: string = ''): T;
144 | end;
145 |
146 | var
147 | ///
148 | /// A global instance of the TIoCContainer used for dependency injection across the application.
149 | ///
150 | ///
151 | /// This variable holds the container that manages registrations and resolutions of dependencies.
152 | /// It is instantiated during application initialization and released during finalization,
153 | /// ensuring that all registered services are available throughout the application's lifetime.
154 | ///
155 | IoC: TIoCContainer;
156 |
157 | implementation
158 |
159 | { TIoCContainer }
160 |
161 | constructor TIoCContainer.Create;
162 | begin
163 | inherited Create;
164 | FRegistry := TDictionary.Create;
165 | end;
166 |
167 | destructor TIoCContainer.Destroy;
168 | begin
169 | FRegistry.Free;
170 | inherited;
171 | end;
172 |
173 | function TIoCContainer.GetRegistrationKey(const AName: string): string;
174 | begin
175 | Result := GetTypeName(TypeInfo(T));
176 | if not AName.Trim.IsEmpty then
177 | Result := Result + '|' + AName;
178 | end;
179 |
180 | procedure TIoCContainer.RegisterType(const AName: string; AFactory: TFunc; ALifetime: TLifetime);
181 | var
182 | RegistrationKey: string;
183 | Registration: TRegistrationInfo;
184 | begin
185 | RegistrationKey := GetRegistrationKey(AName);
186 |
187 | Registration.Lifetime := ALifetime;
188 | Registration.Instance := nil;
189 | {--- Wrapping the lambda to return an IInterface }
190 | Registration.FactoryMethod := function: IInterface
191 | begin
192 | Result := AFactory();
193 | end;
194 |
195 | FRegistry.Add(RegistrationKey, Registration);
196 | end;
197 |
198 | procedure TIoCContainer.RegisterType(AFactory: TFunc;
199 | ALifetime: TLifetime);
200 | begin
201 | RegisterType(EmptyStr, AFactory, ALifetime);
202 | end;
203 |
204 | function TIoCContainer.Resolve(const AName: string): T;
205 | var
206 | RegistrationKey: string;
207 | Registration: TRegistrationInfo;
208 | Intf: IInterface;
209 | begin
210 | RegistrationKey := GetRegistrationKey(AName);
211 |
212 | if not FRegistry.TryGetValue(RegistrationKey, Registration) then
213 | raise Exception.CreateFmt('Type %s not registered in the IoC container with the name "%s"', [GetTypeName(TypeInfo(T)), AName]);
214 |
215 | case Registration.Lifetime of
216 | TLifetime.Singleton:
217 | begin
218 | if Registration.Instance = nil then
219 | begin
220 | Registration.Instance := Registration.FactoryMethod();
221 | {--- Update the record in the dictionary }
222 | FRegistry[RegistrationKey] := Registration;
223 | end;
224 | Intf := Registration.Instance;
225 | end;
226 | TLifetime.Transient:
227 | Intf := Registration.FactoryMethod();
228 | end;
229 |
230 | {--- Convert to type T (ensures the object supports the interface) }
231 | Result := T(Intf);
232 | end;
233 |
234 | initialization
235 | IoC := TIoCContainer.Create;
236 | finalization
237 | IoC.Free;
238 | end.
239 |
240 |
--------------------------------------------------------------------------------
/source/UserSettings.Persistence.pas:
--------------------------------------------------------------------------------
1 | unit UserSettings.Persistence;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : UserSettings.Persistence'}
6 |
7 | (*
8 | Unit: UserSettings.Persistence
9 |
10 | Purpose:
11 | Implements persistent storage, loading, and management of user-specific application settings.
12 | This unit encapsulates all logic for serializing, deserializing, and versioning user preferences,
13 | supporting robust profile management, profile switching, and reliable round-tripping to JSON-based storage.
14 |
15 | Description:
16 | - Defines the TSettings class for structured user configuration with complete property mapping.
17 | - Supplies TSettingsProp, centralizing property name access for all user settings fields.
18 | - Provides the TIniSettings class and IIniSettings interface to unify loading, saving, and file management.
19 | - Leverages JSON serialization for portability, human-readability, and backward compatibility.
20 | - Supports fluent-style manipulation and chained updates to settings, promoting concise and expressive persistence operations.
21 |
22 | Design Notes:
23 | - Follows the single-responsibility principle with a clear focus on configuration persistence.
24 | - Separates storage logic from UI binding and business rules for maintainability.
25 | - Easily extendable for new configuration needs and model evolution.
26 |
27 | Dependencies:
28 | - Relies on System.JSON and REST.Json for data conversion.
29 | - Intended to be used by higher-level modules that orchestrate user experience and interface logic.
30 |
31 | Usage:
32 | Instantiate or resolve an IIniSettings implementation for your persistence needs,
33 | and interact fluently with the TSettings object to load, query, modify, or save user configuration.
34 | *)
35 |
36 | {$ENDREGION}
37 |
38 | uses
39 | System.SysUtils, System.Classes, System.JSON, REST.Json.Types, REST.Json,
40 | Manager.Intf, JSON.Resource;
41 |
42 | type
43 | ///
44 | /// Encapsulates all persistent application user settings and provides JSON serialization functionality.
45 | ///
46 | ///
47 | /// TSettings contains all configurable end-user properties such as proficiency, model selection,
48 | /// API key, identity, and localization details.
49 | /// The class leverages its TJSONResource ancestor to support serialization and deserialization
50 | /// from JSON, enabling persistent storage and retrieval of user settings.
51 | ///
52 | /// Designed as a singleton, this class ensures a single, consistent settings instance is used
53 | /// throughout the application. It also provides static class methods for loading and reloading
54 | /// settings data from files.
55 | ///
56 | ///
57 | TSettings = class(TJSONResource)
58 | strict private
59 | FProficiency: string;
60 | FPreferenceName: string;
61 | FApiKey: string;
62 | FSearchModel: string;
63 | FReasoningModel: string;
64 | FReasoningEffort: string;
65 | FReasoningSummary: string;
66 | FWebContextSize: string;
67 | FTimeOut: string;
68 | FCountry: string;
69 | FCity: string;
70 | FVerbosity: string;
71 | class var FInstance: TSettings;
72 | public
73 | property Proficiency: string read FProficiency write FProficiency;
74 | property PreferenceName: string read FPreferenceName write FPreferenceName;
75 | property ApiKey: string read FApiKey write FApiKey;
76 | property SearchModel: string read FSearchModel write FSearchModel;
77 | property ReasoningModel: string read FReasoningModel write FReasoningModel;
78 | property ReasoningEffort: string read FReasoningEffort write FReasoningEffort;
79 | property ReasoningSummary: string read FReasoningSummary write FReasoningSummary;
80 | property WebContextSize: string read FWebContextSize write FWebContextSize;
81 | property TimeOut: string read FTimeOut write FTimeOut;
82 | property Country: string read FCountry write FCountry;
83 | property City: string read FCity write FCity;
84 | property Verbosity: string read FVerbosity write FVerbosity;
85 | class function Instance: TSettings; static;
86 | class function Reload(const FileName: string = ''): TSettings; static;
87 | class destructor Destroy;
88 | end;
89 |
90 | ///
91 | /// Centralizes property name constants for all user settings fields.
92 | ///
93 | ///
94 | /// TSettingsProp provides a set of static functions that return string keys corresponding
95 | /// to each configurable user setting (such as proficiency, API key, model selection, etc.).
96 | /// Using these constants ensures consistent property access across the application,
97 | /// supports dynamic binding and persistence layers, and reduces the risk of errors due to typos.
98 | ///
99 | /// This record is specifically designed to enable fluent data handling by supporting
100 | /// chained method calls when applying or manipulating settings properties.
101 | ///
102 | ///
103 | TSettingsProp = record
104 | class function Proficiency: string; static; inline;
105 | class function PreferenceName: string; static; inline;
106 | class function APIKey: string; static; inline;
107 | class function SearchModel: string; static; inline;
108 | class function ReasoningModel: string; static; inline;
109 | class function ReasoningEffort: string; static; inline;
110 | class function ReasoningSummary: string; static; inline;
111 | class function WebContextSize: string; static; inline;
112 | class function TimeOut: string; static; inline;
113 | class function Country: string; static; inline;
114 | class function City: string; static; inline;
115 | class function Verbosity: string; static; inline;
116 | end;
117 |
118 | ///
119 | /// Handles the loading, saving, and reloading of user settings from persistent storage.
120 | ///
121 | ///
122 | /// TIniSettings provides a simple interface for reading from and writing to user settings files,
123 | /// encapsulating the persistent instance of TSettings and supporting serialization to and from files.
124 | ///
125 | TIniSettings = class(TInterfacedObject, IIniSettings)
126 | private
127 | FSettings: TSettings;
128 | function GetSettings: TObject;
129 | public
130 | constructor Create;
131 |
132 | ///
133 | /// Reloads the user settings from persistent storage.
134 | ///
135 | ///
136 | /// This method replaces the current settings with those loaded from disk.
137 | /// Typically used to re-synchronize in-memory settings with external changes.
138 | ///
139 | procedure Reload;
140 |
141 | ///
142 | /// Loads settings from the specified file.
143 | ///
144 | ///
145 | /// The file path of the configuration file to load. If empty, the default settings file is used.
146 | ///
147 | ///
148 | /// Returns the file path used for loading, or an empty string if the operation fails.
149 | ///
150 | function LoadFromFile(FileName: string = ''): string;
151 |
152 | ///
153 | /// Saves the current settings to the specified file.
154 | ///
155 | ///
156 | /// The file path where the configuration should be saved. If empty, the default file is used.
157 | ///
158 | procedure SaveToFile(FileName: string = '');
159 |
160 | ///
161 | /// Gets the settings object being managed.
162 | ///
163 | ///
164 | /// Returns the current settings instance as a TObject.
165 | ///
166 | property Settings: TObject read GetSettings;
167 | end;
168 |
169 | implementation
170 |
171 | { TIniSettings }
172 |
173 | constructor TIniSettings.Create;
174 | begin
175 | inherited Create;
176 | Reload;
177 | end;
178 |
179 | function TIniSettings.GetSettings: TObject;
180 | begin
181 | Result := FSettings;
182 | end;
183 |
184 | function TIniSettings.LoadFromFile(FileName: string): string;
185 | begin
186 | FSettings := TSettings.Reload;
187 | end;
188 |
189 | procedure TIniSettings.Reload;
190 | begin
191 | FSettings := TSettings.Reload;
192 | end;
193 |
194 | procedure TIniSettings.SaveToFile(FileName: string);
195 | begin
196 | FSettings.Save(FileName);
197 | end;
198 |
199 | { TSettings }
200 |
201 | class destructor TSettings.Destroy;
202 | begin
203 | FInstance.Free;
204 | end;
205 |
206 | class function TSettings.Instance: TSettings;
207 | begin
208 | if not Assigned(FInstance) then
209 | FInstance := TSettings.Load as TSettings;
210 | Result := FInstance;
211 | end;
212 |
213 | class function TSettings.Reload(const FileName: string): TSettings;
214 | begin
215 | FInstance.Free;
216 | FInstance := TSettings.Load(FileName) as TSettings;
217 | Result := FInstance;
218 | end;
219 |
220 | { TSettingsProp }
221 |
222 | class function TSettingsProp.APIKey: string;
223 | begin
224 | Result := 'apiKey';
225 | end;
226 |
227 | class function TSettingsProp.City: string;
228 | begin
229 | Result := 'city';
230 | end;
231 |
232 | class function TSettingsProp.Country: string;
233 | begin
234 | Result := 'country';
235 | end;
236 |
237 | class function TSettingsProp.PreferenceName: string;
238 | begin
239 | Result := 'preferenceName';
240 | end;
241 |
242 | class function TSettingsProp.Proficiency: string;
243 | begin
244 | Result := 'proficiency';
245 | end;
246 |
247 | class function TSettingsProp.ReasoningEffort: string;
248 | begin
249 | Result := 'reasoningEffort';
250 | end;
251 |
252 | class function TSettingsProp.ReasoningModel: string;
253 | begin
254 | Result := 'reasoningModel';
255 | end;
256 |
257 | class function TSettingsProp.ReasoningSummary: string;
258 | begin
259 | Result := 'reasoningSummary';
260 | end;
261 |
262 | class function TSettingsProp.SearchModel: string;
263 | begin
264 | Result := 'searchModel';
265 | end;
266 |
267 | class function TSettingsProp.TimeOut: string;
268 | begin
269 | Result := 'timeOut';
270 | end;
271 |
272 | class function TSettingsProp.Verbosity: string;
273 | begin
274 | Result := 'verbosity';
275 | end;
276 |
277 | class function TSettingsProp.WebContextSize: string;
278 | begin
279 | Result := 'webContextSize';
280 | end;
281 |
282 | end.
283 |
--------------------------------------------------------------------------------
/deep-dive.md:
--------------------------------------------------------------------------------
1 | # File2knowledgeAI
2 |
3 | ### Table of Contents
4 |
5 | - [Introduction](#introduction)
6 | - [A Laboratory Space for Innovation, Experimentation, and Demonstration](#a-laboratory-space-for-innovation-experimentation-and-demonstration)
7 | - [Key Features](#key-features)
8 | - [File Search and Vectorization](#file-search-and-vectorization)
9 | - [Technical Features](#technical-features)
10 | - [Advanced Use of OpenAI `v1/responses` Endpoint](#advanced-use-of-openai-v1responses-endpoint)
11 | - [Modular, Decoupled, and Testable Architecture](#modular-decoupled-and-testable-architecture)
12 | - [Transactional and Synchronized File Management](#transactional-and-synchronized-file-management)
13 | - [Comprehensive Event-Driven GenAI Response Engine](#comprehensive-event-driven-genai-response-engine)
14 | - [Chat Sessions and Conversational Chaining](#chat-sessions-and-conversational-chaining)
15 | - [UI/Business Logic Synchronization](#uibusiness-logic-synchronization)
16 | - [Extensibility and Rapid Onboarding](#extensibility-and-rapid-onboarding)
17 | - [VCL UI & WebView2](#vcl-ui--webview2)
18 | - [Delphi Promises (JavaScript Style)](#delphi-promises-javascript-style)
19 | - [Fluent JSON & RTTI](#fluent-json--rtti)
20 | - [Application Architecture](#application-architecture)
21 | - [Generalized Dependency Injection](#generalized-dependency-injection)
22 | - [Main Facade: `TOpenAIProvider`](#main-facade-topenaiprovider)
23 | - [UI/Business Logic Decoupling & Enhanced Testability](#uibusiness-logic-decoupling--enhanced-testability)
24 | - [Centralized and Transactional Resource Management](#centralized-and-transactional-resource-management)
25 | - [Modularity, Extensibility, and Robustness](#modularity-extensibility-and-robustness)
26 | - [Native Promises and Asynchrony](#native-promises-and-asynchrony)
27 | - [Orchestrated Application Startup](#orchestrated-application-startup)
28 | - [Structured History and Persistence Management](#structured-history-and-persistence-management)
29 | - ["Everything Injectable, Everything Mockable" Pattern](#everything-injectable-everything-mockable-pattern)
30 |
31 | ___
32 |
33 | ## Introduction
34 |
35 | **File2knowledgeAI** was designed to provide a concrete implementation of OpenAI’s `v1/responses` endpoint. Its primary goal is to demonstrate how to leverage advanced file search capabilities (`file_search`) and vector store integration to enhance semantic document exploration. This approach enables more contextual, relevant, and intelligent responses when querying technical documentation, source code, or any other textual file.
36 |
37 | Aimed at Delphi developers, this application showcases how to effectively integrate and orchestrate OpenAI/GenAI capabilities in real-world projects. The project is primarily educational: it’s intended to share best practices and promote experimentation with the `v1/responses` API, modern vectorization, indexing, and conversational chaining. It’s not a competitor to commercial tools, but a platform to encourage learning, exploration, and the joy of coding.
38 |
39 | ## A Laboratory Space for Innovation, Experimentation, and Demonstration
40 |
41 | **File2knowledgeAI** was conceived as a real experimental lab for developers who want to quickly explore or validate the latest advancements of the OpenAI API. Its architecture, modularity, and refined VCL provide:
42 |
43 | - **A ready-to-use foundation for testing OpenAI features**
44 | No need to reinvent the wheel or stitch together technical building blocks: File2knowledgeAI offers a stable, decoupled, and fully mockable environment focused on GenAI integration in Delphi.
45 | All OpenAI entry points (file_search, vectorization, chaining, conversation, event streaming, etc.) are exposed and extensible. Any new endpoint, API, or method can be integrated and tested quickly.
46 |
47 | - **Focus on innovation, not plumbing**
48 | Developers can focus purely on exploring OpenAI features (advanced prompting, response handling, vector store, conversational chaining, etc.) without worrying about infrastructure, UI/business synchronization, or low-level integration.
49 |
50 | - **Inspiration and comparison with modern stacks**
51 | The project demonstrates that with Delphi—VCL, native asynchrony, and IoC—you can build architectures as robust as those in modern JS/TS stacks, while benefiting from a strongly-typed and extremely fast environment.
52 |
53 | - **No direct competition with commercial solutions**
54 | File2knowledgeAI is not a competitor to “pro” market tools: it’s a playground, a demonstrative tool, and an open-source reference to learn, experiment, understand, and deeply validate OpenAI API usage within the Delphi/VCL framework.
55 |
56 | Feel free to explore the code and contribute your own extensions.
57 |
58 | ## Key Features
59 |
60 | ### File Search and Vectorization
61 | - Import of `.txt` and `.md` files to test vectorization and AI querying via OpenAI.
62 | - Automatic embedding generation (stored on OpenAI) upon import; each query undergoes the same treatment, enabling retrieval of the most relevant passages from the indexed file base.
63 | - Results returned with similarity scores, facilitating access to key information.
64 | - Integration of 9 textual files including source code + documentation for Delphi API wrappers: each element is vectorized, providing contextual tutoring support.
65 | - Currently single-platform, focused on the Delphi ecosystem.
66 |
67 | ## Technical Features
68 |
69 | #### **Advanced Use of OpenAI `v1/responses` Endpoint**
70 | - Vector indexing for enriched, contextual search.
71 | - Prompt/response chaining via refined use of OpenAI session response IDs.
72 |
73 | #### **Modular, Decoupled, and Testable Architecture**
74 | - Systematic use of IoC (Inversion of Control).
75 | - Strict separation between UI (VCL), business logic, and domain logic (file management, prompts, vector store…).
76 | - Adherence to best practices: async promises, DI (dependency injection), simplified refactoring.
77 |
78 | #### **Transactional and Synchronized File Management**
79 | - Typed dictionaries for file/FileUploadId handling, atomic operations (add, delete, rollback), client/server sync.
80 | - Centralized controller `TFileUploadIdController` managing all file states and syncing with OpenAI’s Vector Store.
81 |
82 | #### **Comprehensive Event-Driven GenAI Response Engine**
83 | - Event engine covering every event type from the `v1/responses` endpoint (classes `TEventEngineManager`, `IStreamEventHandler`).
84 |
85 | #### **Chat Sessions and Conversational Chaining**
86 | - Persistent sessions, multi-turn chaining, dynamic history, automatic state JSON.
87 | - Centralized traceability of OpenAI IDs for intelligent response chaining.
88 |
89 | #### **UI/Business Logic Synchronization**
90 | - Centralized mode logic (file search, web search, reasoning) via a unified endpoint.
91 | - Helpers for introspection, batch editing, and sync.
92 |
93 | #### **Extensibility and Rapid Onboarding**
94 | - Abundant documentation, modular code: fast ramp-up, easy extensions, clean demo model.
95 |
96 | ## VCL UI & WebView2
97 |
98 | - Dedicated control `TEdgeDisplayerVCL`: WebView2 encapsulated under VCL.
99 | - Styled markdown rendering, advanced prompt and reasoning UI.
100 | - Dynamic HTML/JS templates hot-editable (no recompilation needed).
101 | - Async HTML/JS injection and synchronization, full decoupling via `Manager.Intf` and IoC for easy maintenance.
102 |
103 | ## Delphi Promises (JavaScript Style)
104 |
105 | - Class `TPromise`: handles Pending, Fulfilled, Rejected states.
106 | - Supports chaining (`&Then`, `&Catch`), auto-cleanup, thread-safe, built for heavy IO and OpenAI async requests.
107 | - Structured error management: no more callback hell.
108 | - Used pervasively throughout the project: async by default.
109 |
110 | ## Fluent JSON & RTTI
111 |
112 | - Class `TJSONChain` with Delphi RTTI: fluent, chainable manipulation of JSON objects (add, edit, traverse).
113 | - Auto serialization/deserialization via public properties.
114 | - Support for complex structures through fluent methods, dynamic path-based assignment, natural handling of arrays and nested properties.
115 |
116 | ## Application Architecture
117 |
118 | **File2knowledgeAI** is built on a service-oriented, ultra-modular architecture powered by Inversion of Control (IoC). This ensures strict decoupling between components, enabling:
119 |
120 | #### Generalized Dependency Injection
121 |
122 | - All major services (prompt execution, file management, vector stores, UI, session handling, etc.) are defined as interfaces. Their resolution, initialization, and lifecycle (singleton or transient) are orchestrated via a custom IoC container (`Manager.IoC`).
123 | - This simplifies mocking or replacing any system component for testing or functional extensions without heavy refactoring.
124 |
125 | #### Main Facade: `TOpenAIProvider`
126 |
127 | - This central component orchestrates all OpenAI/GenAI integration, delegating via IoC interfaces to specialized modules: async prompt handling, session chaining, streaming, vector stores, etc.
128 | - Switching execution engines (prompt engine, storage, etc.) is as simple as reconfiguring IoC.
129 |
130 | #### UI/Business Logic Decoupling & Enhanced Testability
131 |
132 | - UI interactions (VCL/WebView2) rely on interfaces (e.g., `IDisplayer`, `IServicePrompt`, `IChatSessionHistoryView`), all interchangeable, for unified handling of views, prompts, and history.
133 | - Allows complete simulation/mocking of the UI and business backend for unit or integration testing.
134 |
135 | #### Centralized and Transactional Resource Management
136 |
137 | - File management (associations, indexing, snapshots/drafts, rollback/validation) relies on injected managers.
138 | Example: `TFileUploadIdController` ensures consistency between client, server, and UI, with atomic operations on attached resources.
139 |
140 | #### Modularity, Extensibility, and Robustness
141 |
142 | - All critical logic (prompt execution, async sync, annotation, session navigation, etc.) is exposed via interfaces and designed to be plug & play via the DI container.
143 | - Fine-grained services and their decoupling make it easy to integrate new strategies, mock components for testing, or bring in future OpenAI improvements.
144 |
145 | #### Native Promises and Asynchrony
146 |
147 | - The promise engine (`Manager.Async.Promise`) allows reactive, non-blocking processing of all OpenAI, UI, or IO chains (chained execution, structured error handling, thread safety) — while remaining mockable via promise interfaces for test workflows.
148 |
149 | #### Orchestrated Application Startup
150 |
151 | - The startup core (async launch, resource checks, UI state handling, user alerts) is also injected: the `TStartupService` works from an `IStartupContext`, assembling all critical ecosystem dependencies during app launch.
152 |
153 | #### Structured History and Persistence Management
154 |
155 | - Chat sessions (persistence, chaining, editing, history navigation) rely on specialized classes and interfaces (`IPersistentChat`, `IChatSessionHistoryView`), all interchangeable and extensible.
156 |
157 | #### "Everything Injectable, Everything Mockable" Pattern
158 |
159 | - Thanks to IoC, the app is never hardwired to any technical implementation: any component can be overridden, prototyped, or simulated (e.g., to fake an OpenAI backend, simulate storage, reroute UI, etc.).
160 |
161 | This radical decoupling ensures flexibility, robustness to change, fast learning curves, and a strong testing culture. Any OpenAI platform evolution, workflow overhaul, or module addition (UI or services) becomes trivially integrable within the File2knowledgeAI ecosystem.
--------------------------------------------------------------------------------
/source/Helper.UserSettings.pas:
--------------------------------------------------------------------------------
1 | unit Helper.UserSettings;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : Helper.UserSettings'}
6 |
7 | (*
8 | Unit: Helper.UserSettings
9 |
10 | Purpose:
11 | Provides centralized helpers and metadata for user settings types and enums
12 | used throughout the application (such as AI model types, proficiency levels, intensities, summaries, and timeouts).
13 | This unit encapsulates string conversions, default values, option lists, and utility operations—
14 | supporting clear, maintainable, and scalable settings logic for both UI and backend.
15 |
16 | Technical details:
17 | - Defines and extends key enums with helper records for display, parsing, indexing, and defaults (e.g., `TModelTypeHelper`, `TProficiencyLevelHelper`).
18 | - Aggregates name arrays, default selections, icon representations, and provides methods for retrieving allowed values and string conversions.
19 | - Includes a dedicated `TModelCosts` class for model token cost calculation and formatted output.
20 | - Supports settings logic extension uniformly, from adding new options to managing localized labels or costs.
21 |
22 | Usage:
23 | - Use enum helpers for clean conversion and UI rendering logic (e.g., for ComboBox population, settings serialization, etc.).
24 | - Query model costs, allowed values, or icons via the provided helper methods.
25 | - Extend user settings simply by updating these helpers, reducing code duplication across the codebase.
26 |
27 | This unit helps ensure a single source of truth for user settings metadata, decoupling display and logic,
28 | and fostering extensibility throughout the application.
29 | *)
30 |
31 | {$ENDREGION}
32 |
33 | uses
34 | System.SysUtils, System.Classes, System.Generics.Collections;
35 |
36 | type
37 | TModelType = (mtSearch, mtReasoning);
38 |
39 | TModelTypeHelper = record helper for TModelType
40 | private
41 | const
42 | ModelNames: array[TModelType] of TArray = (
43 | {--- mtSearch }
44 | ['gpt-4o', 'gpt-4o-mini', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-5', 'gpt-5-mini',
45 | 'gpt-5-nano', 'gpt-5-chat-latest', 'gpt-5-pro', 'o3-deep-research', 'o4-mini-deep-research'],
46 | {--- mtReasoning }
47 | ['o1', 'o1-pro', 'o3', 'o3-mini', 'o4-mini', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano',
48 | 'gpt-5-pro']
49 | );
50 |
51 | DefaultModels: array[TModelType] of string = (
52 | {--- mtSearch }
53 | 'gpt-4.1-mini',
54 | {--- mtReasoning }
55 | 'o4-mini'
56 | );
57 |
58 | public
59 | function GetModelNames: TArray;
60 | function GetDefaultModel: string;
61 | function IndexOfModel(const ModelName: string): Integer;
62 | end;
63 |
64 | TProficiencyLevel = (plJunior, plIntermediate, plSenior, plLeadDev, plArchitect);
65 |
66 | TProficiencyLevelHelper = record helper for TProficiencyLevel
67 | private
68 | const
69 | Names: array[TProficiencyLevel] of string = (
70 | 'Delphi Dev – Junior',
71 | 'Delphi Dev – Intermediate',
72 | 'Delphi Dev – Senior',
73 | 'Lead Dev Delphi',
74 | 'Delphi Software Architect'
75 | );
76 |
77 | Icons: array[TProficiencyLevel] of string = (
78 | '', '', '', '', ''
79 | );
80 |
81 | DefaultLevel = plIntermediate;
82 | public
83 | function ToString: string;
84 | function ToIcon: string;
85 | class function FromIndex(Index: Integer): TProficiencyLevel; static;
86 | class function Default: TProficiencyLevel; static;
87 | class function Count: Integer; static;
88 | class function AllIcons: string; static;
89 | end;
90 |
91 | TModelCosts = class
92 | private
93 | FCosts: TDictionary>;
94 | const TOKEN_COST_PATTERN = 'Per 1M tokens Input: %s output: %s';
95 | public
96 | constructor Create;
97 | destructor Destroy; override;
98 | function GetCost(ModelType: TModelType; Index: Integer): string;
99 | end;
100 |
101 | TIntensity = (iyLow, iyMedium, iyHigh);
102 |
103 | TIntensityHelper = record Helper for TIntensity
104 | private
105 | const
106 | Intensities : array[TIntensity] of string = (
107 | 'Low', 'Medium', 'High'
108 | );
109 | DefaultIntensity = iyMedium;
110 | public
111 | function ToString: string;
112 | class function Default: TIntensity; static;
113 | class function FromIndex(Index: Integer): TIntensity; static;
114 | class function Count: Integer; static;
115 | class function AllIntensities: string; static;
116 | end;
117 |
118 | TVerbosity = (vyLow, vyMedium, vyHigh);
119 |
120 | TVerbosityHelper = record Helper for TVerbosity
121 | private
122 | const
123 | Verbosities : array[TVerbosity] of string = (
124 | 'Low', 'Medium', 'High'
125 | );
126 | DefaultVerbosity = vyMedium;
127 | public
128 | function ToString: string;
129 | class function Default: TVerbosity; static;
130 | class function FromIndex(Index: Integer): TVerbosity; static;
131 | class function Count: Integer; static;
132 | class function AllVerbosities: string; static;
133 | end;
134 |
135 | TSummary = (syNone, syDetailed);
136 |
137 | TSummaryHelper = record Helper for TSummary
138 | private
139 | const
140 | Summaries : array[TSummary] of string = (
141 | 'None', 'Detailed'
142 | );
143 | DefaultSummary = syNone;
144 | public
145 | function ToString: string;
146 | class function Default: TSummary; static;
147 | class function FromIndex(Index: Integer): TSummary; static;
148 | class function Count: Integer; static;
149 | class function AllSummaries: string; static;
150 | end;
151 |
152 | TTimeOut = (t30s, t60s, t5m, t10m, t20m, t30m, t60m, t5h, t12h, t24h);
153 |
154 | TTimeOutHelper = record Helper for TTimeOut
155 | private
156 | const
157 | TimeOuts : array[TTimeOut] of string = (
158 | '30 seconds' , '60 seconds' ,
159 | '5 minutes' , '10 minutes' , '20 minutes' , '30 minutes' , '60 minutes' ,
160 | '5 hours' , '12 hours' , '24 hours'
161 | );
162 |
163 | Timeoutms : array[TTimeOut] of Cardinal = (
164 | 30000, 60000,
165 | 300000, 600000, 1200000, 1800000, 3600000,
166 | 18000000, 43200000, 86400000
167 | );
168 |
169 | DefaultTimeOut = t60m;
170 | public
171 | function ToString: string;
172 | function ToMilliseconds: Cardinal;
173 | class function Default: TTimeOut; static;
174 | class function FromIndex(Index: Integer): TTimeOut; static;
175 | class function TextToCardinal(const Text: string): Cardinal; static;
176 | class function Count: Integer; static;
177 | class function AllTimeOuts: string; static;
178 | end;
179 |
180 | function IsDeepResearchModel(const Value: string): Boolean;
181 |
182 | implementation
183 |
184 | function IsDeepResearchModel(const Value: string): Boolean;
185 | begin
186 | Result := Value.Contains('research');
187 | end;
188 |
189 | { TModelTypeHelper }
190 |
191 | function TModelTypeHelper.GetDefaultModel: string;
192 | begin
193 | Result := DefaultModels[Self];
194 | end;
195 |
196 | function TModelTypeHelper.GetModelNames: TArray;
197 | begin
198 | Result := ModelNames[Self];
199 | end;
200 |
201 | function TModelTypeHelper.IndexOfModel(const ModelName: string): Integer;
202 | begin
203 | Result := TArray.IndexOf(ModelNames[Self], ModelName);
204 | end;
205 |
206 | { TProficiencyLevelHelper }
207 |
208 | class function TProficiencyLevelHelper.AllIcons: string;
209 | begin
210 | Result := String.Join(#10, Icons);
211 | end;
212 |
213 | class function TProficiencyLevelHelper.Count: Integer;
214 | begin
215 | Result := Length(Names);
216 | end;
217 |
218 | class function TProficiencyLevelHelper.Default: TProficiencyLevel;
219 | begin
220 | Result := DefaultLevel;
221 | end;
222 |
223 | class function TProficiencyLevelHelper.FromIndex(
224 | Index: Integer): TProficiencyLevel;
225 | begin
226 | if (Index >= 0) and (Index < Count) then
227 | Result := TProficiencyLevel(Index)
228 | else
229 | Result := Default;
230 | end;
231 |
232 | function TProficiencyLevelHelper.ToIcon: string;
233 | begin
234 | Result := Icons[Self];
235 | end;
236 |
237 | function TProficiencyLevelHelper.ToString: string;
238 | begin
239 | Result := Names[Self];
240 | end;
241 |
242 | { TModelCosts }
243 |
244 | constructor TModelCosts.Create;
245 | begin
246 | FCosts := TDictionary>.Create;
247 |
248 | {--- Costs of research models }
249 | FCosts.Add(Ord(mtSearch) * 1000 + 0, ['$2.50', '$10.00']); // gpt-4o
250 | FCosts.Add(Ord(mtSearch) * 1000 + 1, ['$0.15', '$0.60']); // gpt-4o-mini
251 | FCosts.Add(Ord(mtSearch) * 1000 + 2, ['$2.00', '$8.00']); // gpt-4.1
252 | FCosts.Add(Ord(mtSearch) * 1000 + 3, ['$0.40', '$1.60']); // gpt-4.1-mini
253 | FCosts.Add(Ord(mtSearch) * 1000 + 4, ['$0.10', '$0.40']); // gpt-4.1-nano
254 | FCosts.Add(Ord(mtSearch) * 1000 + 5, ['$1.25', '$10.00']); // gpt-5
255 | FCosts.Add(Ord(mtSearch) * 1000 + 6, ['$0.25', '$2.00']); // gpt-5-mini
256 | FCosts.Add(Ord(mtSearch) * 1000 + 7, ['$0.05', '$0.40']); // gpt-5-nano
257 | FCosts.Add(Ord(mtSearch) * 1000 + 8, ['$1.25', '$10.00']); // gpt-5-chat-latest
258 | FCosts.Add(Ord(mtSearch) * 1000 + 9, ['$15.00', '$120.00']); // gpt-5-pro
259 | FCosts.Add(Ord(mtSearch) * 1000 + 10, ['$10.00', '$40.00']); // o3-deep-research
260 | FCosts.Add(Ord(mtSearch) * 1000 + 11, ['$2.00', '$8.00']); // o4-mini-deep-research
261 |
262 | {--- Costs of reasoning models }
263 | FCosts.Add(Ord(mtReasoning) * 1000 + 0, ['$15.00', '$60.00']); // o1
264 | FCosts.Add(Ord(mtReasoning) * 1000 + 1, ['$150.00', '$600.00']); // o1-pro
265 | FCosts.Add(Ord(mtReasoning) * 1000 + 2, ['$10.00', '$40.00']); // o3
266 | FCosts.Add(Ord(mtReasoning) * 1000 + 3, ['$1.10', '$4.40']); // o3-mini
267 | FCosts.Add(Ord(mtReasoning) * 1000 + 4, ['$1.10', '$4.40']); // o4-mini
268 | FCosts.Add(Ord(mtReasoning) * 1000 + 5, ['$1.25', '$10.00']); // gpt-5
269 | FCosts.Add(Ord(mtReasoning) * 1000 + 6, ['$0.25', '$2.00']); // gpt-5-mini
270 | FCosts.Add(Ord(mtReasoning) * 1000 + 7, ['$0.05', '$0.40']); // gpt-5-nano
271 |
272 | FCosts.Add(Ord(mtReasoning) * 1000 + 8, ['$15.00', '$120.00']); // gpt-5-pro
273 | end;
274 |
275 | destructor TModelCosts.Destroy;
276 | begin
277 | FCosts.Free;
278 | inherited;
279 | end;
280 |
281 | function TModelCosts.GetCost(ModelType: TModelType; Index: Integer): string;
282 | var
283 | CostValues: TArray;
284 | begin
285 | var Key := Ord(ModelType) * 1000 + Index;
286 | if FCosts.TryGetValue(Key, CostValues) then
287 | begin
288 | if Length(CostValues) = 2 then
289 | Result := Format(TOKEN_COST_PATTERN, [CostValues[0], CostValues[1]])
290 | else
291 | Result := 'Invalid Cost Format';
292 | end
293 | else
294 | Result := 'Unknown Cost';
295 | end;
296 |
297 | { TIntensityHelper }
298 |
299 | class function TIntensityHelper.AllIntensities: string;
300 | begin
301 | Result := String.Join(#10, Intensities);
302 | end;
303 |
304 | class function TIntensityHelper.Count: Integer;
305 | begin
306 | Result := Length(Intensities);
307 | end;
308 |
309 | class function TIntensityHelper.Default: TIntensity;
310 | begin
311 | Result := DefaultIntensity;
312 | end;
313 |
314 | class function TIntensityHelper.FromIndex(Index: Integer): TIntensity;
315 | begin
316 | if (Index >= 0) and (Index < Count) then
317 | Result := TIntensity(Index)
318 | else
319 | Result := Default;
320 | end;
321 |
322 | function TIntensityHelper.ToString: string;
323 | begin
324 | Result := Intensities[Self];
325 | end;
326 |
327 | { TSummaryHelper }
328 |
329 | class function TSummaryHelper.AllSummaries: string;
330 | begin
331 | Result := string.Join(#10, Summaries);
332 | end;
333 |
334 | class function TSummaryHelper.Count: Integer;
335 | begin
336 | Result := Length(Summaries);
337 | end;
338 |
339 | class function TSummaryHelper.Default: TSummary;
340 | begin
341 | Result := DefaultSummary;
342 | end;
343 |
344 | class function TSummaryHelper.FromIndex(Index: Integer): TSummary;
345 | begin
346 | if (Index >= 0) and (Index < Count) then
347 | Result := TSummary(Index)
348 | else
349 | Result := Default;
350 | end;
351 |
352 | function TSummaryHelper.ToString: string;
353 | begin
354 | Result := Summaries[Self];
355 | end;
356 |
357 | { TTimeOutHelper }
358 |
359 | class function TTimeOutHelper.AllTimeOuts: string;
360 | begin
361 | Result := string.Join(#10, TimeOuts);
362 | end;
363 |
364 | class function TTimeOutHelper.Count: Integer;
365 | begin
366 | Result := Length(TimeOuts);
367 | end;
368 |
369 | class function TTimeOutHelper.Default: TTimeOut;
370 | begin
371 | Result := DefaultTimeOut;
372 | end;
373 |
374 | class function TTimeOutHelper.FromIndex(Index: Integer): TTimeOut;
375 | begin
376 | if (Index >= 0) and (Index < Count) then
377 | Result := TTimeOut(Index)
378 | else
379 | Result := Default;
380 | end;
381 |
382 | class function TTimeOutHelper.TextToCardinal(const Text: string): Cardinal;
383 | begin
384 | var NormalizedText := Text.Trim.ToLower;
385 | for var index := ord(Low(TTimeOut)) to Ord(High(TTimeOut)) do
386 | if TimeOuts[TTimeOut(index)].Trim.ToLower = NormalizedText then
387 | Exit(TTimeOut(index).ToMilliseconds);
388 | raise Exception.CreateFmt('"%s" is not a correct timeout format', [Text]);
389 | end;
390 |
391 | function TTimeOutHelper.ToMilliseconds: Cardinal;
392 | begin
393 | Result := Timeoutms[Self];
394 | end;
395 |
396 | function TTimeOutHelper.ToString: string;
397 | begin
398 | Result := TimeOuts[Self];
399 | end;
400 |
401 | { TVerbosityHelper }
402 |
403 | class function TVerbosityHelper.AllVerbosities: string;
404 | begin
405 | Result := String.Join(#10, Verbosities);
406 | end;
407 |
408 | class function TVerbosityHelper.Count: Integer;
409 | begin
410 | Result := Length(Verbosities);
411 | end;
412 |
413 | class function TVerbosityHelper.Default: TVerbosity;
414 | begin
415 | Result := DefaultVerbosity;
416 | end;
417 |
418 | class function TVerbosityHelper.FromIndex(Index: Integer): TVerbosity;
419 | begin
420 | if (Index >= 0) and (Index < Count) then
421 | Result := TVerbosity(Index)
422 | else
423 | Result := Default;
424 | end;
425 |
426 | function TVerbosityHelper.ToString: string;
427 | begin
428 | Result := Verbosities[Self];
429 | end;
430 |
431 | end.
432 |
--------------------------------------------------------------------------------
/VCL/UI.ServiceFeatureSelector.VCL.pas:
--------------------------------------------------------------------------------
1 | unit UI.ServiceFeatureSelector.VCL;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : UI.ServiceFeatureSelector.VCL'}
6 |
7 | (*
8 | Unit: UI.ServiceFeatureSelector.VCL
9 |
10 | Purpose:
11 | This unit implements the interface and business logic for dynamically managing the activation,
12 | deactivation, and presentation of File2knowledgeAI's main search features within a modern
13 | Delphi VCL application.
14 | It connects the visual service bar buttons (web search, file search, reasoning, etc.) to the
15 | project’s internal mode logic.
16 | The goal: to centralize and standardize the handling, synchronization, and display of service
17 | states (Web Search, File Search, Reasoning) while providing immediate user feedback and
18 | a consistent UX.
19 |
20 | Note on Architecture and Design Choices:
21 | Following the project’s pragmatic tradition, this unit favors self-contained, straightforward,
22 | and readable code: UI event handling, service state management, and UI effects are grouped
23 | together for easy iteration, demos, and rapid onboarding.
24 | This approach increases clarity while allowing for extensibility (adding modes, new buttons, or
25 | custom styles).
26 | The design remains modular enough for future refactoring, should stricter architectural patterns
27 | (MVC/MVP/MVVM) become necessary.
28 | The use of record helpers for interfaces and centralization of captions/hints ensures scalability
29 | and possible multilingual support, with built-in testability.
30 |
31 | Usage:
32 | Instantiate the selector with your VCL buttons (TSpeedButton) and the main caption label.
33 | All event wiring, state management, and UI updates are handled automatically to enable
34 | one-click mode switching and immediate UX enhancement.
35 |
36 | Dependencies:
37 | - VCL controls: TSpeedButton, TLabel, etc.
38 | - Project units: Manager.Intf, Manager.IoC, Manager.Types, UI.Styles.VCL
39 | - Internal helpers for mode management, styles, and UI callbacks.
40 |
41 | File2knowledgeAI project context:
42 | This unit embodies the best practice of centralizing feature toggling and UX synchronization
43 | around the new v1/responses endpoint and the product’s multimodal capabilities.
44 | *)
45 |
46 | {$ENDREGION}
47 |
48 | uses
49 | Winapi.Windows, Winapi.Messages, Winapi.CommCtrl,
50 | System.SysUtils, System.Classes, System.Generics.Collections,
51 | Vcl.Graphics, Vcl.Controls, Vcl.Buttons, Vcl.StdCtrls,
52 | Manager.Intf, Manager.IoC, Manager.Types, UI.Styles.VCL;
53 |
54 | type
55 | TChainMode = (cmOpen, cmClose);
56 |
57 | TChainModeHelper = record Helper for TChainMode
58 | private
59 | const
60 | Icons: array[TChainMode] of string = (
61 | '', ''
62 | );
63 | Hints: array[TChainMode] of string = (
64 | 'Disable File_search tool F6',
65 | 'Enable File_search tool F6'
66 | );
67 | public
68 | function ToIcon: string;
69 | function ToHint: string;
70 | class function FromBoolean(Value: Boolean): TChainMode; static;
71 | end;
72 |
73 | TWebSearchHint = (wbEnable, wbDisable);
74 |
75 | TWebSearchHintHelper = record Helper for TWebSearchHint
76 | private
77 | const
78 | Hints: array[TWebSearchHint] of string = (
79 | 'Enable Web Search F5',
80 | 'Disable Web Search F5'
81 | );
82 | public
83 | function ToHint: string;
84 | class function FromBoolean(Value: Boolean): TWebSearchHint; static;
85 | end;
86 |
87 | TReasoningHint = (rEnable, rDisable);
88 |
89 | TReasoningHintHelper = record Helper for TReasoningHint
90 | private
91 | const
92 | Hints: array[TReasoningHint] of string = (
93 | 'Enable Reasoning'#10'File_search disable F7',
94 | 'Disable Reasoning'#10'File_search enable F7'
95 | );
96 | public
97 | function ToHint: string;
98 | class function FromBoolean(Value: Boolean): TReasoningHint; static;
99 | end;
100 |
101 | TMainCaptionType = (
102 | mcFileSearchActive,
103 | mcWebFileSearchActive,
104 | mcFileSearchDisabled,
105 | mcWebSearchActive,
106 | mcReasoningActive);
107 |
108 | TMainCaptionTypeHelper = record Helper for TMainCaptionType
109 | private
110 | const
111 | Labels: array[TMainCaptionType] of string = (
112 | 'File Search Only',
113 | 'Web && File Search',
114 | 'File Search Disabled',
115 | 'Web Search Only',
116 | 'Reasoning Mode (Web && File Search Disabled)'
117 | );
118 | public
119 | function ToString: string;
120 | class function FromFeatureModes(Value: TFeatureModes): TMainCaptionType; static;
121 | end;
122 |
123 |
124 | ///
125 | /// UI and logic orchestrator for File2knowledgeAI main feature toggles in a Delphi VCL application.
126 | ///
127 | ///
128 | /// TServiceFeatureSelector centralizes all event wiring, state management, and UI feedback related
129 | /// to toggling the core service modes—Web Search, File Search, and Reasoning.
130 | /// Through its constructor, you bind VCL controls (TSpeedButton and TLabel) for immediate
131 | /// synchronization of internal states with user-facing UI elements and captions.
132 | ///
133 | /// This class streamlines user experience by ensuring that visual control state, feature mode, and
134 | /// contextual hints/captions always remain coherent, and that switching one feature correctly
135 | /// updates the others as per the service logic.
136 | ///
137 | ///
138 | /// The pragmatic, consolidated design emphasizes demo-readiness, maintainability, and fast
139 | /// onboarding, while still allowing for later modularization if stricter architectural separation is
140 | /// needed.
141 | ///
142 | ///
143 | /// This implementation is core to File2knowledgeAI's best practice of keeping dynamic UX features
144 | /// (mode switching, feature availability, etc.) synchronized with business logic and endpoint capabilities.
145 | ///
146 | ///
147 | /// Button to activate/deactivate Web Search mode.
148 | /// Button to enable/disable File Search mode.
149 | /// Button to enable/disable Reasoning mode (which also disables
150 | /// Web Search).
151 | /// Label control for displaying the current main caption, reflecting
152 | /// combined feature state.
153 | ///
154 | TServiceFeatureSelector = class(TInterfacedObject, IServiceFeatureSelector)
155 | private
156 | FWebSearchButton: TSpeedButton;
157 | FDisableFileSearchButton: TSpeedButton;
158 | FReasoningButton: TSpeedButton;
159 | FCaptionLabel: TLabel;
160 | procedure SetWebSearchButton(const Value: TSpeedButton);
161 | procedure SetDisableFileSearchButton(const Value: TSpeedButton);
162 | procedure SetReasoningButton(const Value: TSpeedButton);
163 | procedure SetCaptionLabel(const Value: TLabel);
164 | function GetFeatureModes: TFeatureModes;
165 | procedure HandleWebSearchButtonClick(Sender: TObject);
166 | procedure HandleDisableFileSearchButtonClick(Sender: TObject);
167 | procedure HandleReasoningButtonClick(Sender: TObject);
168 | procedure HintAndCaptionUpdate;
169 | procedure HandleCaptionOnMouseEnter(Sender: TObject);
170 | procedure HandleCaptionOnMouseLeave(Sender: TObject);
171 | procedure HandleCaptionOnMouseUp(Sender: TObject; Button: TMouseButton;
172 | Shift: TShiftState; X, Y: Integer);
173 | procedure Activate(const Value: TSpeedButton);
174 | public
175 | constructor Create(const AWebSearchButton, ADisableFileSearchButton, AReasoningButton: TSpeedButton;
176 | const ACaptionLabel: TLabel);
177 |
178 | ///
179 | /// Programmatically toggles the Web Search mode, updating both internal state and UI accordingly.
180 | ///
181 | procedure SwitchWebSearch;
182 |
183 | ///
184 | /// Programmatically toggles the File Search feature disable mode, updating internal state and the UI.
185 | ///
186 | procedure SwitchDisableFileSearch;
187 |
188 | ///
189 | /// Programmatically toggles the Reasoning mode, enforcing the required disabling of Web Search and updating the UI.
190 | ///
191 | procedure SwitchReasoning;
192 |
193 | ///
194 | /// Gets the current combination of feature modes (Web Search, File Search Disabled, Reasoning)
195 | /// as reflected by the UI state of the corresponding buttons.
196 | ///
197 | property FeatureModes: TFeatureModes read GetFeatureModes;
198 | end;
199 |
200 | implementation
201 |
202 | { TServiceFeatureSelector }
203 |
204 | procedure TServiceFeatureSelector.Activate(const Value: TSpeedButton);
205 | begin
206 | Value.Down := not Value.Down;
207 | Value.Click;
208 | end;
209 |
210 | constructor TServiceFeatureSelector.Create(const AWebSearchButton,
211 | ADisableFileSearchButton, AReasoningButton: TSpeedButton;
212 | const ACaptionLabel: TLabel);
213 | begin
214 | inherited Create;
215 | SetWebSearchButton(AWebSearchButton);
216 | SetDisableFileSearchButton(ADisableFileSearchButton);
217 | SetReasoningButton(AReasoningButton);
218 | SetCaptionLabel(ACaptionLabel);
219 | HintAndCaptionUpdate;
220 | end;
221 |
222 | function TServiceFeatureSelector.GetFeatureModes: TFeatureModes;
223 | begin
224 | Result := [];
225 | if FWebSearchButton.Down then
226 | Result := Result + [sf_webSearch];
227 | if FDisableFileSearchButton.Down then
228 | Result := Result + [sf_fileSearchDisabled];
229 | if FReasoningButton.Down then
230 | Result := Result + [sf_reasoning];
231 | end;
232 |
233 | procedure TServiceFeatureSelector.HandleCaptionOnMouseEnter(Sender: TObject);
234 | begin
235 | FCaptionLabel.Font.Style := [fsBold,fsUnderline];
236 | FCaptionLabel.Cursor := crHandPoint;
237 | end;
238 |
239 | procedure TServiceFeatureSelector.HandleCaptionOnMouseLeave(Sender: TObject);
240 | begin
241 | FCaptionLabel.Font.Style := [fsBold];
242 | FCaptionLabel.Cursor := crDefault;
243 | end;
244 |
245 | procedure TServiceFeatureSelector.HandleCaptionOnMouseUp(Sender: TObject;
246 | Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
247 | begin
248 | Selector.ShowPage(psSettings);
249 | end;
250 |
251 | procedure TServiceFeatureSelector.HandleDisableFileSearchButtonClick(
252 | Sender: TObject);
253 | begin
254 | HintAndCaptionUpdate;
255 | end;
256 |
257 | procedure TServiceFeatureSelector.HandleReasoningButtonClick(Sender: TObject);
258 | begin
259 | FWebSearchButton.Down := False;
260 | HintAndCaptionUpdate;
261 | end;
262 |
263 | procedure TServiceFeatureSelector.HandleWebSearchButtonClick(Sender: TObject);
264 | begin
265 | if FReasoningButton.Down then
266 | FWebSearchButton.Down := False;
267 | HintAndCaptionUpdate;
268 | end;
269 |
270 | procedure TServiceFeatureSelector.HintAndCaptionUpdate;
271 | begin
272 | FWebSearchButton.Hint := TWebSearchHint.FromBoolean(FWebSearchButton.Down).ToHint;
273 | FDisableFileSearchButton.Hint := TChainMode.FromBoolean(FDisableFileSearchButton.Down).ToHint;
274 | FDisableFileSearchButton.Caption := TChainMode.FromBoolean(FDisableFileSearchButton.Down).ToIcon;
275 | FReasoningButton.Hint := TReasoningHint.FromBoolean(FReasoningButton.Down).ToHint;
276 | FCaptionLabel.Caption := TMainCaptionType.FromFeatureModes(FeatureModes).ToString;
277 | end;
278 |
279 | procedure TServiceFeatureSelector.SetCaptionLabel(const Value: TLabel);
280 | begin
281 | FCaptionLabel := Value;
282 | TAppStyle.ApplyCaptionLabelStyle(Value,
283 | procedure
284 | begin
285 | FCaptionLabel.OnMouseEnter := HandleCaptionOnMouseEnter;
286 | FCaptionLabel.OnMouseLeave := HandleCaptionOnMouseLeave;
287 | FCaptionLabel.OnMouseUp := HandleCaptionOnMouseUp;
288 | end);
289 | end;
290 |
291 | procedure TServiceFeatureSelector.SetDisableFileSearchButton(
292 | const Value: TSpeedButton);
293 | begin
294 | FDisableFileSearchButton := Value;
295 | TAppStyle.ApplyDisableFileSearchButtonStyle(Value,
296 | procedure
297 | begin
298 | FDisableFileSearchButton.OnClick := HandleDisableFileSearchButtonClick;
299 | end);
300 | end;
301 |
302 | procedure TServiceFeatureSelector.SetReasoningButton(const Value: TSpeedButton);
303 | begin
304 | FReasoningButton := Value;
305 | TAppStyle.ApplyReasoningButtonStyle(Value,
306 | procedure
307 | begin
308 | FReasoningButton.OnClick := HandleReasoningButtonClick;
309 | end)
310 | end;
311 |
312 | procedure TServiceFeatureSelector.SetWebSearchButton(const Value: TSpeedButton);
313 | begin
314 | FWebSearchButton := Value;
315 | TAppStyle.ApplyWebSearchButtonStyle(Value,
316 | procedure
317 | begin
318 | FWebSearchButton.OnClick := HandleWebSearchButtonClick;
319 | end);
320 | end;
321 |
322 | procedure TServiceFeatureSelector.SwitchDisableFileSearch;
323 | begin
324 | Activate(FDisableFileSearchButton);
325 | end;
326 |
327 | procedure TServiceFeatureSelector.SwitchReasoning;
328 | begin
329 | Activate(FReasoningButton);
330 | end;
331 |
332 | procedure TServiceFeatureSelector.SwitchWebSearch;
333 | begin
334 | Activate(FWebSearchButton);
335 | end;
336 |
337 | { TChainModeHelper }
338 |
339 | class function TChainModeHelper.FromBoolean(Value: Boolean): TChainMode;
340 | begin
341 | Result := TChainMode(Ord(Value));
342 | end;
343 |
344 | function TChainModeHelper.ToHint: string;
345 | begin
346 | Result := Hints[Self];
347 | end;
348 |
349 | function TChainModeHelper.ToIcon: string;
350 | begin
351 | Result := Icons[Self];
352 | end;
353 |
354 | { TWebSearchHintHelper }
355 |
356 | class function TWebSearchHintHelper.FromBoolean(Value: Boolean): TWebSearchHint;
357 | begin
358 | Result := TWebSearchHint(Ord(Value));
359 | end;
360 |
361 | function TWebSearchHintHelper.ToHint: string;
362 | begin
363 | Result := Hints[Self];
364 | end;
365 |
366 | { TReasoningHintHelper }
367 |
368 | class function TReasoningHintHelper.FromBoolean(Value: Boolean): TReasoningHint;
369 | begin
370 | Result := TReasoningHint(Ord(Value));
371 | end;
372 |
373 | function TReasoningHintHelper.ToHint: string;
374 | begin
375 | Result := Hints[Self];
376 | end;
377 |
378 | { TMainCaptionTypeHelper }
379 |
380 | class function TMainCaptionTypeHelper.FromFeatureModes(
381 | Value: TFeatureModes): TMainCaptionType;
382 | begin
383 | Result := TMainCaptionType(PByte(@Value)^);
384 | end;
385 |
386 | function TMainCaptionTypeHelper.ToString: string;
387 | begin
388 | Result := Labels[Self];
389 | end;
390 |
391 | end.
392 |
--------------------------------------------------------------------------------
/VCL/UI.Container.VCL.pas:
--------------------------------------------------------------------------------
1 | unit UI.Container.VCL;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : UI.Container.VCL'}
6 |
7 | (*
8 | Note on the Implementation of the Internal Registry in TContainer
9 |
10 | The TContainer class, which inherits from TPanel, includes an internal registry (FRegistry) listing all
11 | its active instances. This approach addresses the need for simple and efficient tracking of decorative
12 | panels added to a TScrollBox, without requiring an external, heavier management system that would be
13 | unsuitable for the intended purpose of the class.
14 |
15 | It's true that this solution departs from strict separation of concerns: the instance tracking logic is
16 | embedded directly in the purely visual component. However, this compromise is fully intentional:
17 | TContainer is intended only to decorate a TScrollBox—it should not handle anything beyond its sole and
18 | specific visual purpose, managed in a straightforward and self-contained way.
19 | Adding an external manager would unnecessarily complicate things for this particular use case.
20 |
21 | Simple integration and usage
22 | Lightweight in terms of memory and code management
23 | Responsibility limited to what is strictly necessary
24 | This remains an area open for improvement: in a production solution or different context, it could make
25 | sense to isolate this logic using a dedicated manager or registry, in line with stricter architectural
26 | principles.
27 | Nevertheless, this implementation is outside the scope of the current application, which focuses
28 | primarily on demonstrating best practices regarding the use of the v1/responses endpoint: clarity,
29 | simplicity, and practical effectiveness in the example.
30 | *)
31 |
32 | {$ENDREGION}
33 |
34 | uses
35 | Winapi.Windows, Winapi.Messages, Winapi.ShellAPI,
36 | System.SysUtils, System.Classes, System.Generics.Collections, System.Types,
37 | Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Graphics, Vcl.Buttons, Vcl.Menus, Vcl.Forms, Vcl.Dialogs,
38 | Helper.PanelRoundedCorners.VCL, UI.Styles.VCL, Manager.WebServices, Manager.Intf, Manager.Types;
39 |
40 | type
41 | TContainer = class;
42 | TContainerRegistry = TObjectList;
43 |
44 | TContainer = class(TPanel)
45 | strict private
46 |
47 | {--- shared registry; lifetime handled by class }
48 | class var FRegistry: TContainerRegistry;
49 | class constructor Create;
50 | class destructor Destroy;
51 |
52 | private
53 | FThumbnail: TImage;
54 | FLabel: TLabel;
55 | FContextPanel: TPanel;
56 | FPopupMenu: TPopupMenu;
57 | FIndex: Integer;
58 | FSelected: Boolean;
59 | FSelectionProc: TProc;
60 | FGitHubUrl: string;
61 | FGetitUrl: string;
62 |
63 | procedure CreatePopupMenu;
64 | procedure CreateThumbnail;
65 | procedure CreateLabel;
66 | procedure CreatePanelButton;
67 |
68 | procedure SetCommonEvents(Component: TControl);
69 |
70 | {--- general events Thumbnail, Label }
71 | procedure HandleMouseEnter(Sender: TObject);
72 | procedure HandleMouseLeave(Sender: TObject);
73 | procedure HandleMouseDown(Sender: TObject; Button: TMouseButton;
74 | Shift: TShiftState; X, Y: Integer); virtual;
75 |
76 | {--- special PanelButton events }
77 | procedure HandleContextMouseEnter(Sender: TObject);
78 | procedure HandleContextMouseLeave(Sender: TObject);
79 | procedure HandleContextMouseDown(Sender: TObject; Button: TMouseButton;
80 | Shift: TShiftState; X, Y: Integer);
81 |
82 | {--- Popup menu events }
83 | procedure HandlePopupMenuPopup(Sender: TObject);
84 | procedure HandlePopupMenuClose(Sender: TObject);
85 | procedure HandleGitHubClick(Sender: TObject);
86 | procedure HandleGetitClick(Sender: TObject);
87 | procedure HandlePopupMenuModify(Sender: TObject);
88 |
89 | {--- main getters }
90 | function GetDisplayName: string;
91 | function GetDescription: string;
92 | function GetSelected: Boolean;
93 | function GetGitHubUrl: string;
94 | function GetGetitUrl: string;
95 |
96 | {--- Add popup menu item }
97 | procedure AddPopupItem(const ACaption, AShortCut: string;
98 | AOnClick: TNotifyEvent);
99 | public
100 | constructor Create(AOwner: TComponent); override;
101 | destructor Destroy; override;
102 |
103 | function ApplyImage(const AStream: TStream): TContainer;
104 | function ApplyName(const AValue: string): TContainer; reintroduce;
105 | function ApplyDescription(const AValue: string): TContainer;
106 | function ApplyTop(const AValue: Integer): TContainer;
107 | function ApplyIndex(const AValue: Integer): TContainer;
108 | function ApplySelected(const AValue: Boolean): TContainer;
109 | function ApplyGitHubUrl(const AValue: string): TContainer;
110 | function ApplyGetitUrl(const AValue: string): TContainer;
111 | function OnSelect(AProc: TProc): TContainer;
112 |
113 | property DisplayName: string read GetDisplayName;
114 | property Description: string read GetDescription;
115 | property Index: Integer read FIndex;
116 | property Selected: Boolean read GetSelected;
117 | property GitHubUrl: string read GetGitHubUrl;
118 | property GetitUrl: string read GetGetitUrl;
119 |
120 | class property Items: TContainerRegistry read FRegistry;
121 | class function ContainerList: TContainerRegistry;
122 | class procedure ContainerSelect(const Value: Integer);
123 | class procedure Select(const AIndex: Integer);
124 | class procedure Unselect(const AIndex: Integer = -1);
125 | end;
126 |
127 | function GetTopPosition(const Index: Integer): Integer;
128 |
129 | implementation
130 |
131 | function GetTopPosition(const Index: Integer): Integer;
132 | begin
133 | Result := 20 + 66 * Index;
134 | end;
135 |
136 | class constructor TContainer.Create;
137 | begin
138 | {--- Single registry for all instances – list does NOT own the items }
139 | FRegistry := TContainerRegistry.Create(False);
140 | end;
141 |
142 | class destructor TContainer.Destroy;
143 | begin
144 | FreeAndNil(FRegistry);
145 | end;
146 |
147 | class function TContainer.ContainerList: TContainerRegistry;
148 | begin
149 | Result := FRegistry;
150 | end;
151 |
152 | class procedure TContainer.ContainerSelect(const Value: Integer);
153 | begin
154 | if FRegistry.Count > 0 then
155 | FRegistry[0].Select(Value);
156 | end;
157 |
158 | constructor TContainer.Create(AOwner: TComponent);
159 | begin
160 | Assert(AOwner is TWinControl, 'AOwner must be a TWinControl');
161 | inherited Create(AOwner);
162 |
163 | {--- foundation panel }
164 | Parent := TWinControl(AOwner);
165 | TAppStyle.ApplyContainerCorePanelStyle(Self,
166 | procedure
167 | begin
168 | Width := 320;
169 | Height := 58;
170 | SetCommonEvents(Self);
171 |
172 | {--- PopupMenu
173 | NOTE: The context menu should be instantiated only once, not for each panel. }
174 | CreatePopupMenu;
175 |
176 | {--- Hosted controls }
177 | CreateThumbnail;
178 | CreateLabel;
179 | CreatePanelButton;
180 | end);
181 |
182 | {--- Register instance }
183 | FRegistry.Add(Self);
184 | end;
185 |
186 | procedure TContainer.CreatePopupMenu;
187 | begin
188 | FPopupMenu := TPopupMenu.Create(Application);
189 | FPopupMenu.OnPopup := HandlePopupMenuPopup;
190 | FPopupMenu.OnClose := HandlePopupMenuClose;
191 | AddPopupItem('GitHub', '', HandleGitHubClick);
192 | AddPopupItem('Getit', '', HandleGetitClick);
193 | AddPopupItem('Modify', '', HandlePopupMenuModify);
194 | end;
195 |
196 | destructor TContainer.Destroy;
197 | begin
198 | FRegistry.Remove(Self);
199 | inherited;
200 | end;
201 |
202 | procedure TContainer.AddPopupItem(const ACaption, AShortCut: string;
203 | AOnClick: TNotifyEvent);
204 | begin
205 | var Item := TMenuItem.Create(FPopupMenu);
206 | Item.Caption := ACaption;
207 | Item.ShortCut := TextToShortCut(AShortCut);
208 | Item.OnClick := AOnClick;
209 | FPopupMenu.Items.Add(Item);
210 | end;
211 |
212 | procedure TContainer.CreateThumbnail;
213 | begin
214 | {--- The image container }
215 | var LPanel := TPanel.Create(Self);
216 | LPanel.Parent := Self;
217 | TAppStyle.ApplyContainerBackgroundPanelStyle(LPanel,
218 | procedure
219 | begin
220 | LPanel.SetBounds(8, 4, 50, 50);
221 | end);
222 |
223 | {--- The image with the rounded edges }
224 | FThumbnail := TImage.Create(LPanel);
225 | FThumbnail.Parent := LPanel;
226 | TAppStyle.ApplyContainerImageStyle(FThumbnail,
227 | procedure
228 | begin
229 | SetCommonEvents(FThumbnail);
230 | end);
231 | end;
232 |
233 | procedure TContainer.CreateLabel;
234 | begin
235 | FLabel := TLabel.Create(Self);
236 | FLabel.Parent := Self;
237 | TAppStyle.ApplyContainerLabelStyle(FLabel,
238 | procedure
239 | begin
240 | FLabel.SetBounds(70, 14, 150, 24);
241 | FLabel.Transparent := True;
242 | SetCommonEvents(FLabel);
243 | end);
244 | end;
245 |
246 | procedure TContainer.HandleMouseEnter(Sender: TObject);
247 | begin
248 | Color := TAppStyle.ApplyContainerMouseEnterColor;
249 | FLabel.Font.Color := clBlack;
250 | end;
251 |
252 | procedure TContainer.HandleMouseLeave(Sender: TObject);
253 | begin
254 | if not FSelected then
255 | begin
256 | Color := TAppStyle.ApplyContainerMouseLeaveColor;
257 | FLabel.Font.Color := clWhite;
258 | end;
259 | end;
260 |
261 | procedure TContainer.HandlePopupMenuClose(Sender: TObject);
262 | begin
263 |
264 | end;
265 |
266 | procedure TContainer.HandlePopupMenuModify(Sender: TObject);
267 | begin
268 | Selector.ShowPage(psVectorFile);
269 | end;
270 |
271 | procedure TContainer.HandlePopupMenuPopup(Sender: TObject);
272 | begin
273 | if FGitHubUrl.Trim.IsEmpty then
274 | FPopupMenu.Items[0].Enabled := False;
275 |
276 | if FGetitUrl.Trim.IsEmpty then
277 | FPopupMenu.Items[1].Enabled := False;
278 | end;
279 |
280 | procedure TContainer.HandleContextMouseDown(Sender: TObject;
281 | Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
282 | begin
283 | var P := ClientToScreen(Point(Left + Width + 18, Height div 2));
284 | FPopupMenu.Popup(P.X, P.Y);
285 | end;
286 |
287 | procedure TContainer.HandleContextMouseEnter(Sender: TObject);
288 | begin
289 | FContextPanel.Font.Color := clWhite;
290 | end;
291 |
292 | procedure TContainer.HandleContextMouseLeave(Sender: TObject);
293 | begin
294 | FContextPanel.Font.Color := clBlack;
295 | end;
296 |
297 | procedure TContainer.HandleGetitClick(Sender: TObject);
298 | begin
299 | TWebUrlManager.Open(FGetitUrl);
300 | end;
301 |
302 | procedure TContainer.HandleGitHubClick(Sender: TObject);
303 | begin
304 | TWebUrlManager.Open(FGitHubUrl);
305 | end;
306 |
307 | procedure TContainer.HandleMouseDown(Sender: TObject; Button: TMouseButton;
308 | Shift: TShiftState; X, Y: Integer);
309 | begin
310 | if Assigned(FSelectionProc) then
311 | FSelectionProc(Self);
312 | FContextPanel.Visible := FSelected;
313 | end;
314 |
315 | function TContainer.GetDescription: string;
316 | begin
317 | Result := FThumbnail.Hint;
318 | end;
319 |
320 | function TContainer.GetDisplayName: string;
321 | begin
322 | Result := FLabel.Caption;
323 | end;
324 |
325 | function TContainer.GetGetitUrl: string;
326 | begin
327 | Result := FGetitUrl;
328 | end;
329 |
330 | function TContainer.GetGitHubUrl: string;
331 | begin
332 | Result := FGitHubUrl;
333 | end;
334 |
335 | function TContainer.GetSelected: Boolean;
336 | begin
337 | Result := FSelected;
338 | end;
339 |
340 | class procedure TContainer.Select(const AIndex: Integer);
341 | begin
342 | Unselect;
343 |
344 | if (AIndex >= 0) and (AIndex < FRegistry.Count) then
345 | FRegistry[AIndex].ApplySelected(True);
346 | end;
347 |
348 | procedure TContainer.CreatePanelButton;
349 | begin
350 | FContextPanel := TPanel.Create(Self);
351 | FContextPanel.Parent := Self;
352 | TAppStyle.ApplyContainerPanelStyle(FContextPanel,
353 | procedure
354 | begin
355 | FContextPanel.PopupMenu := FPopupMenu;
356 | FContextPanel.SetBounds(Width - 24, 8, 18, 36);
357 | FContextPanel.Tag := 1;
358 | SetCommonEvents(FContextPanel);
359 | end);
360 |
361 | end;
362 |
363 | procedure TContainer.SetCommonEvents(Component: TControl);
364 | begin
365 | if not Assigned(Component) then
366 | Exit;
367 |
368 | if Component is TLabel then
369 | begin
370 | TLabel(Component).OnMouseEnter := HandleMouseEnter;
371 | TLabel(Component).OnMouseLeave := HandleMouseLeave;
372 | TLabel(Component).OnMouseDown := HandleMouseDown;
373 | end
374 | else
375 | if Component is TImage then
376 | begin
377 | TImage(Component).OnMouseEnter := HandleMouseEnter;
378 | TImage(Component).OnMouseLeave := HandleMouseLeave;
379 | TImage(Component).OnMouseDown := HandleMouseDown;
380 | end
381 | else
382 | if Component is TPanel then
383 | begin
384 | if Component.Tag = 1 then
385 | begin
386 | TPanel(Component).OnMouseEnter := HandleContextMouseEnter;
387 | TPanel(Component).OnMouseLeave := HandleContextMouseLeave;
388 | TPanel(Component).OnMouseDown := HandleContextMouseDown;
389 | end
390 | else
391 | begin
392 | TPanel(Component).OnMouseEnter := HandleMouseEnter;
393 | TPanel(Component).OnMouseLeave := HandleMouseLeave;
394 | TPanel(Component).OnMouseDown := HandleMouseDown;
395 | end;
396 | end
397 | end;
398 |
399 | class procedure TContainer.Unselect(const AIndex: Integer);
400 | begin
401 | if AIndex = -1 then
402 | begin
403 | for var Item in FRegistry do
404 | Item.ApplySelected(False);
405 | end
406 | else
407 | begin
408 | if (AIndex >= 0) and (AIndex < FRegistry.Count) then
409 | FRegistry[AIndex].ApplySelected(False);
410 | end;
411 | end;
412 |
413 | function TContainer.ApplyDescription(const AValue: string): TContainer;
414 | begin
415 | FThumbnail.Hint := AValue;
416 | Result := Self;
417 | end;
418 |
419 | function TContainer.ApplyGetitUrl(const AValue: string): TContainer;
420 | begin
421 | FGetitUrl := AValue;
422 | Result := Self;
423 | end;
424 |
425 | function TContainer.ApplyGitHubUrl(const AValue: string): TContainer;
426 | begin
427 | FGitHubUrl := AValue;
428 | Result := Self;
429 | end;
430 |
431 | function TContainer.ApplyImage(const AStream: TStream): TContainer;
432 | begin
433 | if Assigned(AStream) then
434 | try
435 | FThumbnail.Picture.LoadFromStream(AStream);
436 | finally
437 | AStream.Free;
438 | end;
439 | Result := Self;
440 | end;
441 |
442 | function TContainer.ApplyIndex(const AValue: Integer): TContainer;
443 | begin
444 | FIndex := AValue;
445 | Result := Self;
446 | end;
447 |
448 | function TContainer.ApplyName(const AValue: string): TContainer;
449 | begin
450 | FLabel.Caption := AValue;
451 | Result := Self;
452 | end;
453 |
454 | function TContainer.ApplySelected(const AValue: Boolean): TContainer;
455 | begin
456 | FSelected := AValue;
457 |
458 | if AValue then
459 | begin
460 | Color := TAppStyle.ApplyContainerMouseEnterColor;
461 | FLabel.Font.Color := TAppStyle.ApplyContainerFontSelectedColor;
462 | FContextPanel.Visible := True;
463 | end
464 | else
465 | begin
466 | Color := TAppStyle.ApplyContainerMouseLeaveColor;
467 | FLabel.Font.Color := TAppStyle.ApplyContainerFontUnSelectedColor;
468 | FContextPanel.Visible := False;
469 | end;
470 |
471 | Result := Self;
472 | end;
473 |
474 | function TContainer.ApplyTop(const AValue: Integer): TContainer;
475 | begin
476 | Top := AValue;
477 | Result := Self;
478 | end;
479 |
480 | function TContainer.OnSelect(AProc: TProc): TContainer;
481 | begin
482 | FSelectionProc := AProc;
483 | Result := Self;
484 | end;
485 |
486 | end.
487 |
--------------------------------------------------------------------------------
/providers/Provider.OpenAI.VectorStore.pas:
--------------------------------------------------------------------------------
1 | unit Provider.OpenAI.VectorStore;
2 |
3 | interface
4 |
5 | {$REGION 'Dev notes : Provider.OpenAI.VectorStore'}
6 |
7 | (*
8 | Unit: Provider.OpenAI.VectorStore
9 |
10 | Purpose:
11 | Manage creation, retrieval, and deletion of vector stores
12 | and their associated files via the GenAI client.
13 |
14 | Context & Architecture:
15 | - Encapsulates all access to the OpenAI VectorStore and VectorStoreFiles services.
16 | - Provides methods to:
17 | • Create a vector store or link a file to a vector store.
18 | • Check for existence of a vector store or linked file.
19 | • Delete a vector store or remove a file from a store.
20 | - Uses TPromise for orchestrating async calls and explicitly handles
21 | 404 errors (not found) without throwing uncaught exceptions.
22 | - Error handling delegates to AlertService for centralized user feedback.
23 |
24 | Key Points:
25 | - Ensure* methods verify presence and create the resource if needed.
26 | - Retrieve* methods perform fine-grained lookup with non-blocking error handling.
27 | - Promises resolve to an empty string when an item is missing, allowing flow continuity.
28 | - Adheres to SRP: this manager focuses solely on vector access and delegates UI/alerting to AlertService.
29 |
30 | External Dependencies:
31 | - GenAI (IGenAI)
32 | - GenAI.Types
33 | - GenAI.Async.Promise
34 | - Manager.Intf (IVectorStoreManager)
35 |
36 | Usage:
37 | Resolve via IoC (singleton) and invoke Ensure*, Retrieve*, Create*, or Delete*
38 | methods as appropriate. No direct API calls should occur outside this manager.
39 | *)
40 |
41 | {$ENDREGION}
42 |
43 | uses
44 | System.SysUtils, GenAI, GenAI.Types, Manager.Intf, GenAI.Async.Promise;
45 |
46 | type
47 | ///
48 | /// Manages the lifecycle and file associations of vector stores in the OpenAI backend.
49 | ///
50 | ///
51 | /// Encapsulates creation, retrieval, linking, and deletion of vector stores and their associated files
52 | /// via the GenAI client. Uses promise-based asynchronous calls to handle API interactions, converting
53 | /// 404 (not found) errors into empty results for callers to decide on resource creation.
54 | /// Errors other than 404 are forwarded to for centralized reporting.
55 | /// Designed as a singleton service resolved through dependency injection, adhering to SOLID principles
56 | /// for modularity and testability.
57 | ///
58 | TVectorStoreManager = class(TInterfacedObject, IVectorStoreManager)
59 | private
60 | FClient: IGenAI;
61 | procedure HandleError(E: Exception);
62 | function HandleThenVectorStore(Value: TVectorStore): string;
63 | function HandleThenVectorStoreFile(Value: TVectorStoreFile): string;
64 | function HandleThenDeleteVectorStoreFile(Value: TDeletion): string;
65 | public
66 | {--- Vector store }
67 |
68 | ///
69 | /// Asynchronously retrieves the ID of the specified vector store.
70 | ///
71 | ///
72 | /// The ID of the vector store to look up. If this string is empty, the method will
73 | /// still resolve gracefully without creating a new store.
74 | ///
75 | ///
76 | /// A TPromise<string> which resolves to the existing vector store ID,
77 | /// or to an empty string if the store does not exist (including 404 errors) or if
78 | /// the input value is empty.
79 | ///
80 | ///
81 | /// Uses a custom promise wrapper to intercept 404 errors and convert them into
82 | /// a successful resolution with an empty string, allowing callers to decide
83 | /// whether to create a new vector store.
84 | ///
85 | function RetrieveVectorStoreId(const Value: string): TPromise;
86 |
87 | ///
88 | /// Creates a new vector store in the OpenAI backend.
89 | ///
90 | ///
91 | /// A TPromise<string> that resolves to the identifier of the newly created
92 | /// vector store.
93 | ///
94 | ///
95 | /// Invokes the GenAI client's vector store creation API with default parameters.
96 | /// Any errors are caught and forwarded to for centralized handling.
97 | ///
98 | function CreateVectorStore: TPromise;
99 |
100 | ///
101 | /// Ensures that a valid vector store exists for the given identifier.
102 | ///
103 | ///
104 | /// The identifier of an existing vector store.
105 | /// If this string is empty or does not correspond to an existing store, a new one will be created.
106 | ///
107 | ///
108 | /// A TPromise<string> which resolves to the identifier of the confirmed or newly created vector store.
109 | ///
110 | ///
111 | /// - If is empty, invokes immediately.
112 | /// - Otherwise, attempts to retrieve the store; on 404 (not found) or empty result, falls back to creation.
113 | /// - Any other errors are propagated to for centralized handling.
114 | ///
115 | function EnsureVectorStoreId(const VectorStoreId: string): TPromise;
116 |
117 | {--- Vector store file }
118 |
119 | ///
120 | /// Asynchronously retrieves the linkage ID for a file within a specific vector store.
121 | ///
122 | ///
123 | /// The identifier of the vector store to query.
124 | ///
125 | ///
126 | /// The identifier of the file whose association you want to retrieve.
127 | ///
128 | ///
129 | /// A TPromise<string> that resolves to the vector‐store‐file association ID
130 | /// if found, or to an empty string if the association does not exist (including 404 errors).
131 | ///
132 | ///
133 | ///
134 | /// - Uses a custom promise wrapper to catch 404 (not found) errors and convert them into
135 | /// a successful resolution with an empty string, enabling callers to decide whether
136 | /// to create the association.
137 | ///
138 | ///
139 | /// - Other errors are propagated to for centralized handling.
140 | ///
141 | ///
142 | function RetrieveVectorStoreFileId(const VectorStoreId: string; const FileId: string): TPromise;
143 |
144 | ///
145 | /// Creates a new file association within the specified vector store.
146 | ///
147 | ///
148 | /// The identifier of the vector store to which the file will be linked.
149 | ///
150 | ///
151 | /// The identifier of the file to link into the vector store.
152 | ///
153 | ///
154 | /// A TPromise<string> that resolves to the identifier of the newly created
155 | /// vector‐store‐file link.
156 | ///
157 | ///
158 | ///
159 | /// - Invokes the GenAI client's VectorStoreFiles create API for the given store and file IDs.
160 | ///
161 | ///
162 | /// - On success, extracts and returns the association ID via .
163 | ///
164 | ///
165 | /// - Any errors (other than 404) are forwarded to for centralized handling.
166 | ///
167 | ///
168 | function CreateVectorStoreFile(const VectorStoreId: string; const FileId: string): TPromise;
169 |
170 | ///
171 | /// Ensures that a link between the specified file and vector store exists.
172 | ///
173 | ///
174 | /// The identifier of the target vector store.
175 | ///
176 | ///
177 | /// The identifier of the file to associate with the vector store.
178 | ///
179 | ///
180 | /// A TPromise<string> which resolves to:
181 | ///
182 | /// - the existing association ID if the link is already present, or
183 | ///
184 | ///
185 | /// - a newly created association ID if the link was missing.
186 | ///
187 | ///
188 | ///
189 | ///
190 | /// - First calls to check for an existing link.
191 | ///
192 | ///
193 | /// - If no association is found (empty result or 404), invokes .
194 | ///
195 | ///
196 | /// - Errors other than 404 are propagated to for centralized handling.
197 | ///
198 | ///
199 | function EnsureVectorStoreFileId(const VectorStoreId, FileId: string): TPromise;
200 |
201 | ///
202 | /// Asynchronously deletes the association between a file and the specified vector store.
203 | ///
204 | ///
205 | /// The identifier of the vector store from which the file association will be removed.
206 | ///
207 | ///
208 | /// The identifier of the file to unlink from the vector store.
209 | ///
210 | ///
211 | /// A TPromise<string> that resolves to a confirmation message (typically "deleted")
212 | /// when the association has been removed successfully.
213 | ///
214 | ///
215 | ///
216 | /// - Invokes the GenAI client's VectorStoreFiles delete API via AsyncAwaitDelete.
217 | ///
218 | ///
219 | /// - On success, the association ID is passed to
220 | /// to format the confirmation message.
221 | ///
222 | ///
223 | /// - Any exceptions (other than 404) are caught and forwarded to
224 | /// for centralized error reporting.
225 | ///
226 | ///
227 | function DeleteVectorStoreFile(const VectorStoreId, FileId: string): TPromise;
228 |
229 | ///
230 | /// Asynchronously deletes the specified vector store from the OpenAI backend.
231 | ///
232 | ///
233 | /// The identifier of the vector store to remove.
234 | ///
235 | ///
236 | /// A TPromise<string> that resolves to a confirmation message (typically "deleted")
237 | /// when the vector store has been removed successfully.
238 | ///
239 | ///
240 | ///
241 | /// - Invokes the GenAI client's VectorStore delete API via AsyncAwaitDelete.
242 | ///
243 | ///
244 | /// - On success, matches the deleted store ID and returns "deleted".
245 | ///
246 | ///
247 | /// - Any exceptions (other than 404) are caught and forwarded to
248 | /// for centralized error reporting.
249 | ///
250 | ///
251 | function DeleteVectorStore(const VectorStoreId: string): TPromise;
252 |
253 | constructor Create(const GenAIClient: IGenAI);
254 | end;
255 |
256 | implementation
257 |
258 | { TVectorStoreManager }
259 |
260 | constructor TVectorStoreManager.Create(const GenAIClient: IGenAI);
261 | begin
262 | inherited Create;
263 | FClient := GenAIClient;
264 | end;
265 |
266 | function TVectorStoreManager.CreateVectorStore: TPromise;
267 | begin
268 | Result := FClient.VectorStore
269 | .AsyncAwaitCreate(
270 | procedure (Params: TVectorStoreCreateParams)
271 | begin
272 | Params.Name('Helper for wrapper Assistant');
273 | end)
274 | .&Then(HandleThenVectorStore)
275 | .&Catch(HandleError);
276 | end;
277 |
278 | function TVectorStoreManager.CreateVectorStoreFile(const VectorStoreId,
279 | FileId: string): TPromise;
280 | begin
281 | Result := FClient.VectorStoreFiles
282 | .AsyncAwaitCreate(
283 | VectorStoreId,
284 | procedure (Params: TVectorStoreFilesCreateParams) begin
285 | Params.FileId(FileId);
286 | end)
287 | .&Then(HandleThenVectorStoreFile)
288 | .&Catch(HandleError);
289 | end;
290 |
291 | function TVectorStoreManager.DeleteVectorStore(
292 | const VectorStoreId: string): TPromise;
293 | begin
294 | Result := FClient.VectorStore
295 | .AsyncAwaitDelete(VectorStoreId)
296 | .&Then(
297 | function (Value: TDeletion): string
298 | begin
299 | if VectorStoreId = Value.Id then
300 | Result := 'deleted';
301 | end)
302 | .&Catch(HandleError);
303 | end;
304 |
305 | function TVectorStoreManager.DeleteVectorStoreFile(const VectorStoreId,
306 | FileId: string): TPromise;
307 | begin
308 | Result := FClient.VectorStoreFiles
309 | .AsyncAwaitDelete(VectorStoreId, FileId)
310 | .&Then(HandleThenDeleteVectorStoreFile)
311 | .&Catch(HandleError);
312 | end;
313 |
314 | function TVectorStoreManager.EnsureVectorStoreFileId(const VectorStoreId,
315 | FileId: string): TPromise;
316 | begin
317 | {--- Ensure the presence of the vectorStoreId and the FileId in the vector store file. }
318 | Result := RetrieveVectorStoreFileId(VectorStoreId, FileId)
319 | .&Then(
320 | function (Value: string): TPromise
321 | begin
322 | if Value.Trim.IsEmpty then
323 | Result := CreateVectorStoreFile(VectorStoreId, FileId)
324 | else
325 | {--- The Id exists, so do nothing }
326 | Result := TPromise.Resolved('exists');
327 | end)
328 | end;
329 |
330 | function TVectorStoreManager.EnsureVectorStoreId(
331 | const VectorStoreId: string): TPromise;
332 | begin
333 | if VectorStoreId.Trim.IsEmpty then
334 | Result := CreateVectorStore
335 | else
336 | {--- Ensure the presence of the Id in the vector store. }
337 | Result := RetrieveVectorStoreId(VectorStoreId)
338 | .&Then(
339 | function (Value: string): TPromise
340 | begin
341 | {--- The Id does not exist. Create the Id and obtain its ID. }
342 | if Value.Trim.IsEmpty then
343 | Result := CreateVectorStore
344 | else
345 | {--- The Id exists, so do nothing }
346 | Result := TPromise.Resolved(Value);
347 | end);
348 | end;
349 |
350 | procedure TVectorStoreManager.HandleError(E: Exception);
351 | begin
352 | AlertService.ShowError(E.Message);
353 | end;
354 |
355 | function TVectorStoreManager.HandleThenDeleteVectorStoreFile(
356 | Value: TDeletion): string;
357 | begin
358 | Result := 'deleted';
359 | end;
360 |
361 | function TVectorStoreManager.HandleThenVectorStore(Value: TVectorStore): string;
362 | begin
363 | Result := Value.Id;
364 | end;
365 |
366 | function TVectorStoreManager.HandleThenVectorStoreFile(
367 | Value: TVectorStoreFile): string;
368 | begin
369 | Result := Value.Id;
370 | end;
371 |
372 | function TVectorStoreManager.RetrieveVectorStoreFileId(const VectorStoreId,
373 | FileId: string): TPromise;
374 | {$REGION 'Dev notes'}
375 | (* Notes :
376 | 1. We are not using the AsyncAwaitRetrieve method here because we want to define an
377 | onError handler that allows the promise to be resolved explicitly.
378 | *)
379 | {$ENDREGION}
380 | begin
381 | Result := TPromise.Create(
382 | procedure(Resolve: TProc; Reject: TProc)
383 | begin
384 | FClient.VectorStoreFiles.AsynRetrieve(
385 | VectorStoreId,
386 | FileId,
387 | function : TAsynVectorStoreFile
388 | begin
389 | Result.OnSuccess :=
390 | procedure (Sender: TObject; VectorStoreFile: TVectorStoreFile)
391 | begin
392 | Resolve(VectorStoreFile.Id);
393 | end;
394 |
395 | Result.OnError :=
396 | procedure (Sender: TObject; Error: string)
397 | begin
398 | {$REGION 'Dev notes'}
399 | (* 1. The promise is still resolved, even if no ID is provided or
400 | if the vector store file cannot be found, since a new vector
401 | store will be created in the next step.
402 | 2. error 404: when vector store file not found.
403 | *)
404 | {$ENDREGION}
405 | if Error.StartsWith('error 404') then
406 | Resolve(EmptyStr)
407 | else
408 | Reject(Exception.Create(Error));
409 | end;
410 | end);
411 | end);
412 | end;
413 |
414 | function TVectorStoreManager.RetrieveVectorStoreId(
415 | const Value: string): TPromise;
416 | {$REGION 'Dev notes'}
417 | (* Notes :
418 | 1. Empty string handling (value = '') is retained, even though the EnsureVectorStoreId
419 | method excludes them to improve processing efficiency.
420 | 2. We are not using the AsyncAwaitRetrieve method here because we want to define an
421 | onError handler that allows the promise to be resolved explicitly.
422 | *)
423 | {$ENDREGION}
424 | begin
425 | var EmptyValue := Value.Trim.IsEmpty;
426 | Result := TPromise.Create(
427 | procedure(Resolve: TProc; Reject: TProc)
428 | begin
429 | FClient.VectorStore.AsynRetrieve(Value,
430 | function : TAsynVectorStore
431 | begin
432 | Result.OnSuccess :=
433 | procedure (Sender: TObject; Vector: TVectorStore)
434 | begin
435 | Resolve(Vector.Id);
436 | end;
437 |
438 | Result.OnError :=
439 | procedure (Sender: TObject; Error: string)
440 | begin
441 | {$REGION 'Dev notes'}
442 | (* 1. The promise is still resolved, even if no ID is provided or
443 | if the vector store cannot be found, since a new vector store
444 | will be created in the next step.
445 | 2. error 404: when vector store not found.
446 | *)
447 | {$ENDREGION}
448 | if EmptyValue or Error.StartsWith('error 404') then
449 | Resolve(EmptyStr)
450 | else
451 | Reject(Exception.Create(Error));
452 | end;
453 | end);
454 | end);
455 | end;
456 |
457 | end.
458 |
--------------------------------------------------------------------------------