秋·风

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  276 随笔 :: 0 文章 :: 305 评论 :: 19万 阅读
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
使用了opensslsockets, fphttpclient,  fpjson, jsonparse,cef4delphi,synedit,fpc_Markdown等单元,实现DeepSeek(只要兼容OpenAI接口就可以)或ollama本地部署的API Chat接口的流式接收的程序。
程序运行时的截图如下:

 

 

 

 以下是精简后的代码【删除CEF4Delphi控件及与流接收功能无关的代码(可以正常编译运行)】:

复制代码
unit LargeModels;

{$Mode objfpc}{$H+}

interface

uses
  {$ifdef windows}
  Windows,
  {$endif}
  LCLIntf,
  Messages, SysUtils, Variants, Classes, Graphics, Menus, DB,process,
  Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Types, ComCtrls, ClipBrd,
  DBGrids, Grids,fphttpclient, fpjson, jsonparser, HTTPThreadUnit, SynEdit;

type

  ModelList=record
    Model:String;
    parameter_size:string;
    size:String;
    quantization_level:String;
  end;

  { TLargeModelFrm }

  TLargeModelFrm = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button4: TButton;
    ComboBox1: TComboBox;
    SynEdit1: TSynEdit;
    URLBox: TComboBox;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Memo2: TMemo;
    Panel1: TPanel;
    Panel3: TPanel;
    Panel4: TPanel;
    Panel6: TPanel;
    StatusPnl: TPanel;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure updateModelList;
    procedure HandleStreamData(const Data: string);
    procedure LoadLargeModelAPI(question,urls,APIKey,modelType:string);
    procedure URLBoxChange(Sender: TObject);
  private
    ModelLists:array of ModelList;
    FHTTPThread: THTTPThread; // 保存线程引用
    answerStr:String;
    RequestBody: TStringStream;
    StartTime:TDateTime;
    ollamapath:String;
  protected

  public

  end;

var
  LargeModelFrm : TLargeModelFrm;

implementation

{$R *.lfm}

procedure TLargeModelFrm.LoadLargeModelAPI(question,urls,APIkey,modelName:string);
var
  JSONRequest: TJSONObject;
begin
  if RequestBody<>nil then
    RequestBody.Free;
  RequestBody := TStringStream.Create('', TEncoding.UTF8);
  JSONRequest := TJSONObject.Create;

  try
    // 构建请求 JSON
    JSONRequest.Add('model', modelName);
    JSONRequest.Add('stream', true);
    JSONRequest.Add('messages', TJSONArray.Create([
      TJSONObject.Create(['role', 'user', 'content', question])]));
    JSONRequest.Add('temperature', 0.7);

    // 设置请求头和 URL
    RequestBody.WriteString(JSONRequest.AsJSON);

    //启用多线程提交查询的问题
    FHTTPThread := THTTPThread.Create(urls+'/v1/chat/completions',APIkey, RequestBody);
    FHTTPThread.OnStreamData := @HandleStreamData;//接收返回的答案

  finally
    JSONRequest.Free;
  end;
end;

procedure TLargeModelFrm.URLBoxChange(Sender: TObject);
begin
   Label1.Enabled:=true;
   ComboBox1.Enabled:=true;
   StatusPnl.Caption:=ComboBox1.Text+'【模型大小:'+ModelLists[ComboBox1.ItemIndex].size+'  模型参数(单位10亿):'+ModelLists[ComboBox1.ItemIndex].parameter_size+'  模型量化方案:'+ModelLists[ComboBox1.ItemIndex].quantization_level+'';
end;

//接收返回的答案
procedure TLargeModelFrm.HandleStreamData(const Data: string);
begin
  answerStr:=answerStr+ Data;
  SynEdit1.Lines.Text:=answerStr;
  SynEdit1.TopLine:=SynEdit1.Lines.Count;//滚动到最后一行
  Application.ProcessMessages; // 确保UI更新
end;

procedure TLargeModelFrm.Button1Click(Sender: TObject);
var
  Models,DeployType:String;
