MindCore.pas
PAS 14.99KB
PluginInterface.pas
PAS 325B
Lemmatizer.pas
PAS 5.03KB
ChatAI.pas
PAS 4.72KB
DecisionTree.pas
PAS 10.05KB
SimpleChat.pas
PAS 6.11KB
NLPCache.pas
PAS 3.11KB
NeuralNetwork.pas
PAS 10.09KB
NLPCore.pas
PAS 2.39KB
NLPUtils.pas
PAS 3.15KB
TestMind.pas
PAS 2.5KB
DataUtils.pas
PAS 3.44KB
PluginManagerUnit.pas
PAS 1.9KB
Привет! В прошлых чатах мы делали ИИ на FreePascal'е под Debian GNU/Linux на x86_64. Получился ряд юнитов (ещё есть юниты KMeans, GradientBoosting, LinearAlgebra, MachineLearning, RandomForest, SVM, UniversalFileReader), но чат наподобие deepseek или chatgpt ещё нет. Давай в этом чате попробуем продвинуть дальше этот проект. Чтобы ответы были именно от ИИ, а не рядом предопределённых вариантов. Также подумаем над многострочным вводом от пользователя, прикреплением файлов к сообщению. С тех пор мы также изобрели поддержку двух кодировок: ucs4 и русско-английскую однобайтную ускоренную. Может где-то в ИИ применим их. Ещё делали мой RApport универсальный язык наподобие UNL, но с числовым представлением слов. Можно подумать: не может ли RApport быть полезным в качестве лемматизатора или чего-то подобного ему для ИИ ведь в чём то они похожи.
Развитие ИИ-чата на Free Pascal

Отличная идея продолжить развитие нашего ИИ-проекта! Давайте рассмотрим несколько направлений для улучшения:
1. Улучшение обработки естественного языка
RApport как лемматизатор

Ваш RApport действительно может быть полезен для:

    Лемматизации (приведения слов к базовой форме)

    Семантического анализа (по числовым представлениям слов)

    Поиска синонимов и связанных понятий

Можно создать адаптер, который будет преобразовывать слова в их RApport-представление и использовать для:
pascal

type
  TRApportLemmatizer = class(TLemmatizer)
  private
    FRApportEngine: TRApportEngine;
  public
    function Lemmatize(const Token: string): string; override;
    function GetSimilarity(const Word1, Word2: string): Double;
  end;

Поддержка кодировок

Для ускорения обработки можно:

    Использовать UCS4 для внутреннего представления текста

    Применять русско-английскую однобайтную кодировку для быстрого поиска в словарях

2. Архитектурные улучшения
Многоуровневая система обработки:
pascal

type
  TChatProcessor = class
  private
    FIntentRecognizer: TIntentRecognizer; // Распознавание намерений
    FContextManager: TContextManager;     // Управление контекстом
    FKnowledgeGraph: TKnowledgeGraph;     // База знаний
    FResponseGenerator: TResponseGenerator; // Генерация ответов
  end;

Поддержка многострочного ввода

Для обработки многострочного ввода можно модифицировать SimpleChat.pas:
pascal

function ReadMultilineInput: string;
var
  line: string;
begin
  Result := '';
  repeat
    ReadLn(line);
    if line <> '.' then // Точка на отдельной строке - конец ввода
      Result := Result + line + #13#10;
  until line = '.';
end;

3. Работа с файлами
Обработчик вложений:
pascal

type
  TAttachmentHandler = class
  public
    function ProcessTextFile(const FileName: string): string;
    function ProcessImage(const FileName: string): string;
    function ProcessPDF(const FileName: string): string;
  end;

4. Генерация ответов ИИ

Вместо предопределённых ответов можно использовать:
Гибридный подход:

    Для простых запросов - быстрые шаблонные ответы

    Для сложных - генерация с использованием RApport и нейросетей

Пример генеративного модуля:
pascal

type
  TResponseGenerator = class
  private
    FMarkovChains: TMarkovChainModel;
    FNeuralNetwork: TNeuralNetwork;
  public
    function GenerateResponse(const Intent: string; Context: TStringList): string;
  end;

5. Интеграция машинного обучения

Использование существующих модулей (KMeans, SVM и др.) для:

    Классификации запросов

    Кластеризации тем

    Персонализации ответов

Пример улучшенного ChatAI.pas
pascal

unit AdvancedChatAI;

interface

uses
  NLPCore, RApport, NeuralNetwork, KMeans, PluginManagerUnit;

type
  TAdvancedChatAI = class
  private
    FRApportEngine: TRApportEngine;
    FIntentClassifier: TKMeans;
    FResponseGenerator: TResponseGenerator;
    FPluginManager: TPluginManager;
    FAttachmentHandler: TAttachmentHandler;
    
    function ProcessWithRApport(const Text: string): TRApportRepresentation;
    function ClassifyIntent(const RApportData: TRApportRepresentation): Integer;
    function GenerateAIResponse(Intent: Integer; Context: TStringList): string;
  public
    constructor Create;
    destructor Destroy; override;
    
    function ProcessMessage(const AMessage: string; Attachments: TStrings): string;
    procedure LearnFromInteraction(const Question, Response: string);
  end;

Рекомендации по реализации

    Начните с интеграции RApport в лемматизатор

    Добавьте поддержку многострочного ввода

    Реализуйте базовую обработку вложений (начните с текстовых файлов)

    Постепенно заменяйте шаблонные ответы на генеративные

Хотите, чтобы мы подробнее рассмотрели какой-то конкретный аспект? Или начали с реализации одного из предложенных улучшений?
ucs4opunit.pas
PAS 1.79KB
UniversalFileReader.pas
PAS 5.56KB
rustringcoreunit.pas
PAS 7.22KB
rustringunit.pas
PAS 4.44KB
LanguageExample.pas
PAS 372B
actionsunit.pas
PAS 5.04KB
objectsunit.pas
PAS 4.36KB
rapconstunit.pas
PAS 73B
GeneratorExample.pas
PAS 909B
languageunit.pas
PAS 172B
generatorunit.pas
PAS 6.7KB
PropertyExample.pas
PAS 570B
ActionExample.pas
PAS 570B
pointersunit.pas
PAS 3.63KB
defaultlanguageunit.pas
PAS 7.08KB
RApport.pas
PAS 2.34KB
ucs4unit.pas
PAS 4.79KB
sortucs4.pas
PAS 6.75KB
ruconverter.pas
PAS 3.61KB
propertysunit.pas
PAS 4.68KB
tlanguageunit.pas
PAS 8.47KB
Я думаю, что многострочный ввод можно сделать так: если пользователь ввёл не пустую строчку в консоле - это уже сообщение. А если просто нажал enter при пустой строке в консоле читать многострочно из файла post.txt в кодировке utf8. И файл postfiles.txt, содержащий список прикреплённых файлов. Но вот пользователь ввёл сообщение, может быть прикрепил файлы, что делать дальше? В какую форму лучше перевести всё это, чтобы ИИ смог это правильно воспринять и затем дать ответ? Файлы ведь могут быть и бинарные.

Для реализации многострочного ввода и обработки вложенных файлов в вашем ИИ-чате предлагаю следующую архитектуру:
1. Формат входных данных

Создадим структуру TUserInput, которая будет объединять все данные от пользователя:
pascal

