├── 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.dpr1D:\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 | ![Delphi Next Gen Ready](https://img.shields.io/badge/Delphi--Next--Gen-ready-brightgreen) 3 | ![GitHub](https://img.shields.io/badge/IDE%20Version-Delphi%2012-yellow) 4 | ![GitHub](https://img.shields.io/badge/Updated%20on%20November%202,%202025-blue) 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 | --------------------------------------------------------------------------------