begin
  if Memo2.Text<>'' Then
  begin
    answerStr:='';
    StartTime:=now;
    SynEdit1.Lines.Add('-----------------------------------------');
    SynEdit1.Lines.Add('使用的大模型:'+ComboBox1.Text);
    SynEdit1.Lines.Add('开始时间:'+DateTimeToStr(StartTime));
    Panel6.Caption:=StatusPnl.Caption;
    if URLBox.ItemIndex<0 then
       URLBox.ItemIndex:=0;
      DeployType:=ComboBox1.Text;
    LoadLargeModelAPI(memo2.Text,URLBox.Text,'',DeployType);
    Button2.Enabled:=true;
  end;
end;

procedure TLargeModelFrm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := True;
  Visible  := False;
end;

//从Ollama取本地大模型列表
procedure TLargeModelFrm.updateModelList;
var
  jData:TJSONData;
  jDataArr,jDataArr2:TJSONData;
  i,j:Integer;
  tempstr:String;
  HttpClient: TFPHttpClient;
  Response: String;
begin
  HttpClient := TFPHttpClient.Create(nil);

    Response := HttpClient.Get(URLBox.Text+'/api/tags');
    ComboBox1.Items.Clear;
    jData:=GetJSON(Response);
    jDataArr:=jData.FindPath('models');
    i:=jDataArr.Count;
    SetLength(ModelLists,i);
    for j:=0 to i-1 do       //列出已部署的大模型
    begin
      jDataArr2:=jData.FindPath('models['+j.ToString+'].model');
      tempstr:=jDataArr2.AsJSON;
      tempstr:= tempstr.Replace('"','',[rfReplaceAll,rfIgnoreCase]);
      ModelLists[j].Model:=tempstr;
      ComboBox1.Items.Add(tempstr);

      jDataArr2:=jData.FindPath('models['+j.ToString+'].size');
      tempstr:=jDataArr2.AsJSON;
      tempstr:= tempstr.Replace('"','',[rfReplaceAll,rfIgnoreCase]);
      ModelLists[j].size:=tempstr;

      jDataArr2:=jData.FindPath('models['+j.ToString+'].details.parameter_size');
      tempstr:=jDataArr2.AsJSON;
      tempstr:= tempstr.Replace('"','',[rfReplaceAll,rfIgnoreCase]);
      ModelLists[j].parameter_size:=tempstr;
      jDataArr2:=jData.FindPath('models['+j.ToString+'].details.quantization_level');
      tempstr:=jDataArr2.AsJSON;
      tempstr:= tempstr.Replace('"','',[rfReplaceAll,rfIgnoreCase]);
      ModelLists[j].quantization_level:=tempstr;
    end;
    HttpClient.Free;
    ComboBox1.ItemIndex:=0;
    StatusPnl.Caption:=ComboBox1.Text+'【模型大小:'+ModelLists[ComboBox1.ItemIndex].size+'  模型参数(单位10亿):'+ModelLists[ComboBox1.ItemIndex].parameter_size+'  模型量化方案:'+ModelLists[ComboBox1.ItemIndex].quantization_level+'';
end;

procedure TLargeModelFrm.FormCreate(Sender: TObject);
var
  i:Integer;
begin
  updateModelList;//取本地大模型列表
  Memo2.Lines.Clear;
end;

procedure TLargeModelFrm.FormDestroy(Sender: TObject);
var
  s:String;
begin           
  if Assigned(FHTTPThread) then FreeAndNil(FHTTPThread);
  if Assigned(RequestBody) then RequestBody.Free;
end;

procedure TLargeModelFrm.Button2Click(Sender: TObject);
begin
  if Assigned(FHTTPThread) then
  begin
    FHTTPThread.Terminate; // 触发终止
    FHTTPThread.WaitFor;   // 等待线程结束
    FreeAndNil(FHTTPThread); // 释放线程
    SynEdit1.Lines.Add('已终止请求');
    Button2.Enabled:=False;
  end;
end;

procedure TLargeModelFrm.Button4Click(Sender: TObject);
begin
  Memo2.Lines.Clear;
end;

procedure TLargeModelFrm.ComboBox1Change(Sender: TObject);
begin
  StatusPnl.Caption:=ComboBox1.Text+'【模型大小:'+ModelLists[ComboBox1.ItemIndex].size+'  模型参数(单位10亿):'+ModelLists[ComboBox1.ItemIndex].parameter_size+'  模型量化方案:'+ModelLists[ComboBox1.ItemIndex].quantization_level+'';