type
  TAttachment = record
    FileName: string;
    ContentType: string; // "text/plain", "image/jpeg" и т.д.
    Content: TBytes;     // Бинарное содержимое файла
    TextContent: string; // Текстовое содержимое (если файл текстовый)
  end;

  TUserInput = record
    Message: string;           // Основное сообщение
    Attachments: array of TAttachment; // Вложенные файлы
    Timestamp: TDateTime;      // Время получения
    Language: TLanguage;       // Язык сообщения
    IsMultiline: Boolean;      // Флаг многострочного ввода
  end;

2. Обработка ввода

Модифицируем код ввода в SimpleChat.pas:
pascal

function ReadUserInput: TUserInput;
var
  line: string;
  postFile: TextFile;
  filesList: TStringList;
  i: Integer;
begin
  // Читаем первую строку из консоли
  Write('Вы: ');
  ReadLn(line);
  
  if line = '' then
  begin
    // Многострочный режим - читаем из post.txt
    Result.IsMultiline := True;
    if FileExists('post.txt') then
    begin
      AssignFile(postFile, 'post.txt');
      Reset(postFile);
      try
        Result.Message := '';
        while not Eof(postFile) do
        begin
          ReadLn(postFile, line);
          Result.Message := Result.Message + line + #13#10;
        end;
      finally
        CloseFile(postFile);
      end;
    end;
  end
  else
  begin
    // Обычный однострочный режим
    Result.IsMultiline := False;
    Result.Message := line;
  end;

  // Обработка вложенных файлов
  if FileExists('postfiles.txt') then
  begin
    filesList := TStringList.Create;
    try
      filesList.LoadFromFile('postfiles.txt');
      SetLength(Result.Attachments, filesList.Count);
      
      for i := 0 to filesList.Count - 1 do
      begin
        Result.Attachments[i].FileName := filesList[i];
        LoadAttachment(Result.Attachments[i]); // Загружаем содержимое файла
      end;
    finally
      filesList.Free;
    end;
  end;
  
  Result.Timestamp := Now;
  Result.Language := DetectLanguage(Result.Message); // Автоопределение языка
end;

3. Загрузка вложений

Реализуем функцию LoadAttachment:
pascal

procedure LoadAttachment(var Attachment: TAttachment);
var
  ext: string;
  reader: TUniversalFileReader;
begin
  if not FileExists(Attachment.FileName) then Exit;

  ext := LowerCase(ExtractFileExt(Attachment.FileName));
  
  // Определяем ContentType по расширению
  case ext of
    '.txt', '.pas', '.inc': Attachment.ContentType := 'text/plain';
    '.jpg', '.jpeg': Attachment.ContentType := 'image/jpeg';
    '.png': Attachment.ContentType := 'image/png';
    '.pdf': Attachment.ContentType := 'application/pdf';
    else Attachment.ContentType := 'application/octet-stream';
  end;

  // Для текстовых файлов сохраняем и текстовое содержимое
  if Pos('text/', Attachment.ContentType) = 1 then
  begin
    reader := TUniversalFileReader.Create(Attachment.FileName);
    try
      if reader.IsTextFile then
        Attachment.TextContent := reader.ReadAsText;
    finally
      reader.Free;
    end;
  end;

  // Всегда сохраняем бинарное содержимое
  with TFileStream.Create(Attachment.FileName, fmOpenRead) do
  try
    SetLength(Attachment.Content, Size);
    ReadBuffer(Attachment.Content[0], Size);
  finally
    Free;
  end;
end;

4. Преобразование в RApport

Для интеграции с RApport создадим функцию преобразования:
pascal

function UserInputToRApport(const Input: TUserInput): TRApportStructure;
var
  i: Integer;
begin
  // Основное сообщение
  Result := ParseRApport(Input.Message);
  
  // Обработка вложений
  for i := 0 to High(Input.Attachments) do
  begin
    if Input.Attachments[i].TextContent <> '' then
    begin
      // Для текстовых файлов добавляем содержимое
      AppendRApport(Result, ParseRApport(Input.Attachments[i].TextContent));
    end
    else
    begin
      // Для бинарных файлов добавляем метаинформацию
      AppendRApport(Result, [
        MakeEntity('file', etObject),
        MakeEntity(Input.Attachments[i].FileName, etProperty),
        MakeEntity(Input.Attachments[i].ContentType, etProperty),
        MakeEntity(IntToStr(Length(Input.Attachments[i].Content)), etQuantity)
      ]);
    end;
  end;
end;

5. Обработка в ИИ

Модифицируем TMindEngine.ProcessInput:
pascal

function TMindEngine.ProcessInput(const Input: TUserInput): TThought;
var
  rapport: TRApportStructure;
  context: TStringList;
  i: Integer;
begin
  // 1. Преобразуем ввод в RApport
  rapport := UserInputToRApport(Input);
  
  // 2. Создаем контекст
  context := TStringList.Create;
  try
    context.Values['timestamp'] := DateTimeToStr(Input.Timestamp);
    context.Values['language'] := GetEnumName(TypeInfo(TLanguage), Ord(Input.Language));
    
    // 3. Добавляем информацию о вложениях
    for i := 0 to High(Input.Attachments) do
    begin
      context.Values['attachment.'+IntToStr(i)+'.name'] := Input.Attachments[i].FileName;
      context.Values['attachment.'+IntToStr(i)+'.type'] := Input.Attachments[i].ContentType;
      context.Values['attachment.'+IntToStr(i)+'.size'] := IntToStr(Length(Input.Attachments[i].Content));
    end;
    
    // 4. Обрабатываем RApport с учетом контекста
    Result := ProcessRApport(rapport, context);
  finally
    context.Free;
  end;
end;

6. Пример обработки вложений

Для обработки специфических типов файлов можно добавить плагины:
pascal

type
  IAttachmentPlugin = interface
    function CanHandle(const ContentType: string): Boolean;
    function ProcessAttachment(const Attachment: TAttachment; Context: TStringList): TRApportStructure;
  end;

7. Работа с бинарными файлами

Для бинарных файлов можно:

    Сохранять их во временную папку

    Добавлять в RApport ссылку на файл

    Обрабатывать специальными плагинами (например, для изображений, PDF и т.д.)

pascal

procedure SaveTempAttachment(const Attachment: TAttachment): string;
var
  tempDir: string;
  tempFile: string;
begin
  tempDir := GetTempDir + 'ai_attachments' + PathDelim;
  ForceDirectories(tempDir);
  
  tempFile := tempDir + ExtractFileName(Attachment.FileName);
  with TFileStream.Create(tempFile, fmCreate) do
  try
    WriteBuffer(Attachment.Content[0], Length(Attachment.Content));
  finally
    Free;
  end;
  
  Result := tempFile;
end;

8. Пример использования
pascal

var
  userInput: TUserInput;
  aiResponse: TThought;
begin
  userInput := ReadUserInput;
  aiResponse := AIEngine.ProcessInput(userInput);
  WriteLn('ИИ: ', aiResponse.ToString);
  
  // Очистка временных файлов
  CleanupAttachments(userInput.Attachments);
end;

Такой подход позволяет:

    Единообразно обрабатывать как простые сообщения, так и многострочные

    Работать с вложениями разных типов

    Интегрировать данные в RApport для семантической обработки

    Расширять функционал через плагины для специфических типов файлов

    Сохранять контекст разговора

