unit uKandinskyAPI; interface uses System.SysUtils, System.Classes, System.JSON, System.Net.HttpClient, System.Net.URLClient, System.NetConsts, system.StrUtils, System.Net.Mime, System.NetEncoding, System.Threading; type TGenerationDoneEvent = procedure(Sender: TObject; const FileName: string) of object; TStatusUpdateEvent = procedure(Sender: TObject; const Message: string) of object; TErrorEvent = procedure(Sender: TObject; const ErrorMessage: string) of object; TFusionBrainAPI = class(TComponent) private FBaseURL: string; FApiKey: string; FSecretKey: string; FClient: THTTPClient; FOnGenerationDone: TGenerationDoneEvent; FOnStatusUpdate: TStatusUpdateEvent; FOnError: TErrorEvent; procedure DoStatusUpdate(const AMessage: string); procedure DoGenerationDone(const AFileName: string); procedure DoError(const AErrorMessage: string); function GetAuthHeaders: TNetHeaders; function GetPipeline: string; function Generate(const Prompt, PipelineId: string): string; function CheckGeneration(const RequestId: string): TArray; procedure SaveBase64Image(const Base64Str, FileName: string); public constructor Create(AOwner: TComponent; aKey:string; aSecret:string); destructor Destroy; override; procedure StartGeneration(const APrompt: string); property OnGenerationDone: TGenerationDoneEvent read FOnGenerationDone write FOnGenerationDone; property OnStatusUpdate: TStatusUpdateEvent read FOnStatusUpdate write FOnStatusUpdate; property OnError: TErrorEvent read FOnError write FOnError; end; implementation uses ugeneral; constructor TFusionBrainAPI.Create(AOwner: TComponent; aKey:string; aSecret:string); begin inherited Create(AOwner); FClient := THTTPClient.Create; FBaseURL := 'https://api-key.fusionbrain.ai/'; FApiKey :=aKey; FSecretKey := aSecret; end; destructor TFusionBrainAPI.Destroy; begin FClient.Free; inherited; end; procedure TFusionBrainAPI.StartGeneration(const APrompt: string); var Task: ITask; begin Task :=TTask.Run(procedure var PipelineID, UUID, FileName: string; Links: TArray; begin try TThread.Queue(nil, procedure begin DoStatusUpdate('Получение конвейера...'); end); PipelineID := GetPipeline; TThread.Queue(nil, procedure begin DoStatusUpdate('Генерация изображения...'); end); UUID := Generate(APrompt, PipelineID); TThread.Queue(nil, procedure begin DoStatusUpdate('Проверка статуса...'); end); Links := CheckGeneration(UUID); FileName := myConst.AppDataPath + 'imgs\kandinsky_' + FormatDateTime('yyyymmddhhnnss', Now) + '.jpg'; SaveBase64Image(Links[0], FileName); TThread.Queue(nil, procedure begin DoGenerationDone(FileName); end); except on E: Exception do TThread.Queue(nil, procedure begin DoError(E.Message); end); end; end); end; function TFusionBrainAPI.GetAuthHeaders: TNetHeaders; begin SetLength(Result, 2); Result[0] := TNetHeader.Create('X-Key', 'Key ' + FApiKey); Result[1] := TNetHeader.Create('X-Secret', 'Secret ' + FSecretKey); end; function TFusionBrainAPI.GetPipeline: string; var Response: IHTTPResponse; Json: TJSONArray; begin Response := FClient.Get(FBaseURL + 'key/api/v1/pipelines', nil, GetAuthHeaders); Json := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONArray; try Result := Json.Items[0].GetValue('id'); finally Json.Free; end; end; function TFusionBrainAPI.Generate(const Prompt, PipelineId: string): string; var Params, Root: TJSONObject; Multipart: TMultipartFormData; Response: IHTTPResponse; Json: TJSONObject; begin // Упрощенное создание JSON Params := TJSONObject.Create; Root := TJSONObject.Create; try Params.AddPair('query', Prompt); Root.AddPair('type', 'GENERATE'); Root.AddPair('numImages', TJSONNumber.Create(1)); Root.AddPair('width', TJSONNumber.Create(512)); Root.AddPair('height', TJSONNumber.Create(512)); Root.AddPair('generateParams', Params); Multipart := TMultipartFormData.Create; try Multipart.AddField('pipeline_id', PipelineId); Multipart.AddField('params', Root.ToString, 'application/json'); Response := FClient.Post(FBaseURL + 'key/api/v1/pipeline/run', Multipart, nil, GetAuthHeaders); Json := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONObject; try Result := Json.GetValue('uuid'); finally Json.Free; end; finally Multipart.Free; end; finally Root.Free; // Params освобождается автоматически через Root end; end; function TFusionBrainAPI.CheckGeneration(const RequestId: string): TArray; const MaxAttempts = 60; var Response: IHTTPResponse; Json, ResultObj: TJSONObject; Files: TJSONArray; i, Attempt: Integer; Status: string; begin Attempt := 0; while Attempt < MaxAttempts do begin Sleep(5000); Inc(Attempt); Response := FClient.Get(FBaseURL + 'key/api/v1/pipeline/status/' + RequestId, nil, GetAuthHeaders); Json := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONObject; try Status := Json.GetValue('status'); if Status = 'DONE' then begin ResultObj := Json.GetValue('result'); Files := ResultObj.GetValue('files'); SetLength(Result, Files.Count); for i := 0 to Files.Count - 1 do Result[i] := Files.Items[i].Value; Exit; end else if Status = 'FAILED' then raise Exception.Create('Generation failed'); finally Json.Free; end; end; raise Exception.Create('Timeout waiting for generation'); end; procedure TFusionBrainAPI.SaveBase64Image(const Base64Str, FileName: string); var DecodedStream: TMemoryStream; InputStr: TStringStream; begin DecodedStream := TMemoryStream.Create; InputStr := TStringStream.Create(Base64Str, TEncoding.ASCII); try TNetEncoding.Base64.Decode(InputStr, DecodedStream); DecodedStream.SaveToFile(FileName); finally DecodedStream.Free; InputStr.Free; end; end; procedure TFusionBrainAPI.DoStatusUpdate(const AMessage: string); begin if Assigned(FOnStatusUpdate) then FOnStatusUpdate(Self, AMessage); end; procedure TFusionBrainAPI.DoGenerationDone(const AFileName: string); begin if Assigned(FOnGenerationDone) then FOnGenerationDone(Self, AFileName); end; procedure TFusionBrainAPI.DoError(const AErrorMessage: string); begin if Assigned(FOnError) then FOnError(Self, AErrorMessage); end; end.