unit uKandinskyAPI;

interface

uses
  System.SysUtils, System.Classes, System.JSON, System.Net.HttpClient,
  System.Net.URLClient, System.NetConsts, 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<string>;
    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<string>;
    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<string>('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<string>('uuid');
      finally
        Json.Free;
      end;
    finally
      Multipart.Free;
    end;
  finally
    Root.Free;
    // Params освобождается автоматически через Root
  end;
end;

function TFusionBrainAPI.CheckGeneration(const RequestId: string): TArray<string>;
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<string>('status');
      if Status = 'DONE' then
      begin
        ResultObj := Json.GetValue<TJSONObject>('result');
        Files := ResultObj.GetValue<TJSONArray>('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.