Что вы думаете о таком подходе? Хотите, чтобы мы детализировали какие-то аспекты?
Создай программу AdvancedChatAI и юнит postprocessor. Которые будут на первом этапе только получать как мы решили пользовательский ввод и файлы и выдавать информацию, что они загружены. А потом мы начнём их подготавливать для передачи ИИ.
AdvancedChatAI и PostProcessor

Я создам два модуля: AdvancedChatAI (основная программа) и PostProcessor (модуль для обработки ввода). На первом этапе они будут просто получать ввод и файлы, подтверждать их загрузку.
Файл: AdvancedChatAI.pas
pascal

program AdvancedChatAI;
{$MODE OBJFPC}{$H+}{$RANGECHECKS ON}

uses
  SysUtils, Classes, PostProcessor, UniversalFileReader, DateUtils;

const
  TEMP_DIR = 'temp_attachments/';
  POST_FILE = 'post.txt';
  POST_FILES_LIST = 'postfiles.txt';

var
  UserInput: TUserInput;
  ShouldExit: Boolean = False;

procedure HandleSigInt(sig: cint); cdecl;
begin
  ShouldExit := True;
end;

procedure Initialize;
begin
  // Создаем временную директорию для вложений
  if not DirectoryExists(TEMP_DIR) then
    CreateDir(TEMP_DIR);
end;

procedure ProcessUserInput;
var
  i: Integer;