end;


end.
复制代码

LargeModels.lfm:

复制代码
object LargeModelFrm: TLargeModelFrm
  Left = 0
  Height = 1656
  Top = 0
  Width = 1836
  Caption = 'Lazarus AI助手'
  ClientHeight = 1656
  ClientWidth = 1836
  Color = clBtnFace
  DesignTimePPI = 192
  Font.Color = clWindowText
  Font.Height = -24
  Font.Name = 'Tahoma'
  Position = poScreenCenter
  OnCloseQuery = FormCloseQuery
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  object StatusPnl: TPanel
    Left = 0
    Height = 48
    Top = 1608
    Width = 1836
    Align = alBottom
    BevelOuter = bvLowered
    ParentBackground = False
    TabOrder = 0
  end
  object Panel1: TPanel
    Left = 0
    Height = 1608
    Top = 0
    Width = 1836
    Align = alClient
    ClientHeight = 1608
    ClientWidth = 1836
    ParentBackground = False
    TabOrder = 1
    object Panel3: TPanel
      Left = 1
      Height = 383
      Top = 1224
      Width = 1834
      Align = alBottom
      ClientHeight = 383
      ClientWidth = 1834
      ParentBackground = False
      TabOrder = 0
      object Label2: TLabel
        Left = 1337
        Height = 1
        Top = 24
        Width = 1
        ParentColor = False
      end
      object Button1: TButton
        Left = 1630
        Height = 247
        Top = 128
        Width = 192
        Anchors = [akTop, akRight, akBottom]
        Caption = '提问'
        TabOrder = 0
        OnClick = Button1Click
      end
      object Label1: TLabel
        AnchorSideTop.Control = ComboBox1
        AnchorSideTop.Side = asrCenter
        Left = 660
        Height = 29
        Top = 16
        Width = 72
        Caption = '大模型'
      end
      object Label3: TLabel
        AnchorSideTop.Side = asrCenter
        AnchorSideBottom.Control = Memo2
        Left = 8
        Height = 37
        Top = 91
        Width = 120
        Anchors = [akTop, akLeft, akBottom]
        BorderSpacing.Top = 5
        Caption = '提问内容:'
      end
      object ComboBox1: TComboBox
        AnchorSideLeft.Control = Label1
        AnchorSideLeft.Side = asrBottom
        Left = 737
        Height = 37
        Top = 12
        Width = 496
        BorderSpacing.Left = 5
        ItemHeight = 29
        ItemIndex = 0
        Items.Strings = (
          'DeepSeek-v3'
          'Kimi'
        )
        Style = csDropDownList
        TabOrder = 1
        Text = 'DeepSeek-v3'
        OnChange = ComboBox1Change
      end
      object Label4: TLabel
        AnchorSideTop.Side = asrCenter
        Left = 8
        Height = 29
        Top = 16
        Width = 84
        Caption = 'API地址'
      end
      object Label5: TLabel
        AnchorSideLeft.Control = ComboBox1
        AnchorSideLeft.Side = asrBottom
        AnchorSideTop.Control = ComboBox1
        AnchorSideTop.Side = asrCenter
        Left = 1243
        Height = 1
        Top = 30
        Width = 1
        BorderSpacing.Left = 10
      end
      object Memo2: TMemo
        Left = 8
        Height = 247
        Top = 128
        Width = 1614
        Anchors = [akTop, akLeft, akRight, akBottom]
        Lines.Strings = (
          'lazarus编写计算MD5的程序'
        )
        ScrollBars = ssBoth
        TabOrder = 2
      end
      object Button2: TButton
        AnchorSideTop.Control = ComboBox1
        AnchorSideTop.Side = asrBottom
        AnchorSideBottom.Control = Memo2
        Left = 1630
        Height = 64
        Top = 59
        Width = 192
        Anchors = [akTop, akRight, akBottom]
        BorderSpacing.Top = 10
        BorderSpacing.Bottom = 5
        Caption = '中止思考'
        Enabled = False
        TabOrder = 3
        OnClick = Button2Click
      end
      object Button4: TButton
        AnchorSideTop.Control = ComboBox1
        AnchorSideTop.Side = asrBottom
        AnchorSideBottom.Control = Memo2
        Left = 1384
        Height = 64
        Top = 59
        Width = 231
        Anchors = [akTop, akRight, akBottom]
        BorderSpacing.Top = 10
        BorderSpacing.Bottom = 5
        Caption = '清空提问内容'
        TabOrder = 4
        OnClick = Button4Click
      end
      object URLBox: TComboBox
        Left = 96
        Height = 37
        Top = 12
        Width = 544
        ItemHeight = 29
        ItemIndex = 0
        Items.Strings = (
          'http://localhost:11434'
        )
        Style = csDropDownList
        TabOrder = 5
        Text = 'http://localhost:11434'
        OnChange = URLBoxChange
      end
    end
    object Panel4: TPanel
      Left = 1
      Height = 1223
      Top = 1
      Width = 1834
      Align = alClient
      ClientHeight = 1223
      ClientWidth = 1834
      ParentBackground = False
      TabOrder = 1
      object Panel6: TPanel
        Left = 1
        Height = 47
        Top = 1
        Width = 1832
        Align = alTop
        BevelOuter = bvNone
        ParentBackground = False
        TabOrder = 0
      end
      inline SynEdit1: TSynEdit
        Left = 1
        Height = 1174
        Top = 48
        Width = 1832
        Align = alClient
        Anchors = [akTop, akLeft, akRight]
        Font.CharSet = ANSI_CHARSET
        Font.Height = -24
        Font.Name = 'Courier New'
        Font.Pitch = fpFixed
        Font.Quality = fqDraft
        ParentColor = False
        ParentFont = False
        TabOrder = 1
        Gutter.Width = 109
        Gutter.MouseActions = <>
        RightGutter.Width = 0
        RightGutter.MouseActions = <>
        Keystrokes = <        
          item
            Command = ecUp
            ShortCut = 38
          end        
          item
            Command = ecSelUp
            ShortCut = 8230
          end        
          item
            Command = ecScrollUp
            ShortCut = 16422
          end        
          item
            Command = ecDown
            ShortCut = 40
          end        
          item
            Command = ecSelDown
            ShortCut = 8232
          end        
          item
            Command = ecScrollDown
            ShortCut = 16424
          end        
          item
            Command = ecLeft
            ShortCut = 37
          end        
          item
            Command = ecSelLeft
            ShortCut = 8229
          end        
          item
            Command = ecWordLeft
            ShortCut = 16421
          end        
          item
            Command = ecSelWordLeft
            ShortCut = 24613
          end        
          item
            Command = ecRight
            ShortCut = 39
          end        
          item
            Command = ecSelRight
            ShortCut = 8231
          end        
          item
            Command = ecWordRight
            ShortCut = 16423
          end        
          item
            Command = ecSelWordRight
            ShortCut = 24615
          end        
          item
            Command = ecPageDown
            ShortCut = 34
          end        
          item
            Command = ecSelPageDown
            ShortCut = 8226
          end        
          item
            Command = ecPageBottom
            ShortCut = 16418
          end        
          item
            Command = ecSelPageBottom
            ShortCut = 24610
          end        
          item
            Command = ecPageUp
            ShortCut = 33
          end        
          item
            Command = ecSelPageUp
            ShortCut = 8225
          end        
          item
            Command = ecPageTop
            ShortCut = 16417
          end        
          item
            Command = ecSelPageTop
            ShortCut = 24609
          end        
          item
            Command = ecLineStart
            ShortCut = 36
          end        
          item
            Command = ecSelLineStart
            ShortCut = 8228
          end        
          item
            Command = ecEditorTop
            ShortCut = 16420
          end        
          item
            Command = ecSelEditorTop
            ShortCut = 24612
          end        
          item
            Command = ecLineEnd
            ShortCut = 35
          end        
          item
            Command = ecSelLineEnd
            ShortCut = 8227
          end        
          item
            Command = ecEditorBottom
            ShortCut = 16419
          end        
          item
            Command = ecSelEditorBottom
            ShortCut = 24611
          end        
          item
            Command = ecToggleMode
            ShortCut = 45
          end        
          item
            Command = ecCopy
            ShortCut = 16429
          end        
          item
            Command = ecPaste
            ShortCut = 8237
          end        
          item
            Command = ecDeleteChar
            ShortCut = 46
          end        
          item
            Command = ecCut
            ShortCut = 8238
          end        
          item
            Command = ecDeleteLastChar
            ShortCut = 8
          end        
          item
            Command = ecDeleteLastChar
            ShortCut = 8200
          end        
          item
            Command = ecDeleteLastWord
            ShortCut = 16392
          end        
          item
            Command = ecUndo
            ShortCut = 32776
          end        
          item
            Command = ecRedo
            ShortCut = 40968
          end        
          item
            Command = ecLineBreak
            ShortCut = 13
          end        
          item
            Command = ecSelectAll
            ShortCut = 16449
          end        
          item
            Command = ecCopy
            ShortCut = 16451
          end        
          item
            Command = ecBlockIndent
            ShortCut = 16457
          end        
          item
            Command = ecLineBreak
            ShortCut = 16461
          end        
          item
            Command = ecInsertLine
            ShortCut = 16462
          end        
          item
            Command = ecDeleteWord
            ShortCut = 16468
          end        
          item
            Command = ecBlockUnindent
            ShortCut = 16469
          end        
          item
            Command = ecPaste
            ShortCut = 16470
          end        
          item
            Command = ecCut
            ShortCut = 16472
          end        
          item
            Command = ecDeleteLine
            ShortCut = 16473
          end        
          item
            Command = ecDeleteEOL
            ShortCut = 24665
          end        
          item
            Command = ecUndo
            ShortCut = 16474
          end        
          item
            Command = ecRedo
            ShortCut = 24666
          end        
          item
            Command = ecGotoMarker0
            ShortCut = 16432
          end        
          item
            Command = ecGotoMarker1
            ShortCut = 16433
          end        
          item
            Command = ecGotoMarker2
            ShortCut = 16434
          end        
          item
            Command = ecGotoMarker3
            ShortCut = 16435
          end        
          item
            Command = ecGotoMarker4
            ShortCut = 16436
          end        
          item
            Command = ecGotoMarker5
            ShortCut = 16437
          end        
          item
            Command = ecGotoMarker6
            ShortCut = 16438
          end        
          item
            Command = ecGotoMarker7
            ShortCut = 16439
          end        
          item
            Command = ecGotoMarker8
            ShortCut = 16440
          end        
          item
            Command = ecGotoMarker9
            ShortCut = 16441
          end        
          item
            Command = ecSetMarker0
            ShortCut = 24624
          end        
          item
            Command = ecSetMarker1
            ShortCut = 24625
          end        
          item
            Command = ecSetMarker2
            ShortCut = 24626
          end        
          item
            Command = ecSetMarker3
            ShortCut = 24627
          end        
          item
            Command = ecSetMarker4
            ShortCut = 24628
          end        
          item
            Command = ecSetMarker5
            ShortCut = 24629
          end        
          item
            Command = ecSetMarker6
            ShortCut = 24630
          end        
          item
            Command = ecSetMarker7
            ShortCut = 24631
          end        
          item
            Command = ecSetMarker8
            ShortCut = 24632
          end        
          item
            Command = ecSetMarker9
            ShortCut = 24633
          end        
          item
            Command = EcFoldLevel1
            ShortCut = 41009
          end        
          item
            Command = EcFoldLevel2
            ShortCut = 41010
          end        
          item
            Command = EcFoldLevel3
            ShortCut = 41011
          end        
          item
            Command = EcFoldLevel4
            ShortCut = 41012
          end        
          item
            Command = EcFoldLevel5
            ShortCut = 41013
          end        
          item
            Command = EcFoldLevel6
            ShortCut = 41014
          end        
          item
            Command = EcFoldLevel7
            ShortCut = 41015
          end        
          item
            Command = EcFoldLevel8
            ShortCut = 41016
          end        
          item
            Command = EcFoldLevel9
            ShortCut = 41017
          end        
          item
            Command = EcFoldLevel0
            ShortCut = 41008
          end        
          item
            Command = EcFoldCurrent
            ShortCut = 41005
          end        
          item
            Command = EcUnFoldCurrent
            ShortCut = 41003
          end        
          item
            Command = EcToggleMarkupWord
            ShortCut = 32845
          end        
          item
            Command = ecNormalSelect
            ShortCut = 24654
          end        
          item
            Command = ecColumnSelect
            ShortCut = 24643
          end        
          item
            Command = ecLineSelect
            ShortCut = 24652
          end        
          item
            Command = ecTab
            ShortCut = 9
          end        
          item
            Command = ecShiftTab
            ShortCut = 8201
          end        
          item
            Command = ecMatchBracket
            ShortCut = 24642
          end        
          item
            Command = ecColSelUp
            ShortCut = 40998
          end        
          item
            Command = ecColSelDown
            ShortCut = 41000
          end        
          item
            Command = ecColSelLeft
            ShortCut = 40997
          end        
          item
            Command = ecColSelRight
            ShortCut = 40999
          end        
          item
            Command = ecColSelPageDown
            ShortCut = 40994
          end        
          item
            Command = ecColSelPageBottom
            ShortCut = 57378
          end        
          item
            Command = ecColSelPageUp
            ShortCut = 40993
          end        
          item
            Command = ecColSelPageTop
            ShortCut = 57377
          end        
          item
            Command = ecColSelLineStart
            ShortCut = 40996
          end        
          item
            Command = ecColSelLineEnd
            ShortCut = 40995
          end        
          item
            Command = ecColSelEditorTop
            ShortCut = 57380
          end        
          item
            Command = ecColSelEditorBottom
            ShortCut = 57379
          end>
        MouseActions = <>
        MouseTextActions = <>
        MouseSelActions = <>
        VisibleSpecialChars = [vscSpace, vscTabAtLast]
        ReadOnly = True
        SelectedColor.BackPriority = 50
        SelectedColor.ForePriority = 50
        SelectedColor.FramePriority = 50
        SelectedColor.BoldPriority = 50
        SelectedColor.ItalicPriority = 50
        SelectedColor.UnderlinePriority = 50
        SelectedColor.StrikeOutPriority = 50
        BracketHighlightStyle = sbhsBoth
        BracketMatchColor.Background = clNone
        BracketMatchColor.Foreground = clNone
        BracketMatchColor.Style = [fsBold]
        FoldedCodeColor.Background = clNone
        FoldedCodeColor.Foreground = clGray
        FoldedCodeColor.FrameColor = clGray
        MouseLinkColor.Background = clNone
        MouseLinkColor.Foreground = clBlue
        LineHighlightColor.Background = clNone
        LineHighlightColor.Foreground = clNone
        inline SynLeftGutterPartList1: TSynGutterPartList
          object SynGutterMarks1: TSynGutterMarks
            Width = 48
            MouseActions = <>
            MaxExtraMarksColums = 0
            Options = [sgmoDeDuplicateMarksOnOverflow]
          end
          object SynGutterLineNumber1: TSynGutterLineNumber
            Width = 29
            MouseActions = <>
            MarkupInfo.Background = clBtnFace
            MarkupInfo.Foreground = clNone
            DigitCount = 2
            ShowOnlyLineNumbersMultiplesOf = 1
            ZeroStart = False
            LeadingZeros = False
          end
          object SynGutterChanges1: TSynGutterChanges
            Width = 8
            MouseActions = <>
            ModifiedColor = 59900
            SavedColor = clGreen
          end
          object SynGutterSeparator1: TSynGutterSeparator
            Width = 4
            MouseActions = <>
            MarkupInfo.Background = clWhite
            MarkupInfo.Foreground = clGray
          end
          object SynGutterCodeFolding1: TSynGutterCodeFolding
            Width = 20
            MouseActions = <>
            MarkupInfo.Background = clNone
            MarkupInfo.Foreground = clGray
            MouseActionsExpanded = <>
            MouseActionsCollapsed = <>
          end
        end
      end
    end
  end
