Как создать форму с запросом к ИИ и выводом результата на странице

Введение 

В этой статье разберем типовое решение для реализации формы, у которой на входе есть какие-то параметры, при нажатии на кнопку идет запрос к ИИ и отклик выдается на странице. 

Пример берем с сайта Web-automation.ru, где собраны различные формы с ИИ (все они сделаны по одному принципу).  

Здесь мы разбираем эту форму - https://web-automation.ru/go/ai-gen-header

Создание таблиц БД

Эти таблицы мы задействуем в форме и в запросе API

CREATE TABLE [dbo].[as_apiProfiles](
	[id] [int] IDENTITY(1,1) NOT NULL,
	[code] [nvarchar](128) NULL,
	[options] [nvarchar](max) NULL,
	[name] [nvarchar](256) NULL,
	[apiCode] [nvarchar](128) NULL,
	[roles] [nvarchar](256) NULL,
	[description] [nvarchar](max) NULL,
	[shortDesc] [nvarchar](512) NULL,
 CONSTRAINT [PK_as_apiProfiles] PRIMARY KEY CLUSTERED 
(
	[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

CREATE TABLE [dbo].[as_apiProfileLog](
	[id] [int] IDENTITY(1,1) NOT NULL,
	[profileID] [int] NULL,
	[created] [datetime] NULL,
	[createdBy] [nvarchar](128) NULL,
	[request] [nvarchar](max) NULL,
	[response] [nvarchar](max) NULL,
	[jsonResponse] [nvarchar](max) NULL,
	[g] [uniqueidentifier] NULL,
	[tokens] [int] NULL,
	[requestAdditional] [nvarchar](max) NULL,
	[commandID] [int] NULL,
	[commandMsg] [nvarchar](max) NULL,
 CONSTRAINT [PK_as_apiProfileLog] PRIMARY KEY CLUSTERED 
(
	[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [dbo].[as_apiProfileLog]  WITH CHECK ADD  CONSTRAINT [FK__as_apiPro__profi__068F6FDA] FOREIGN KEY([profileID])
REFERENCES [dbo].[as_apiProfiles] ([id])
ON DELETE CASCADE
GO

ALTER TABLE [dbo].[as_apiProfileLog] CHECK CONSTRAINT [FK__as_apiPro__profi__068F6FDA]
GO

Реализация формы

Кастом разметка формы (слева форма, справа будет вывод результата).

{form-title}
{form-subtitle}
<div class="row mb-2 no-gutters">
  <div class="col-12 col-sm-6 col-md-6 order-2 order-sm-1">
    <div class="d-flex h-100">
      <div class="w-100 justify-content-center align-self-center1 mb-3 px-2">
        <div class="card mb-3 shadow-sm border-0">
          <div class="card-body p-3">
            <div class="mb-3">
              {colwithlabel-theme}
            </div>
            
            <div class="row mb-2">
              <div class="col-md-6">
                {colwithlabel-audience}
              </div>
              <div class="col-md-6">
                {colwithlabel-format}
              </div>
            </div>
            
            <div class="row mb-2">
              <div class="col-md-6">
                {colwithlabel-tone}
              </div>
              <div class="col-md-6">
                {colwithlabel-length}
              </div>
            </div>
            
            <div class="mb-2">
              {colwithlabel-seo}
            </div>
            
            <div class="mb-2">
              {colwithlabel-ignore}
            </div>
            
            <div class="mb-2">
              {colwithlabel-dopActions}
            </div>
            
            <div class="hide">
              {colcontrol-g}
            </div>
          </div>
        </div>

        <div class="text-center mt-3">
          {form-button}
        </div>

        <div class="small text-muted text-center mt-2">
          <i class="bi bi-clock me-1"></i>Генерация 10 заголовков займёт до 30 секунд
        </div>
      </div>
    </div>
  </div>

  <div class="col-12 col-md-6 order-1 order-sm-2">
    <div class="w-100 apichatResult d-flex align-items-center justify-content-center" style="min-height: 400px;">
      <img src="/uploads/robot.svg" alt="" class="img-fluid opacity-75" loading="lazy" width="280" height="280" style="display: block;">
      <div class="text-center" style="display: none;">
        <p class="text-muted small mt-2">Здесь появятся заголовки</p>
      </div>
    </div>
  </div>
</div>

GetItem формы (логирование посещения, настройка параметров формы): 

CREATE PROCEDURE [dbo].[fm_ai-gen-header_getItem]
    @itemID int,    
    @username nvarchar(256),
    @parameters ExtendedDictionaryParameter readonly
AS
BEGIN  
    declare @apiProfile nvarchar(max) = dbo.as_setting('defaultAI', 'ds1') 
    declare @profileID int, @roles nvarchar(max)
    select top 1 @profileID = id, @roles = roles from as_apiProfiles where code = @apiProfile
    declare @g uniqueidentifier = newid()
    
    exec service_logForm @formCode = 'ai-gen-header', @guid = @g, @username = @username, @itemID = @itemID, @parameters = @parameters
    
    select 1,
        @g g,
        '{"rows": 3,"speech": true}' options_theme,
        '{"rows": 1}' options_audience,
        '{"rows": 1}' options_format,
        '{"rows": 1}' options_tone,
        '{"rows": 1}' options_seo,
        '{"rows": 1}' options_length,
        '{"rows": 2}' options_ignore,
        '{"rows": 3,"speech": true}' options_dopActions,
        'Как выбрать идеальный матрас~~Как выбрать идеальный матрас||10 способов увеличить продажи в Instagram~~10 способов увеличить продажи в Instagram||Почему кошки мурлыкают: научное объяснение~~Почему кошки мурлыкают: научное объяснение||Как начать бегать с нуля~~Как начать бегать с нуля||Что такое нейросети и как их использовать~~Что такое нейросети и как их использовать||Как сбросить 10 кг без диет~~Как сбросить 10 кг без диет||Секреты идеального тайм-менеджмента~~Секреты идеального тайм-менеджмента' as example_theme,
        'Молодые родители 25–35 лет~~Молодые родители 25–35 лет||Владельцы малого бизнеса~~Владельцы малого бизнеса||Начинающие блогеры~~Начинающие блогеры||Любители ЗОЖ~~Любители ЗОЖ||IT-специалисты~~IT-специалисты||Студенты||Пенсионеры 60+~~Пенсионеры 60+||Молодежь 18–25 лет~~Молодежь 18–25 лет||Женщины в декрете~~Женщины в декрете' as example_audience,
        'Обучающая статья||Подборка||Новость/обзор||Исследование/аналитика||Лайфхак/совет||Вдохновляющая история||Мнение/колонка эксперта' as example_format,
        'Нейтральный/информативный||Интригующий||Эмоциональный||Вызывающий||Юмористический/ироничный' as example_tone,
        'Выбор||Продажи||Нейросети||Обучение||Идеальный подарок||Тренды' as example_seo,        'Короткий (до 50 символов)||Средний (50–70 символов)||Длинный (70–110 символов)' as example_length,
        'Кликбейт||Слова-паразиты||Штампы||Популизм||Избитые фразы' as example_ignore,
        'Добавить цифры в заголовки||Сделать упор на выгоду для читателя||Использовать сильные прилагательные||Обещать решение проблемы' as example_dopActions    
    select 'h1' HeaderTag, 
        1 LineLabel, 
        1 DisableFocusOnLoad
END

 

SaveItem формы (извлекаются параметры, формируется промпт и запуск внешнего действия по АПИ запросу к ИИ): 

CREATE PROCEDURE [dbo].[fm_ai-gen-header_saveItem]
   @username nvarchar(256),
   @itemID int,
   @parameters ExtendedDictionaryParameter readonly
AS
BEGIN
    declare @apiProfile nvarchar(max) = dbo.as_setting('defaultAI', 'ds1') 
    declare @g uniqueidentifier
    select @g = try_convert(uniqueidentifier, Value2) from @parameters where [key]='g'
    declare @profileID int, @apiCode nvarchar(max)
    select @profileID = id, @apiCode = apiCode from as_apiProfiles where code = @apiProfile
    
    declare @theme nvarchar(max)
    declare @audience nvarchar(max)
    declare @format nvarchar(max)
    declare @tone nvarchar(max)
    declare @seo nvarchar(max)
    declare @length nvarchar(max)
    declare @ignore nvarchar(max)
    declare @dopActions nvarchar(max)
    
    select @theme = Value2 from @parameters where [key]='theme'
    select @audience = Value2 from @parameters where [key]='audience'
    select @format = Value2 from @parameters where [key]='format'
    select @tone = Value2 from @parameters where [key]='tone'
    select @seo = Value2 from @parameters where [key]='seo'
    select @length = Value2 from @parameters where [key]='length'
    select @ignore = Value2 from @parameters where [key]='ignore'
    select @dopActions = Value2 from @parameters where [key]='dopActions'
    
    declare @systemPrompt nvarchar(max) = 
'Ты — профессиональный копирайтер и редактор с опытом создания вирусных заголовков. На основе предоставленных данных сгенерируй 10 вариантов заголовков для статьи.
Постарайся уложиться в 1000 токенов на ответ.
Входные данные:
- Тема статьи: ' + isnull(@theme, '') + '
- Целевая аудитория: ' + isnull(@audience, '') + '
- Формат статьи: ' + isnull(@format, '') + '
- Тон заголовка: ' + isnull(@tone, '') + '
- Ключевые слова для SEO: ' + isnull(@seo, 'не указаны') + ' (если указаны, обязательно включи их в заголовки)
- Желаемая длина заголовка: ' + isnull(@length, 'Любая') + '
- Чего следует избегать: ' + isnull(@ignore, 'не указано') + '
- Дополнительные пожелания: ' + isnull(@dopActions, 'нет') + '

Инструкции:
1. Проанализируй тему, аудиторию и формат статьи. Пойми, что будет важно и интересно читателю.
2. Сгенерируй ровно 10 уникальных заголовков. Каждый заголовок должен быть законченной фразой.
3. Учитывай тон: для нейтрального — факты и польза, для интригующего — вопросы и недосказанность, для эмоционального — сильные прилагательные, для провокационного — вызов и дискуссия, для юмористического — ирония и игра слов, для профессионального — деловой стиль.
4. Если указаны SEO-ключевые слова, органично включи их в заголовки.
5. Соблюдай желаемую длину заголовка (количество символов с пробелами):
   - short: до 50 символов
   - medium: 50–70 символов
   - long: 70–110 символов
   - any: любая длина
6. Избегай клише, штампов и указанных в поле "Чего следует избегать" элементов.
7. Разнообразь заголовки: используй разные конструкции (вопросы, списки, инструкции, интригу, цифры).
8. Формат вывода: пронумерованный список, каждый заголовок с новой строки.

Формат вывода:

1. [Заголовок 1]
2. [Заголовок 2]
3. [Заголовок 3]
4. [Заголовок 4]
5. [Заголовок 5]
6. [Заголовок 6]
7. [Заголовок 7]
8. [Заголовок 8]
9. [Заголовок 9]
10. [Заголовок 10]'
    
    insert into as_apiProfileLog(profileID, createdBy, created, request, requestAdditional, response, g)
    values(@profileID, @username, getdate(),
           'Генератор заголовков для статей', 
           'Входные данные:' + char(10) +
           'Тема: ' + isnull(@theme, '') + char(10) +
           'Аудитория: ' + isnull(@audience, '') + char(10) +
           'Формат: ' + isnull(@format, '') + char(10) +
           'Тон: ' + isnull(@tone, '') + char(10) +
           'SEO ключи: ' + isnull(@seo, '') + char(10) +
           'Длина: ' + isnull(@length, '') + char(10) +
           'Избегать: ' + isnull(@ignore, '') + char(10) +
           'Пожелания: ' + isnull(@dopActions, '') + char(10) + char(10) +
           'Системный промпт: ' + @systemPrompt,  
           '', @g)

    declare @logID int = SCOPE_IDENTITY()
    
    select 1 Result, 'OK' Msg, '' SuccessUrl, 0 HideFormAfterSubmit, '' RefreshContainer, 1 EnableSaveAlert
    
    select 'apirequest' type, @apiCode code,
        'text' p1_name, 'Сгенерируй 10 заголовков для статьи' p1_value,
        'systemPrompt' p2_name, @systemPrompt p2_value,
        'g' p3_name, convert(nvarchar(128), @g) p3_value,
        'profile' p4_name, @apiProfile p4_value
END

Колонки таблицы (важно, чтобы был hidden параметр g): 

JS часть (выводит информацию на странице на основе отклика по ИИ):  

var formCode = "ai-gen-header";

as.formcallbacks[formCode + "_beforeSaveItem"] = function(cont, params){
    return aif.form_beforeSaveItem(cont, params);
}

as.formcallbacks[formCode + "_saveItem"] = function(data, params){
    aif.form_saveItem(data, params);
}

В globalJS вынесены эти функции для обработки моментов до отправки запроса и после приема ответа: 

aif = {
	form_beforeSaveItem: function(cont, params){
    	console.log(params.code+ "_beforeSaveItem", params);
    	if($('.apichatResult>img:first').length){
    	    	$('.apichatResult>img:first').attr({'src': '/uploads/progress/dog.gif', width: 100, height: 100});
    	}else{
    	    $('.apichatResult').html('');
    	}

   		return true;
    },
  	form_saveItem: function(data, params){
    	console.log("saveItem", data)
    	if(data.result==false){
    	    as.sys.bootstrapAlert(data.msg || "Произошла какая то ошибка, напишите запрос администрации с указанием деталей", {type:"info", delay: 100000, hidePrev: true});
    	    return


    	}

        var el = JSON.parse(data.additionalData)[0].items;

        // "[{"items":[{"key":"result","value":null,"title":null,"value2":"True"},{"key":"msg","value":null,"title":null,"value2":""},{"key":"response","value":null,"title":null,"value2":"Необходимо зарегистрироваться для использования инструмента"}],"result":true,"msg":"Необходимо зарегистрироваться для использования инструмента","additionalData":null,"icon":null,"type":null,"actions":null}]"
        console.log("API Request data", el);
        var msg = "";
        var outputParams = "";
        $.each(el, function(i,item){
          if(item.key=="response"){
            msg = item.value2;
            var resp = JSON.parse(msg);
            if(resp.error){
                as.sys.bootstrapAlert(resp.error || "Произошла какая то ошибка, напишите запрос администрации с указанием деталей", {type:"warning", delay: 100000, hidePrev: true});
    	        return
            }
            console.log("RESP", resp);
            if(resp && resp.choices && resp.choices.length){
                var m = resp.choices[0].message.content;
              var copy = '
Копировать
';
                var s = copy+'
' +m+ '
';

         
                $('.ratedemo').removeClass('hide');
                $('.apichatResult').html(s).removeClass('d-flex');

         
             as.initControls($('.apichatResult'));
            }

          }
        });
    },
}

Реализация API метода для запроса к ИИ

Используем API DeepSeek. 

В /settings указываем настройку с кодом dsToken. 

В /asapi создаем новый метод ds1 (JSON POST).

Request процедура для метода API ds1: 

CREATE PROCEDURE [dbo].[api_ds1_request]
  @parameters ExtendedDictionaryParameter READONLY,  -- (Key, Value2)
  @username nvarchar(32)  -- current site user
AS
BEGIN
    declare @falconGuid nvarchar(max) = isnull((select value2 from @parameters where [key]='falconGuid'), '')
    declare @userIP nvarchar(max) = isnull((select value2 from @parameters where [key]='userIP'), '')
    declare @g uniqueidentifier = (select try_convert(uniqueidentifier, value2) from @parameters where [key]='g')
	 declare @text nvarchar(max) = (select value2 from @parameters where [key]='text')
    
    
    declare @formCode nvarchar(max) = (select formCode from as_serviceFormLog where guid =@g  )
    declare @serviceID int = (select id from as_services where formCode = @formCode)
    
    
    if(@username <> '' and  (select  count(sfl.id)
	from as_serviceFormLog  sfl
		inner join as_apiProfileLog apl on apl.g = sfl.guid 
		where apl.response is not null and apl.response<>''
		and sfl.username = @username and apl.created > dateadd(minute, -1, getdate() )
	
   )>1)	begin 
   	    print 'Many api DS requests from ' + @username
    	select 0 Result, '' Msg 
        return 
   end 

 	if(len(@text)> 2000) set @text = substring(@text, 1, 2000)
    declare @systemPrompt nvarchar(max) = (select value2 from @parameters where [key]='systemPrompt')
    declare @profile nvarchar(max) = (select value2 from @parameters where [key]='profile')
    declare @url nvarchar(max) = 'https://api.deepseek.com/chat/completions'
    declare @token nvarchar(max) = dbo.as_setting('dsToken', '')
	
   declare @profileID int = (select id from as_apiProfiles where code = @profile)

 	declare @commands nvarchar(max) = ''
    select @commands = @commands + isnull(prompt, '') + char(10) from as_apiProfileCommands
    where profileID = @profileID and len(prompt)>3 and isActive = 1


	if(len(@commands)>4) begin
    	set @systemPrompt = @commands + ' В остальных случаях не используй JSON с полем type. ' + @systemPrompt
    end


	declare @json nvarchar(max) = (
      select 'deepseek-chat' model,
		1000 max_tokens, 
		0.7 temperature,
      	(
          select role, content from (
          select 'system' role,  isnull(@systemPrompt, '') content, dateadd(ms, -200, getdate()) dt  where len(@systemPrompt)>4
          union
          select 'user' role,  	isnull(@text, '') content, getdate() dt ) t1
          order by dt
          FOR JSON PATH)  messages
	  for JSON PATH,WITHOUT_ARRAY_WRAPPER
    )

    print @json

  -- SELECT 1  Msg, Result, Url (outer request url)
  select '' Msg, 1 Result, @url Url, '' ContentType,
      '' Certpath, '' CertPass, '' RequestParameterForResponse, 0 Timeout

  -- SELECT 2 PARAMETERS - request parameters
   select 'Authorization' name, 'Bearer ' + @token value, 'header' [type]
  union
  select 'json' name, @json value, 'json' [type]


END

Response процедура для метода API ds1:

CREATE PROCEDURE [dbo].[api_ds1_response]
  @response nvarchar(max),
  @parameters ExtendedDictionaryParameter READONLY,  --(Key, Value2 - same as in request)
  @username nvarchar(32)
AS
BEGIN
	declare @g uniqueidentifier = (select try_convert(uniqueidentifier, value2) from @parameters where [key]='g')

  	declare @resp nvarchar(max) = @response
    declare @tokens int = 0
	if(ISJSON(@response) =1) begin
    	declare @t table (content nvarchar(max))
		insert into @t
        SELECT content FROM OPENJSON(@response, '$.choices')
        WITH (
			content NVARCHAR(MAX) '$.message.content'			
		)
		declare @messages nvarchar(max)  = ''
		select @messages = @messages + isnull(content, '') + char(10)+ char(10) from @t
		select @resp =  @messages

        set @tokens = JSON_VALUE(@response, '$.usage.total_tokens')

        declare @msg nvarchar(max) = ''

        -- обработка команды, если она есть в JSON
        exec ai_processCommand
        	@s = @resp,
            @ai = 'deepseek',
            @username = @username,
            @parameters = @parameters,
            @msg = @msg OUTPUT

        if(len(@msg)>0) set @resp = @resp + char(10)+ '
---
## Система:
'+ @msg


    end



    update as_apiProfileLog
    set response = @resp,
    	jsonResponse = @response,
        tokens = @tokens
    where g = @g


	-- SELECT 1
  	select '' Msg, 1 Result, @response Response
	if(rtrim(ltrim(@response))= 'The remote server returned an error: (401) Unauthorized.') begin
    	-- SELECT 2
    	select 'apirequest' type, 'gc_auth' code
    end
	--print @response
  -- SELECT 2 Outer actions
END

Процедура ai_processCommand как часть подсистемы (возможно тут можно обойтись и без нее): 

CREATE PROCEDURE [dbo].[ai_processCommand]
	-- ищет JSON в отклике ИИ и вызывает обработчик профиля

    @s nvarchar(max),	
    @ai nvarchar(max) = 'gigachat',
    @username nvarchar(256),
    @parameters ExtendedDictionaryParameter readonly,
    @msg nvarchar(max) OUTPUT
AS
BEGIN		
    set @msg = ''
    declare @json nvarchar(max) = dbo.ai_getJSONFromMessage(@s)
    declare @profile nvarchar(max) = (select value2 from @parameters where [key]='profile')
    if(len(@json)>0 and isjson(@json)=1) begin
    	declare @type nvarchar(128) = isnull(JSON_VALUE(@json, '$.type'), '')
        if(@type = '') return;

        declare @profileID int = (select id from as_apiProfiles where code = @profile)
        declare @commandID int = (select top 1 id from as_apiProfileCommands where profileID = @profileID and code = @type and isActive = 1)
        if(@commandID>0) begin
            declare @sp nvarchar(max) = 'ai_'+@profile+ '_' + @type+'_command'
            IF EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND name = @sp)
            BEGIN
                declare @parameters1 ExtendedDictionaryParameter
                insert into @parameters1 ([Key], value, value2)
                select [key], value, value2
                from @parameters

                declare @sql nvarchar(max) = 'dbo.['+@sp+'] @username = @username, @json=@json, @parameters = @parameters, @msg= @msg output'
                -- внутр может быть SELECT с внеш действиями
                EXEC sp_executesql @sql,
                  N'
                  @username nvarchar(256),
                  @json nvarchar(max),
                  @parameters ExtendedDictionaryParameter readonly,
                 @msg  nvarchar(max) output',
                  @username, @json, @parameters1, @msg OUTPUT;

                declare @g uniqueidentifier = (select try_convert(uniqueidentifier, value2) from @parameters where [key]='g')
                update as_apiProfileLog
                set commandID  = @commandID,
                    commandMsg = @msg
                where g = @g
            end
		end
    end



END


Выводы 

Таким образом можно собрать любые формы, где на входе пользователь что-то вводит, вы добавляете к этому свой системный промпт и отправляете на анализ в ИИ + выдаете потом ответ пользователю.

Можно также сделать так, что пользователь выбирает некие данные (например заказы или проекты) и вы формируете промпт на основе данных этих объектов из базы данных.

Примечание: В коде встречаются некоторые "левые" объекты (as_services, apiPorfileCommands) - их можно просто убирать, т.к. они не играют роли для данного примера. 

Страница-источник на сайте falconspace.ru