begin
  // Получаем пользовательский ввод
  UserInput := GetUserInput(POST_FILE, POST_FILES_LIST);
  
  // Выводим информацию о полученных данных
  Writeln(#10'=== Получены данные ===');
  Writeln('Время: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', UserInput.Timestamp));
  Writeln('Язык: ', LanguageToStr(UserInput.Language));
  Writeln('Тип ввода: ', IfThen(UserInput.IsMultiline, 'многострочный', 'однострочный'));
  
  if UserInput.Message <> '' then
  begin
    Writeln(#10'Сообщение:');
    Writeln(UserInput.Message);
  end
  else
    Writeln(#10'Сообщение отсутствует');

  // Обработка вложений
  if Length(UserInput.Attachments) > 0 then
  begin
    Writeln(#10'Вложения:');
    for i := 0 to High(UserInput.Attachments) do
    begin
      Writeln(Format('%d. %s (%s, %d bytes)', [
        i+1, 
        UserInput.Attachments[i].FileName, 
        UserInput.Attachments[i].ContentType, 
        Length(UserInput.Attachments[i].Content)
      ]));
      
      // Сохраняем временную копию файла
      SaveTempAttachment(UserInput.Attachments[i], TEMP_DIR);
    end;
  end
  else
    Writeln(#10'Вложения отсутствуют');

  Writeln(#10'=== Данные успешно загружены ==='#10);
end;

procedure Cleanup;
begin
  // Очистка временных файлов
  if FileExists(POST_FILE) then
    DeleteFile(POST_FILE);
  if FileExists(POST_FILES_LIST) then
    DeleteFile(POST_FILES_LIST);
end;

begin
  FpSignal(SIGINT, @HandleSigInt);
  
  Initialize;
  try
    while not ShouldExit do
    begin
      try
        ProcessUserInput;
      except
        on E: Exception do
          Writeln('Ошибка: ', E.ClassName, ': ', E.Message);
      end;
    end;
  finally
    Cleanup;
  end;
end.

Файл: PostProcessor.pas
pascal

unit PostProcessor;
{$MODE OBJFPC}{$H+}{$RANGECHECKS ON}

interface

uses
  SysUtils, Classes, UniversalFileReader, LazUTF8;

type
  TAttachment = record
    FileName: string;
    ContentType: string;
    Content: TBytes;
    TextContent: string;
  end;

  TLanguage = (langUnknown, langRussian, langEnglish, langOther);
  
  TUserInput = record
    Message: string;
    Attachments: array of TAttachment;
    Timestamp: TDateTime;
    Language: TLanguage;
    IsMultiline: Boolean;
  end;

function GetUserInput(const PostFile, FilesListFile: string): TUserInput;
function LanguageToStr(Lang: TLanguage): string;
procedure SaveTempAttachment(const Attachment: TAttachment; const DestDir: string);

implementation

function DetectLanguage(const Text: string): TLanguage;
var
  i, ruCount, enCount: Integer;
  c: Char;
begin
  if Text = '' then Exit(langUnknown);
  
  ruCount := 0;
  enCount := 0;
  
  for i := 1 to UTF8Length(Text) do
  begin
    c := UTF8Copy(Text, i, 1)[1];
    case c of
      'а'..'я', 'А'..'Я', 'ё', 'Ё': Inc(ruCount);
      'a'..'z', 'A'..'Z': Inc(enCount);
    end;
  end;
  
  if (ruCount > enCount) and (ruCount > 5) then
    Result := langRussian
  else if (enCount > ruCount) and (enCount > 5) then
    Result := langEnglish
  else
    Result := langOther;
end;

function GetUserInput(const PostFile, FilesListFile: string): TUserInput;
var
  line: string;
  postFile: TextFile;
  filesList: TStringList;
  i: Integer;
  fileReader: TUniversalFileReader;
begin
  // Инициализация результата
  Result.Message := '';
  Result.Timestamp := Now;
  SetLength(Result.Attachments, 0);
  Result.IsMultiline := False;

  // Чтение сообщения
  if FileExists(PostFile) then
  begin
    AssignFile(postFile, PostFile);
    Reset(postFile);
    try
      while not Eof(postFile) do
      begin
        ReadLn(postFile, line);
        Result.Message := Result.Message + line + #10;
      end;
      Result.IsMultiline := True;
    finally
      CloseFile(postFile);
    end;
  end;

  // Определение языка сообщения
  Result.Language := DetectLanguage(Result.Message);

  // Чтение вложений
  if FileExists(FilesListFile) then
  begin
    filesList := TStringList.Create;
    try
      filesList.LoadFromFile(FilesListFile);
      SetLength(Result.Attachments, filesList.Count);
      
      for i := 0 to filesList.Count - 1 do
      begin
        Result.Attachments[i].FileName := filesList[i];
        
        // Определяем ContentType по расширению
        case LowerCase(ExtractFileExt(Result.Attachments[i].FileName)) of
          '.txt', '.pas', '.inc', '.csv': 
            Result.Attachments[i].ContentType := 'text/plain';
          '.jpg', '.jpeg': 
            Result.Attachments[i].ContentType := 'image/jpeg';
          '.png': 
            Result.Attachments[i].ContentType := 'image/png';
          '.pdf': 
            Result.Attachments[i].ContentType := 'application/pdf';
          else 
            Result.Attachments[i].ContentType := 'application/octet-stream';
        end;

        // Загружаем содержимое файла
        with TFileStream.Create(Result.Attachments[i].FileName, fmOpenRead) do
        try
          SetLength(Result.Attachments[i].Content, Size);
          ReadBuffer(Result.Attachments[i].Content[0], Size);
        finally
          Free;
        end;

        // Для текстовых файлов сохраняем текстовое содержимое
        if Pos('text/', Result.Attachments[i].ContentType) = 1 then
        begin
          fileReader := TUniversalFileReader.Create(Result.Attachments[i].FileName);
          try
            if fileReader.IsTextFile then
              Result.Attachments[i].TextContent := fileReader.ReadAsText;
          finally
            fileReader.Free;
          end;
        end;
      end;
    finally
      filesList.Free;
    end;
  end;
end;

function LanguageToStr(Lang: TLanguage): string;
begin
  case Lang of
    langRussian: Result := 'Русский';
    langEnglish: Result := 'English';
    langOther: Result := 'Другой';
    else Result := 'Неизвестен';
  end;
end;

procedure SaveTempAttachment(const Attachment: TAttachment; const DestDir: string);
var
  destPath: string;
begin
  if not DirectoryExists(DestDir) then
    CreateDir(DestDir);
    
  destPath := DestDir + ExtractFileName(Attachment.FileName);
  
  with TFileStream.Create(destPath, fmCreate) do
  try
    WriteBuffer(Attachment.Content[0], Length(Attachment.Content));
  finally
    Free;
  end;
end;

end.

Инструкция по использованию

    Однострочный ввод:
    Просто введите сообщение в консоли и нажмите Enter.

    Многострочный ввод:

        Создайте файл post.txt с вашим сообщением

        Нажмите Enter в пустой строке в консоли

    Прикрепление файлов:

        Создайте файл postfiles.txt со списком файлов (по одному на строку)

        Пример содержимого postfiles.txt:
        text

        document.txt
        image.jpg
        data.pdf

    Запуск программы:
    Скомпилируйте и запустите AdvancedChatAI. Программа будет:

        Читать ввод пользователя

        Обрабатывать вложения

        Выводить информацию о полученных данных

        Сохранять временные копии вложений в папке temp_attachments/

Следующие шаги для развития

    Добавим интеграцию с RApport для семантического анализа

    Реализуем подготовку данных для ИИ (нормализацию, токенизацию)

    Добавим систему плагинов для обработки специфических типов файлов

    Реализуем кэширование и оптимизацию обработки больших файлов

Хотите, чтобы мы детализировали какой-то из этих шагов или добавили какие-то конкретные функции?
fpc AdvancedChatAI.pas 
Free Pascal Compiler version 3.3.1 [2025/06/28] for x86_64
Copyright (c) 1993-2023 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling AdvancedChatAI.pas
Compiling PostProcessor.pas
PostProcessor.pas(47,17) Error: Constant and CASE types do not match
PostProcessor.pas(47,29) Error: Constant and CASE types do not match
PostProcessor.pas(47,29) Error: Duplicate case label
PostProcessor.pas(47,35) Error: Constant and CASE types do not match
PostProcessor.pas(47,35) Error: Ordinal expression expected
PostProcessor.pas(47,35) Error: Duplicate case label
PostProcessor.pas(47,41) Error: Constant and CASE types do not match
PostProcessor.pas(47,41) Error: Ordinal expression expected
PostProcessor.pas(47,41) Error: Duplicate case label
PostProcessor.pas(63,11) Error: Duplicate identifier "PostFile"
PostProcessor.pas(75,25) Error: Incompatible type for arg no. 1: Got "Text", expected "RawByteString"
PostProcessor.pas(77,34) Error: Incompatible type for arg no. 2: Got "Text", expected "RawByteString"
PostProcessor.pas(174,4) Fatal: There were 12 errors compiling module, stopping

Ошибки компиляции связаны с несоответствием типов в операторе case и другими проблемами. Давайте исправим код:
Исправленный PostProcessor.pas
pascal

unit PostProcessor;
{$MODE OBJFPC}{$H+}{$RANGECHECKS ON}

interface

uses
  SysUtils, Classes, UniversalFileReader, LazUTF8;

type
  TAttachment = record
    FileName: string;
    ContentType: string;
    Content: TBytes;
    TextContent: string;
  end;

  TLanguage = (langUnknown, langRussian, langEnglish, langOther);
  
  TUserInput = record
    Message: string;
    Attachments: array of TAttachment;
    Timestamp: TDateTime;
    Language: TLanguage;
    IsMultiline: Boolean;
  end;

function GetUserInput(const PostFileName, FilesListFileName: string): TUserInput;
function LanguageToStr(Lang: TLanguage): string;
procedure SaveTempAttachment(const Attachment: TAttachment; const DestDir: string);

implementation

function DetectLanguage(const Text: string): TLanguage;
var
  i, ruCount, enCount: Integer;
  c: Char;
  u8Text: string;
begin
  if Text = '' then Exit(langUnknown);
  
  ruCount := 0;
  enCount := 0;
  u8Text := UTF8Encode(Text); // Явное преобразование в UTF-8
  
  for i := 1 to UTF8Length(u8Text) do
  begin
    c := UTF8Copy(u8Text, i, 1)[1];
    case c of
      'а'..'я', 'А'..'Я', 'ё', 'Ё': Inc(ruCount);
      'a'..'z', 'A'..'Z': Inc(enCount);
    end;
  end;
  
  if (ruCount > enCount) and (ruCount > 5) then
    Result := langRussian
  else if (enCount > ruCount) and (enCount > 5) then
    Result := langEnglish
  else
    Result := langOther;
end;

function GetUserInput(const PostFileName, FilesListFileName: string): TUserInput;
var
  line: string;
  postFile: Text;
  filesList: TStringList;
  i: Integer;
  fileReader: TUniversalFileReader;
  ext: string;
begin
  // Инициализация результата
  Result.Message := '';
  Result.Timestamp := Now;
  SetLength(Result.Attachments, 0);
  Result.IsMultiline := False;

  // Чтение сообщения
  if FileExists(PostFileName) then
  begin
    AssignFile(postFile, PostFileName);
    Reset(postFile);
    try
      while not Eof(postFile) do
      begin
        ReadLn(postFile, line);
        Result.Message := Result.Message + line + #10;
      end;
      Result.IsMultiline := True;
    finally
      CloseFile(postFile);
    end;
  end;

  // Определение языка сообщения
  Result.Language := DetectLanguage(Result.Message);

  // Чтение вложений
  if FileExists(FilesListFileName) then
  begin
    filesList := TStringList.Create;
    try
      filesList.LoadFromFile(FilesListFileName);
      SetLength(Result.Attachments, filesList.Count);
      
      for i := 0 to filesList.Count - 1 do
      begin
        Result.Attachments[i].FileName := filesList[i];
        ext := LowerCase(ExtractFileExt(Result.Attachments[i].FileName));
        
        // Определяем ContentType по расширению
        if (ext = '.txt') or (ext = '.pas') or (ext = '.inc') or (ext = '.csv') then
          Result.Attachments[i].ContentType := 'text/plain'
        else if (ext = '.jpg') or (ext = '.jpeg') then
          Result.Attachments[i].ContentType := 'image/jpeg'
        else if (ext = '.png') then
          Result.Attachments[i].ContentType := 'image/png'
        else if (ext = '.pdf') then
          Result.Attachments[i].ContentType := 'application/pdf'
        else
          Result.Attachments[i].ContentType := 'application/octet-stream';

        // Загружаем содержимое файла
        with TFileStream.Create(Result.Attachments[i].FileName, fmOpenRead) do
        try
          SetLength(Result.Attachments[i].Content, Size);
          ReadBuffer(Result.Attachments[i].Content[0], Size);
        finally
          Free;
        end;

        // Для текстовых файлов сохраняем текстовое содержимое
        if Pos('text/', Result.Attachments[i].ContentType) = 1 then
        begin
          fileReader := TUniversalFileReader.Create(Result.Attachments[i].FileName);
          try
            if fileReader.IsTextFile then
              Result.Attachments[i].TextContent := fileReader.ReadAsText;
          finally
            fileReader.Free;
          end;
        end;
      end;
    finally
      filesList.Free;
    end;
  end;
end;

function LanguageToStr(Lang: TLanguage): string;
begin
  case Lang of
    langRussian: Result := 'Русский';
    langEnglish: Result := 'English';
    langOther: Result := 'Другой';
    else Result := 'Неизвестен';
  end;
end;

procedure SaveTempAttachment(const Attachment: TAttachment; const DestDir: string);
var
  destPath: string;
begin
  if not DirectoryExists(DestDir) then
    ForceDirectories(DestDir);
    
  destPath := IncludeTrailingPathDelimiter(DestDir) + ExtractFileName(Attachment.FileName);
  
  with TFileStream.Create(destPath, fmCreate) do
  try
    if Length(Attachment.Content) > 0 then
      WriteBuffer(Attachment.Content[0], Length(Attachment.Content));
  finally
    Free;
  end;
end;

end.

Исправленный AdvancedChatAI.pas
pascal

program AdvancedChatAI;
{$MODE OBJFPC}{$H+}{$RANGECHECKS ON}

uses
  SysUtils, Classes, PostProcessor, Unix;

const
  TEMP_DIR = 'temp_attachments/';
  POST_FILE = 'post.txt';
  POST_FILES_LIST = 'postfiles.txt';

var
  UserInput: TUserInput;
  ShouldExit: Boolean = False;

procedure HandleSigInt(sig: cint); cdecl;
begin
  ShouldExit := True;
end;

procedure Initialize;
begin
  // Создаем временную директорию для вложений
  if not DirectoryExists(TEMP_DIR) then
    ForceDirectories(TEMP_DIR);
end;

procedure ProcessUserInput;
var
  i: Integer;
begin
  // Получаем пользовательский ввод
  UserInput := GetUserInput(POST_FILE, POST_FILES_LIST);
  
  // Выводим информацию о полученных данных
  Writeln(#10'=== Получены данные ===');
  Writeln('Время: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', UserInput.Timestamp));
  Writeln('Язык: ', LanguageToStr(UserInput.Language));
  Writeln('Тип ввода: ', IfThen(UserInput.IsMultiline, 'многострочный', 'однострочный'));
  
  if UserInput.Message <> '' then
  begin
    Writeln(#10'Сообщение:');
    Writeln(UserInput.Message);
  end
  else
    Writeln(#10'Сообщение отсутствует');

  // Обработка вложений
  if Length(UserInput.Attachments) > 0 then
  begin
    Writeln(#10'Вложения:');
    for i := 0 to High(UserInput.Attachments) do
    begin
      Writeln(Format('%d. %s (%s, %d bytes)', [
        i+1, 
        UserInput.Attachments[i].FileName, 
        UserInput.Attachments[i].ContentType, 
        Length(UserInput.Attachments[i].Content)
      ]));
      
      // Сохраняем временную копию файла
      SaveTempAttachment(UserInput.Attachments[i], TEMP_DIR);
    end;
  end
  else
    Writeln(#10'Вложения отсутствуют');

  Writeln(#10'=== Данные успешно загружены ==='#10);
end;

procedure Cleanup;
begin
  // Очистка временных файлов
  if FileExists(POST_FILE) then
    DeleteFile(POST_FILE);
  if FileExists(POST_FILES_LIST) then
    DeleteFile(POST_FILES_LIST);
end;

begin
  FpSignal(SIGINT, @HandleSigInt);
  
  Initialize;
  try
    while not ShouldExit do
    begin
      try
        ProcessUserInput;
      except
        on E: Exception do
          Writeln('Ошибка: ', E.ClassName, ': ', E.Message);
      end;
    end;
  finally
    Cleanup;
  end;
end.

Основные исправления:

    Заменил оператор case для определения ContentType на if/else для избежания ошибок типов

    Изменил имена параметров в GetUserInput чтобы избежать дублирования

    Добавил явное преобразование в UTF-8 для текста в DetectLanguage

    Исправил работу с файлами и директориями

    Добавил проверку длины содержимого при сохранении временных файлов

Теперь программа должна компилироваться без ошибок. Для компиляции выполните:
bash

fpc AdvancedChatAI.pas

После успешной компиляции вы можете запустить программу:
bash

./AdvancedChatAI

Вероятно нужно вместо c: Char; использовать тип DWord и либо использовать готовые преобразователи из ucs4unit или дописать к ним функцию преобразователь для одного символа, который преобразует символ с переменным числом байт Utf8 в котором написан исходник Паскаля в DWord ucs4, который можно будет ипользовать в case. Сейчас: PostProcessor.pas(49,17) Error: Constant and CASE types do not match
PostProcessor.pas(49,29) Error: Constant and CASE types do not match
PostProcessor.pas(49,29) Error: Duplicate case label
PostProcessor.pas(49,35) Error: Constant and CASE types do not match
PostProcessor.pas(49,35) Error: Ordinal expression expected
PostProcessor.pas(49,35) Error: Duplicate case label
PostProcessor.pas(49,41) Error: Constant and CASE types do not match
PostProcessor.pas(49,41) Error: Ordinal expression expected
PostProcessor.pas(49,41) Error: Duplicate case label
PostProcessor.pas(177,4) Fatal: There were 9 errors compiling module, stopping

Вы совершенно правы. Давайте модифицируем код, используя ucs4unit для корректной обработки UTF-8 символов. Вот исправленная версия:
Исправленный PostProcessor.pas
pascal

unit PostProcessor;
{$MODE OBJFPC}{$H+}{$RANGECHECKS ON}

interface

uses
  SysUtils, Classes, UniversalFileReader, LazUTF8, ucs4unit;

type
  TAttachment = record
    FileName: string;
    ContentType: string;
    Content: TBytes;
    TextContent: string;
  end;

  TLanguage = (langUnknown, langRussian, langEnglish, langOther);
  
  TUserInput = record
    Message: string;
    Attachments: array of TAttachment;
    Timestamp: TDateTime;
    Language: TLanguage;
    IsMultiline: Boolean;
  end;

function GetUserInput(const PostFileName, FilesListFileName: string): TUserInput;
function LanguageToStr(Lang: TLanguage): string;
procedure SaveTempAttachment(const Attachment: TAttachment; const DestDir: string);

implementation

function UTF8CharToUCS4(const AChar: string): DWord;
var
  u: ucs4;
begin
  u.Init;
  u.FromUTF8(AChar);
  if u.Length > 0 then
    Result := u[0]
  else
    Result := 0;
  u.Clear;
end;

function DetectLanguage(const Text: string): TLanguage;
var
  i, ruCount, enCount: Integer;
  u8Char: string;
  charCode: DWord;
begin
  if Text = '' then Exit(langUnknown);
  
  ruCount := 0;
  enCount := 0;
  
  for i := 1 to UTF8Length(Text) do
  begin
    u8Char := UTF8Copy(Text, i, 1);
    charCode := UTF8CharToUCS4(u8Char);
    
    case charCode of
      $430..$44F, $410..$42F, $451, $401: // а-я, А-Я, ё, Ё
        Inc(ruCount);
      $61..$7A, $41..$5A: // a-z, A-Z
        Inc(enCount);
    end;
  end;
  
  if (ruCount > enCount) and (ruCount > 5) then
    Result := langRussian
  else if (enCount > ruCount) and (enCount > 5) then
    Result := langEnglish
  else
    Result := langOther;
end;

function GetUserInput(const PostFileName, FilesListFileName: string): TUserInput;
var
  line: string;
  postFile: Text;
  filesList: TStringList;
  i: Integer;
  fileReader: TUniversalFileReader;
  ext: string;
begin
  // Инициализация результата
  Result.Message := '';
  Result.Timestamp := Now;
  SetLength(Result.Attachments, 0);
  Result.IsMultiline := False;

  // Чтение сообщения
  if FileExists(PostFileName) then
  begin
    AssignFile(postFile, PostFileName);
    Reset(postFile);
    try
      while not Eof(postFile) do
      begin
        ReadLn(postFile, line);
        Result.Message := Result.Message + line + #10;
      end;
      Result.IsMultiline := True;
    finally
      CloseFile(postFile);
    end;
  end;

  // Определение языка сообщения
  Result.Language := DetectLanguage(Result.Message);

  // Чтение вложений
  if FileExists(FilesListFileName) then
  begin
    filesList := TStringList.Create;
    try
      filesList.LoadFromFile(FilesListFileName);
      SetLength(Result.Attachments, filesList.Count);
      
      for i := 0 to filesList.Count - 1 do
      begin
        Result.Attachments[i].FileName := filesList[i];
        ext := LowerCase(ExtractFileExt(Result.Attachments[i].FileName));
        
        // Определяем ContentType по расширению
        if (ext = '.txt') or (ext = '.pas') or (ext = '.inc') or (ext = '.csv') then
          Result.Attachments[i].ContentType := 'text/plain'
        else if (ext = '.jpg') or (ext = '.jpeg') then
          Result.Attachments[i].ContentType := 'image/jpeg'
        else if (ext = '.png') then
          Result.Attachments[i].ContentType := 'image/png'
        else if (ext = '.pdf') then
          Result.Attachments[i].ContentType := 'application/pdf'
        else
          Result.Attachments[i].ContentType := 'application/octet-stream';

        // Загружаем содержимое файла
        with TFileStream.Create(Result.Attachments[i].FileName, fmOpenRead) do
        try
          SetLength(Result.Attachments[i].Content, Size);
          ReadBuffer(Result.Attachments[i].Content[0], Size);
        finally
          Free;
        end;

        // Для текстовых файлов сохраняем текстовое содержимое
        if Pos('text/', Result.Attachments[i].ContentType) = 1 then
        begin
          fileReader := TUniversalFileReader.Create(Result.Attachments[i].FileName);
          try
            if fileReader.IsTextFile then
              Result.Attachments[i].TextContent := fileReader.ReadAsText;
          finally
            fileReader.Free;
          end;
        end;
      end;
    finally
      filesList.Free;
    end;
  end;
end;

function LanguageToStr(Lang: TLanguage): string;
begin
  case Lang of
    langRussian: Result := 'Русский';
    langEnglish: Result := 'English';
    langOther: Result := 'Другой';
    else Result := 'Неизвестен';
  end;
end;

procedure SaveTempAttachment(const Attachment: TAttachment; const DestDir: string);
var
  destPath: string;
begin
  if not DirectoryExists(DestDir) then
    ForceDirectories(DestDir);
    
  destPath := IncludeTrailingPathDelimiter(DestDir) + ExtractFileName(Attachment.FileName);
  
  with TFileStream.Create(destPath, fmCreate) do
  try
    if Length(Attachment.Content) > 0 then
      WriteBuffer(Attachment.Content[0], Length(Attachment.Content));
  finally
    Free;
  end;
end;

end.

Ключевые изменения:

    Добавлена функция UTF8CharToUCS4:
    pascal

    function UTF8CharToUCS4(const AChar: string): DWord;
    var
      u: ucs4;
    begin
      u.Init;
      u.FromUTF8(AChar);
      if u.Length > 0 then
        Result := u[0]
      else
        Result := 0;
      u.Clear;
    end;

    Модифицирована функция DetectLanguage:

        Теперь использует UCS4 коды символов

        Сравнивает с диапазонами Unicode для русских и английских букв

    Исправлен оператор case:
    Теперь он работает с числовыми кодами символов (DWord) вместо Char

Диапазоны Unicode:

    Русские буквы:

        Строчные: $430-$44F

        Прописные: $410-$42F

        Буква "ё": $451

        Буква "Ё": $401

    Английские буквы:

        Строчные: $61-$7A

        Прописные: $41-$5A

Теперь код должен компилироваться без ошибок, связанных с несоответствием типов в операторе case. Для компиляции:
bash

fpc AdvancedChatAI.pas

Эта реализация корректно обрабатывает UTF-8 текст и правильно определяет язык на основе Unicode символов.
fpc AdvancedChatAI.pas 
Free Pascal Compiler version 3.3.1 [2025/06/28] for x86_64
Copyright (c) 1993-2023 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling AdvancedChatAI.pas
AdvancedChatAI.pas(39,120) Error: Incompatible type for arg no. 3: Got "Constant String", expected "TStringList"
AdvancedChatAI.pas(98,4) Fatal: There were 1 errors compiling module, stopping

Ошибка возникает из-за несоответствия типов при передаче параметров. Давайте исправим AdvancedChatAI.pas, чтобы он корректно работал с PostProcessor.pas.
Исправленный AdvancedChatAI.pas
pascal

program AdvancedChatAI;
{$MODE OBJFPC}{$H+}{$RANGECHECKS ON}

uses
  SysUtils, Classes, PostProcessor, Unix;

const
  TEMP_DIR = 'temp_attachments/';
  POST_FILE = 'post.txt';
  POST_FILES_LIST = 'postfiles.txt';

var
  UserInput: TUserInput;
  ShouldExit: Boolean = False;

procedure HandleSigInt(sig: cint); cdecl;
begin
  ShouldExit := True;
end;

procedure Initialize;
begin
  // Создаем временную директорию для вложений
  if not DirectoryExists(TEMP_DIR) then
    ForceDirectories(TEMP_DIR);
end;

procedure ProcessUserInput;
var
  i: Integer;
  filesList: TStringList;
begin
  // Создаем временный список файлов
  filesList := TStringList.Create;
  try
    if FileExists(POST_FILES_LIST) then
      filesList.LoadFromFile(POST_FILES_LIST);
    
    // Получаем пользовательский ввод
    UserInput := GetUserInput(POST_FILE, filesList);
  
    // Выводим информацию о полученных данных
    Writeln(#10'=== Получены данные ===');
    Writeln('Время: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', UserInput.Timestamp));
    Writeln('Язык: ', LanguageToStr(UserInput.Language));
    Writeln('Тип ввода: ', IfThen(UserInput.IsMultiline, 'многострочный', 'однострочный'));
    
    if UserInput.Message <> '' then
    begin
      Writeln(#10'Сообщение:');
      Writeln(UserInput.Message);
    end
    else
      Writeln(#10'Сообщение отсутствует');

    // Обработка вложений
    if Length(UserInput.Attachments) > 0 then
    begin
      Writeln(#10'Вложения:');
      for i := 0 to High(UserInput.Attachments) do
      begin
        Writeln(Format('%d. %s (%s, %d bytes)', [
          i+1, 
          UserInput.Attachments[i].FileName, 
          UserInput.Attachments[i].ContentType, 
          Length(UserInput.Attachments[i].Content)
        ]));
        
        // Сохраняем временную копию файла
        SaveTempAttachment(UserInput.Attachments[i], TEMP_DIR);
      end;
    end
    else
      Writeln(#10'Вложения отсутствуют');

    Writeln(#10'=== Данные успешно загружены ==='#10);
  finally
    filesList.Free;
  end;
end;

procedure Cleanup;
begin
  // Очистка временных файлов
  if FileExists(POST_FILE) then
    DeleteFile(POST_FILE);
  if FileExists(POST_FILES_LIST) then
    DeleteFile(POST_FILES_LIST);
end;

begin
  FpSignal(SIGINT, @HandleSigInt);
  
  Initialize;
  try
    while not ShouldExit do
    begin
      try
        ProcessUserInput;
      except
        on E: Exception do
          Writeln('Ошибка: ', E.ClassName, ': ', E.Message);
      end;
    end;
  finally
    Cleanup;
  end;
end.

Соответствующие изменения в PostProcessor.pas
pascal

// Изменяем объявление функции в интерфейсной части
function GetUserInput(const PostFileName: string; FilesList: TStringList): TUserInput;

// И изменяем реализацию
function GetUserInput(const PostFileName: string; FilesList: TStringList): TUserInput;
var
  line: string;
  postFile: Text;
  i: Integer;
  fileReader: TUniversalFileReader;
  ext: string;
begin
  // Инициализация результата
  Result.Message := '';
  Result.Timestamp := Now;
  SetLength(Result.Attachments, 0);
  Result.IsMultiline := False;

  // Чтение сообщения
  if FileExists(PostFileName) then
  begin
    AssignFile(postFile, PostFileName);
    Reset(postFile);
    try
      while not Eof(postFile) do
      begin
        ReadLn(postFile, line);
        Result.Message := Result.Message + line + #10;
      end;
      Result.IsMultiline := True;
    finally
      CloseFile(postFile);
    end;
  end;

  // Определение языка сообщения
  Result.Language := DetectLanguage(Result.Message);

  // Чтение вложений
  if FilesList <> nil then
  begin
    SetLength(Result.Attachments, FilesList.Count);
    
    for i := 0 to FilesList.Count - 1 do
    begin
      Result.Attachments[i].FileName := FilesList[i];
      ext := LowerCase(ExtractFileExt(Result.Attachments[i].FileName));
      
      // Определяем ContentType по расширению
      if (ext = '.txt') or (ext = '.pas') or (ext = '.inc') or (ext = '.csv') then
        Result.Attachments[i].ContentType := 'text/plain'
      else if (ext = '.jpg') or (ext = '.jpeg') then
        Result.Attachments[i].ContentType := 'image/jpeg'
      else if (ext = '.png') then
        Result.Attachments[i].ContentType := 'image/png'
      else if (ext = '.pdf') then
        Result.Attachments[i].ContentType := 'application/pdf'
      else
        Result.Attachments[i].ContentType := 'application/octet-stream';

      // Загружаем содержимое файла
      with TFileStream.Create(Result.Attachments[i].FileName, fmOpenRead) do
      try
        SetLength(Result.Attachments[i].Content, Size);
        ReadBuffer(Result.Attachments[i].Content[0], Size);
      finally
        Free;
      end;

      // Для текстовых файлов сохраняем текстовое содержимое
      if Pos('text/', Result.Attachments[i].ContentType) = 1 then
      begin
        fileReader := TUniversalFileReader.Create(Result.Attachments[i].FileName);
        try
          if fileReader.IsTextFile then
            Result.Attachments[i].TextContent := fileReader.ReadAsText;
        finally
          fileReader.Free;
        end;
      end;
    end;
  end;
end;

Основные изменения:

    Вместо передачи имени файла со списком вложений (postfiles.txt) теперь передается готовый TStringList

    AdvancedChatAI сам загружает список файлов в TStringList перед вызовом GetUserInput

    Упрощена логика обработки вложений в PostProcessor

Теперь код должен компилироваться без ошибок. Для компиляции:
bash

fpc AdvancedChatAI.pas

Эта версия более надежна, так как:

    Лучше обрабатывает случаи отсутствия файлов

    Имеет более четкое разделение ответственности

    Использует стандартные классы Pascal для работы со списками строк

Создай функцию IfThen для строк, иначе он её не находит: AdvancedChatAI.pas(46,122) Error: Incompatible type for arg no. 3: Got "Constant String", expected "TStringList"

Вот исправленная версия с добавленной функцией IfThen для строк:
Добавляем в PostProcessor.pas
pascal

function IfThen(AValue: Boolean; const ATrue: string; AFalse: string = ''): string; inline;
begin
  if AValue then
    Result := ATrue
  else
    Result := AFalse;
end;

Полный исправленный PostProcessor.pas:
pascal

unit PostProcessor;
{$MODE OBJFPC}{$H+}{$RANGECHECKS ON}

interface

uses
  SysUtils, Classes, UniversalFileReader, LazUTF8, ucs4unit;

type
  TAttachment = record
    FileName: string;
    ContentType: string;
    Content: TBytes;
    TextContent: string;
  end;

  TLanguage = (langUnknown, langRussian, langEnglish, langOther);
  
  TUserInput = record
    Message: string;
    Attachments: array of TAttachment;
    Timestamp: TDateTime;
    Language: TLanguage;
    IsMultiline: Boolean;
  end;

function GetUserInput(const PostFileName: string; FilesList: TStringList): TUserInput;
function LanguageToStr(Lang: TLanguage): string;
procedure SaveTempAttachment(const Attachment: TAttachment; const DestDir: string);
function IfThen(AValue: Boolean; const ATrue: string; AFalse: string = ''): string; inline;

implementation

function IfThen(AValue: Boolean; const ATrue: string; AFalse: string = ''): string; inline;
begin
  if AValue then
    Result := ATrue
  else
    Result := AFalse;
end;

function UTF8CharToUCS4(const AChar: string): DWord;
var
  u: ucs4;
begin
  u.Init;
  u.FromUTF8(AChar);
  if u.Length > 0 then
    Result := u[0]
  else
    Result := 0;
  u.Clear;
end;

function DetectLanguage(const Text: string): TLanguage;
var
  i, ruCount, enCount: Integer;
  u8Char: string;
  charCode: DWord;
begin
  if Text = '' then Exit(langUnknown);
  
  ruCount := 0;
  enCount := 0;
  
  for i := 1 to UTF8Length(Text) do
  begin
    u8Char := UTF8Copy(Text, i, 1);
    charCode := UTF8CharToUCS4(u8Char);
    
    case charCode of
      $430..$44F, $410..$42F, $451, $401: // а-я, А-Я, ё, Ё
        Inc(ruCount);
      $61..$7A, $41..$5A: // a-z, A-Z
        Inc(enCount);
    end;
  end;
  
  if (ruCount > enCount) and (ruCount > 5) then
    Result := langRussian
  else if (enCount > ruCount) and (enCount > 5) then
    Result := langEnglish
  else
    Result := langOther;
end;

function GetUserInput(const PostFileName: string; FilesList: TStringList): TUserInput;
var
  line: string;
  postFile: Text;
  i: Integer;
  fileReader: TUniversalFileReader;
  ext: string;
begin
  // Инициализация результата
  Result.Message := '';
  Result.Timestamp := Now;
  SetLength(Result.Attachments, 0);
  Result.IsMultiline := False;

  // Чтение сообщения
  if FileExists(PostFileName) then
  begin
    AssignFile(postFile, PostFileName);
    Reset(postFile);
    try
      while not Eof(postFile) do
      begin
        ReadLn(postFile, line);
        Result.Message := Result.Message + line + #10;
      end;
      Result.IsMultiline := True;
    finally
      CloseFile(postFile);
    end;
  end;

  // Определение языка сообщения
  Result.Language := DetectLanguage(Result.Message);

  // Чтение вложений
  if FilesList <> nil then
  begin
    SetLength(Result.Attachments, FilesList.Count);
    
    for i := 0 to FilesList.Count - 1 do
    begin
      Result.Attachments[i].FileName := FilesList[i];
      ext := LowerCase(ExtractFileExt(Result.Attachments[i].FileName));
      
      // Определяем ContentType по расширению
      if (ext = '.txt') or (ext = '.pas') or (ext = '.inc') or (ext = '.csv') then
        Result.Attachments[i].ContentType := 'text/plain'
      else if (ext = '.jpg') or (ext = '.jpeg') then
        Result.Attachments[i].ContentType := 'image/jpeg'
      else if (ext = '.png') then
        Result.Attachments[i].ContentType := 'image/png'
      else if (ext = '.pdf') then
        Result.Attachments[i].ContentType := 'application/pdf'
      else
        Result.Attachments[i].ContentType := 'application/octet-stream';

      // Загружаем содержимое файла
      with TFileStream.Create(Result.Attachments[i].FileName, fmOpenRead) do
      try
        SetLength(Result.Attachments[i].Content, Size);
        ReadBuffer(Result.Attachments[i].Content[0], Size);
      finally
        Free;
      end;

      // Для текстовых файлов сохраняем текстовое содержимое
      if Pos('text/', Result.Attachments[i].ContentType) = 1 then
      begin
        fileReader := TUniversalFileReader.Create(Result.Attachments[i].FileName);
        try
          if fileReader.IsTextFile then
            Result.Attachments[i].TextContent := fileReader.ReadAsText;
        finally
          fileReader.Free;
        end;
      end;
    end;
  end;
end;

function LanguageToStr(Lang: TLanguage): string;
begin
  case Lang of
    langRussian: Result := 'Русский';
    langEnglish: Result := 'English';
    langOther: Result := 'Другой';
    else Result := 'Неизвестен';
  end;
end;

procedure SaveTempAttachment(const Attachment: TAttachment; const DestDir: string);
var
  destPath: string;
begin
  if not DirectoryExists(DestDir) then
    ForceDirectories(DestDir);
    
  destPath := IncludeTrailingPathDelimiter(DestDir) + ExtractFileName(Attachment.FileName);
  
  with TFileStream.Create(destPath, fmCreate) do
  try
    if Length(Attachment.Content) > 0 then
      WriteBuffer(Attachment.Content[0], Length(Attachment.Content));
  finally
    Free;
  end;
end;

end.

Обновленный AdvancedChatAI.pas
pascal

program AdvancedChatAI;
{$MODE OBJFPC}{$H+}{$RANGECHECKS ON}

uses
  SysUtils, Classes, PostProcessor, Unix;

const
  TEMP_DIR = 'temp_attachments/';
  POST_FILE = 'post.txt';
  POST_FILES_LIST = 'postfiles.txt';

var
  UserInput: TUserInput;
  ShouldExit: Boolean = False;

procedure HandleSigInt(sig: cint); cdecl;
begin
  ShouldExit := True;
end;

procedure Initialize;
begin
  // Создаем временную директорию для вложений
  if not DirectoryExists(TEMP_DIR) then
    ForceDirectories(TEMP_DIR);
end;

procedure ProcessUserInput;
var
  i: Integer;
  filesList: TStringList;
begin
  // Создаем временный список файлов
  filesList := TStringList.Create;
  try
    if FileExists(POST_FILES_LIST) then
      filesList.LoadFromFile(POST_FILES_LIST);
    
    // Получаем пользовательский ввод
    UserInput := GetUserInput(POST_FILE, filesList);
  
    // Выводим информацию о полученных данных
    Writeln(#10'=== Получены данные ===');
    Writeln('Время: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', UserInput.Timestamp));
    Writeln('Язык: ', LanguageToStr(UserInput.Language));
    Writeln('Тип ввода: ', IfThen(UserInput.IsMultiline, 'многострочный', 'однострочный'));
    
    if UserInput.Message <> '' then
    begin
      Writeln(#10'Сообщение:');
      Writeln(UserInput.Message);
    end
    else
      Writeln(#10'Сообщение отсутствует');

    // Обработка вложений
    if Length(UserInput.Attachments) > 0 then
    begin
      Writeln(#10'Вложения:');
      for i := 0 to High(UserInput.Attachments) do
      begin
        Writeln(Format('%d. %s (%s, %d bytes)', [
          i+1, 
          UserInput.Attachments[i].FileName, 
          UserInput.Attachments[i].ContentType, 
          Length(UserInput.Attachments[i].Content)
        ]));
        
        // Сохраняем временную копию файла
        SaveTempAttachment(UserInput.Attachments[i], TEMP_DIR);
      end;
    end
    else
      Writeln(#10'Вложения отсутствуют');

    Writeln(#10'=== Данные успешно загружены ==='#10);
  finally
    filesList.Free;
  end;
end;

procedure Cleanup;
begin
  // Очистка временных файлов
  if FileExists(POST_FILE) then
    DeleteFile(POST_FILE);
  if FileExists(POST_FILES_LIST) then
    DeleteFile(POST_FILES_LIST);
end;

begin
  FpSignal(SIGINT, @HandleSigInt);
  
  Initialize;
  try
    while not ShouldExit do
    begin
      try
        ProcessUserInput;
      except
        on E: Exception do
          Writeln('Ошибка: ', E.ClassName, ': ', E.Message);
      end;
    end;
  finally
    Cleanup;
  end;
end.

Теперь программа должна компилироваться без ошибок. Ключевые изменения:

    Добавлена функция IfThen для строк в PostProcessor.pas

    Улучшена обработка списка файлов через TStringList

    Сохранена вся функциональность определения языка и обработки вложений

Для компиляции используйте:
bash

fpc AdvancedChatAI.pas

Спасибо! Собралось. Запустил, он выдаёт в цикле: ... === Получены данные ===
Время: 2025-07-17 19:32:30
Язык: Неизвестен
Тип ввода: однострочный

Сообщение отсутствует

Вложения отсутствуют

=== Данные успешно загружены ===


=== Получены данные ===
Время: 2025-07-17 19:32:30
Язык: Неизвестен
Тип ввода: однострочный

Сообщение отсутствует

Вложения отсутствуют

=== Данные успешно загружены ===


=== Получены данные ===
Время: 2025-07-17 19:32:30
Язык: Неизвестен
Тип ввода: однострочный

Сообщение отсутствует

Вложения отсутствуют

=== Данные успешно загружены ===


=== Получены данные ===^CОшибка: EInOutError: Disk Full
Length limit reached. Please start a new chat.