end
复制代码

HTTPThreadUnit.pas:

复制代码
unit HTTPThreadUnit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, fphttpclient,
  opensslsockets,//使用HTTPS时引用这个单元,需要用到libeay32.dll和ssleay32.dll
  // ssockets, sslsockets,--使用HTTPS时引用这2个单元需要用到libcrypto-1_1-x64.dll/libssl-1_1-x64.dll
  fpjson, jsonparser;

type
  TStreamDataEvent = procedure(const Data: string) of object;

  THTTPThread = class(TThread)

  private
    FTerminated: Boolean; //终止标志
    FURL: string;
    FRequestStream: TStringStream;
    FClient: TFPHttpClient;
    FResponseStream:TStringStream;
    FBuffer: string;
    FOnStreamData: TStreamDataEvent;
    procedure DataReceivedHandler(Sender: TObject; const ContentLength, CurrentPos: Int64);
    procedure HandleEvent(const EventData: string);
    procedure ProcessData;
    procedure DoStreamData(const Data: string);
  protected
    procedure Execute; override;
  public
    constructor Create(const URL,APIKey: string; RequestStream: TStringStream);
    destructor Destroy; override;
    procedure Terminate; // 终止方法
    property OnStreamData: TStreamDataEvent read FOnStreamData write FOnStreamData;
  end;

implementation

constructor THTTPThread.Create(const URL,APIKey: string; RequestStream: TStringStream);
begin
  inherited Create(True);
  FURL := URL;
  FRequestStream:= RequestStream;
  FResponseStream := TStringStream.Create('', TEncoding.UTF8);
  FClient := TFPHttpClient.Create(nil);
  FClient.OnDataReceived := @DataReceivedHandler;
  FClient.RequestHeaders.Add('Accept: text/event-stream');
  FClient.RequestHeaders.Add('Content-Type: application/json');
  FClient.AddHeader('Authorization','Bearer '+ APIKey);
  FClient.RequestBody:=RequestStream;
  Start;
end;

destructor THTTPThread.Destroy;
begin
  FResponseStream.Free;
  FClient.Free;
  inherited Destroy;
end;

procedure THTTPThread.Execute;
begin
  try
    FClient.post(FURL, FResponseStream);
  except
    on E: Exception do
    if not FTerminated then // 仅处理非主动终止的异常
      DoStreamData('Error: ' + E.Message);
  end;
end;

procedure THTTPThread.Terminate;
begin
  FTerminated := True;
  if Assigned(FClient) then
    FClient.Terminate;// 强制断开连接以终止阻塞请求
end;

procedure THTTPThread.DataReceivedHandler(Sender: TObject; const ContentLength, CurrentPos: Int64);
begin
  if FTerminated then Exit; // 如果已终止则跳过处理
    Synchronize(@ProcessData);
end;

procedure THTTPThread.ProcessData;
var
  DataStr: string;
  EventEndPos: Integer;
begin
  FResponseStream.Position := 0;
  SetLength(DataStr, FResponseStream.Size);
  FResponseStream.ReadBuffer(DataStr[1], FResponseStream.Size);
  FResponseStream.Clear;

  FBuffer := FBuffer + DataStr;

  repeat
    EventEndPos := Pos(#10#10, FBuffer);
    if EventEndPos > 0 then
    begin
      HandleEvent(Copy(FBuffer, 1, EventEndPos - 1));
      Delete(FBuffer, 1, EventEndPos + 1);
    end;
  until EventEndPos = 0;
end;

procedure THTTPThread.HandleEvent(const EventData: string);
var
  Lines: TStringList;
  i: Integer;
  DataLine, JSONData: string;
  JSON: TJSONData;
begin
  Lines := TStringList.Create;
  try
    Lines.Text := StringReplace(EventData, #10, sLineBreak, [rfReplaceAll]);
    for i := 0 to Lines.Count - 1 do
    begin
      DataLine := Lines[i];
      if Pos('data: ', DataLine) = 1 then
      begin
        JSONData := Trim(Copy(DataLine, 6, MaxInt));
        if JSONData <> '' then
        begin
          if JSONData<>'[DONE]' then
          begin
            try
              JSON := GetJSON(JSONData);
              try
                if JSON.FindPath('choices[0].delta.content') <> nil then
                  DoStreamData(JSON.FindPath('choices[0].delta.content').AsString);
              finally
                JSON.Free;
              end;
            except
              on E: Exception do
                DoStreamData('JSON Error: ' + E.Message);
            end;
          end;
end;
      end;
    end;
  finally
    Lines.Free;
  end;
end;

procedure THTTPThread.DoStreamData(const Data: string);
begin
  if Assigned(FOnStreamData) then
    FOnStreamData(Data);
end;

end.
复制代码

 

posted on   秋·风  阅读(118)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示