Lazarus MNote2 Mysql pacotes POSTGRES
Versão 2.13 Linux e Windows

Nesta Versão 2.13, foram corrigidos diversos bugs, incluindo tambem uma versão Linux 64bits.

Versão Linux 64bits – Rodando Ubuntu

Todas as compatibilidades da versão 2.13 do windows, agora estão no Linux Ubuntu.

Baixe agora mesmo o pacote:

https://github.com/marcelomaurin/MNote2/blob/master/bin/mnote2-2.13_amd64.deb

Consulte a documentação do projeto:

http://maurinsoft.com.br/index.php/projetos-open-source/projeto-mnote2/

Vídeos

Assista o vídeo do projeto no Linux Ubuntu 64 bits.

Vídeo em Ubuntu 64 bits
Versão em Windows 10

Delphi Lazarus MNote2
MNote2 – 2.11

Segue a lista de mudanças do projeto MNote2 release 11

Mudança da Tela de Pesquisa

Substituição dos comandos FIND por personalização de tela de pesquisa.

Personalização da Tela de Pesquisa e Troca de Palavras.

Correção do Estilo

Correção do formulário frmmnote principal de fixo para tamanho variavel

Codificação da função Replace

Codificação da Função “Trocar” permitindo a troca de uma palavra por outra diferente.

Tambem já foi codificado a troca de todas as palavras encontradas (Change All).

Função de troca

Undo

Implementação da função UNDO, que desfaz o que foi realizado anteriormente.

Versão disponível

Versão disponibilizada no github

https://github.com/marcelomaurin/MNote2

IA PHP pós graduação Python
Chatbot no wordpress – Parte 2b

Neste fragmento criamos uma série de mudanças no plugin do wordpress para centralizar as operações do chatbot.

Git do projeto

https://github.com/marcelomaurin/chatbot-wordpress

Fiz esta modificação para facilitar a gestão dos itens.

Mudanças envolvidas

No plugin foram feitas diversas mudanças, entre elas. Criação de um menu de itens.

Plugin do wordpress

Temos um menu onde temos opções de Teste de funcionalidade do chatbot

Exemplo do chatbot

E o histórico, que mostra as perguntas realizadas, com opção de exclusão de lixo.

Histórico de Pesquisa

O projeto agregou um web service, que permite comunicação com o banco de dados, conforme o projeto historico.php.


<?php
/*phpinfo();*/
/*Registra webservice para processamento de jobs*/
ini_set('display_errors', 'Off');
error_reporting(E_ALL);

include "connectdb.php";

$typereq = $_SERVER['REQUEST_METHOD'];
//echo $typereq;

if ($typereq==='POST')
{
	//echo "POST";
    // The request is using the POST method
	$data = json_decode(file_get_contents("php://input"));
	if($data){
		$localguid = $dbhandle->real_escape_string($data->guid);
		$idhistorico = $dbhandle->real_escape_string($data->idhistorico);
		//echo var_dump(json_decode($foo, true));
	} else {
		$localguid = $dbhandle->real_escape_string($_POST['guid']);
		$idhistorico = $dbhandle->real_escape_string($_POST['idhistorico']);
		//echo "3";
	}
	//echo $localguid;
	//echo $idhistorico;
}

if ($typereq === 'GET') {
	//echo "GET";
    // The request is using the POST method
	$data = json_decode(file_get_contents("php://input"));
	if($data){
		$localguid = $dbhandle->real_escape_string($data->guid);
		$idhistorico = $dbhandle->real_escape_string($data->idhistorico);
	} else {
		$localguid = $dbhandle->real_escape_string($_GET['guid']);
		$idhistorico = $dbhandle->real_escape_string($_GET['idhistorico']);
	}
}

if ($typereq === 'DELETE')
{
	//echo $typereq;
	$data = json_decode(file_get_contents("php://input"));
	if($data){
		$localguid = $dbhandle->real_escape_string($data->guid);
		$idhistorico = $dbhandle->real_escape_string($data->idhistorico);
	} else {
		$localguid = $dbhandle->real_escape_string($_GET['guid']);
		$idhistorico = $dbhandle->real_escape_string($_GET['idhistorico']);
	}
	//$localguid = $data->guid;
	//$idhistorico = $data->idhistorico;
	$query = "delete from historico where idhistorico = ".$idhistorico;
	$dbhandle->query($query);
	//echo ($query);
}

if($localguid!=GUID)
{
	//echo $localguid;
	//echo "  - - ";
	//echo GUID;
	$strJSON =  '{';
	$strJSON = $strJSON . '"mensagem":"Acesso negado"';
	$strJSON = $strJSON . '}';
	echo $strJSON;
	exit();
}


if (($typereq === 'GET')||($typereq === 'POST')){
	if(($idhistorico)&&($idhistorico!='0'))
	{
		$query = "select idhistorico, pergunta from historico where (idhistorico = ".$idhistorico.");";
	} else {
		$query = "select idhistorico, pergunta from historico ;";
	}
	$rs = $dbhandle->query($query);

	$cont = 0;
	//echo $query;
	$strJSON =  '{"rs":[';
	//while($row=$rs->fetch_assoc())
	while($row=$rs->fetch_assoc())
	{

		if($cont!=0)
		{
			$strJSON = $strJSON . ',';
		}
		$strJSON = $strJSON . '{';
		$strJSON = $strJSON . '"idhistorico":'.$row['idhistorico'].',';
		$strJSON = $strJSON . '"pergunta":"'.$row['pergunta'].'"';
		$strJSON = $strJSON . '}';
		$cont ++;
	}
	$strJSON = $strJSON . ']}';
	if ($cont>0)
	{
		echo($strJSON);
	}
}
?>

No código acima, vemos que os verbos do HTML estão contemplados, apesar do sistema apenas usar o POST e DELETE.

O uso destes verbos será modelo para outros web services mais complexos.

Outra melhoria foi o uso de um GUID, que irá aumentar a segurança, restringindo o acesso ao web service, apenas a requisições com a chave correta.

Muitas mudanças ainda serão necessárias, porem estas garantem que nosso chatbot seja mais prático.

Muitas pessoas sabem que um chatbot não precisa deste tipo de melhoria, mas de fato, quando a intensão é o aprendizado, faze-lo de forma que fique mais fácil gerenciar as diversas ações de gestão do chatbot, ajudarão em um futuro próximo.

Há muito o que fazer, espero que acompanhem essa jornada.

Abraço.

IA PHP Python wordpress
Chatbot no wordpress – Parte 2

Agora que criei a página do Chatbot, vou comecar a preparar o recheio.

Definindo objetivo do Chatbot

A primeira coisa que precisamos saber é sobre o que devemos realizar nosso chatbot.

Bom irei falar sobre os projetos da maurinsoft.

Então o primeiro trabalho que irei precisar é criar simulados de perguntas, para facilitar isso, irei criar um conjunto de aplicações.

Neste post irei falar sobre o primeiro.

A melhor forma de criar um conjunto de perguntas é perguntando.

Então vou adicionar ao meu projeto uma tabela historico.

historico.sql

use maurinsoftdb;

create table historico (
  idhistorico int AUTO_INCREMENT PRIMARY KEY,
  pergunta varchar(200) not null,
  ip varchar(40),
  dtquest TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Agora que criei o espaço de armazenamento no banco, irei incluir no web service que processa a pergunta.

Fonte: runpy.php

<?php
    /*phpinfo();*/
	/*Registra webservice para processamento de jobs*/
	ini_set('display_errors', 'ON');
	error_reporting(E_ALL);

	header('Content-Type: application/json');  // <-- header declaration

	include "/var/www/html/python/connectdb.php";

	//header('Cache-Control: no-cache, must-revalidate');

$data = json_decode(file_get_contents("php://input"));

if($data){
		$pergunta = $dbhandle->real_escape_string($data->pergunta);
} else {
		$pergunta = $dbhandle->real_escape_string($_GET['pergunta']);
}

$ip = $_SERVER['REMOTE_ADDR']; /*Pega o ip do cliente*/

//echo "Inseriu";
$query= "INSERT into historico (pergunta, ip) values ( '". $pergunta. "', '".$ip."')";
$dbhandle->query($query); /*Executa*/

$json =  '{"rs":[';
if($pergunta){
  $command = escapeshellcmd('/var/www/html/python/nlp.py "'.$pergunta.'" > /var/log/proclog.log');
  $resposta = shell_exec($command);
  $resposta = str_replace (array("\r\n", "\n", "\r"), ' ', $resposta);

  if($resposta){
     $json = $json  . '{';
     $json = $json  .  '"pergunta":"'.$pergunta.'",';
     $json = $json  .  '"resposta":'.$resposta;
     $json = $json  .  '}';
  } else {
	 $json = $json  . '{';
	 $json = $json  .  '"pergunta":"'.$pergunta.'",';
     $json = $json  .  '"resposta":"'.'sem resposta'.'"';
	 $json = $json  .  '}';
  }
} else {
	$json = $json  . '{';
	$json = $json  .  '"pergunta":"'.$pergunta.'",';
    $json = $json  .  '"resposta":"'.'sem resposta'.'"';
	$json = $json  .  '}';
}
$json = $json  .  ']}';

echo $json
?>

Neste projeto que não irei falar muito, pois ja comentei sobre a maior parte em posts anteriores, fiz a inserção do código abaixo:

//echo "Inseriu";
$query= "INSERT into historico (pergunta, ip) values ( '". $pergunta. "', '".$ip."')";
$dbhandle->query($query); /*Executa*/	

Nela ao receber a pergunta armazenamos ela p pesquisa histórica.

Próximos passos

A seguir os próximos passos que iremos postar.

  • Iremos montar as questões que serão respondidas
  • Iremos cadastrar as respostas possíveis
  • Iremos associar as perguntas as respostas
  • Iremos gerar treinamento da nossa rede
  • Iremos codificar nosso código em python.
Blog C/C++
ISO8583

Este material esta sendo construído.

Objetivo

O objetivo deste artigo é apresentar uma visão geral sobre este protocolo.

Histórico

Criado em 1987 a ISO8583 descreve o intercâmbio entre requisições bancárias.

Funcionamento

Sem entrar muito no detalhe do funcionamento.

Basicamente a imagem abaixo, ilustra o funcionamento do protocolo em linhas gerais.

Visão geral do protocolo

De forma geral, o PDV ou POS solicita a operadora uma dada solicitação.

Por exemplo:

inicio de pagamento com cartão de crédito.

No corpo da solicitação, irão alguns dados identificadores da solicitação.

A operadora, pode conforme o tipo da solicitação, perguntar algumas coisas, como senha, tipo de cartão entre outras coisas.

Ou mesmo solicitar a visualização de outras.

A esta solicitação (chamamos de ação), que deve ser respondida pelo PDV ou POS.

A operadora pode realizar quantas perguntas forem necessárias para atendimento de suas necessidades.

Ao fim a operadora envia a resposta final, dando por encerrado a operação.

Em linhas bem gerais é assim que funciona o protocolo ISO 8583.

Estaremos entrando em detalhes, mais adiante.

Porem para entendimento geral, é um protocolo mestre/escravo, onde após a solicitação inicial, a operadora passa a solicitar informações, que devem ser respondidas.

Identificadores de tipo de mensagens

O identificador de tipo de mensagem é um campo numérico de 4 dígitos que especifica o tipo da mensagem que deve ser processado.

Formatação do cabeçalho da solicitação
  • V: número de versão da ISO 8583 (0 indica que é ISO 8583:1987; 1 indica ISO 8583:1992).
  • n: Classe da Mensagem conforme a tabela abaixo: | 0 Reserved for ISO use | 1 Authorization | 2 Financial | | 3 File action | 4 Reversal/Chargeback | 5 Reconciliation | | 6 Administration | 7 Fee collection | 8 Network management | | 9 Reserved for ISO use | | |
  • X: Função da Mensagem conforme tabela: | 0 Request | 1 Request response | 2 Advice | | 3 Advice response | 4 Notification | 5 – 9 Reserved for ISO use |
  • Y: Origem da Transação | 0 Acquirer | 1 Acquirer repeat | 2 Card issuer | | 3 Card issuer repeat | 4 Other | 5 Other repeat | | 6 –9 Reserved for ISO use | | |

Simulando PDV e Simulando Autorizadora

Eu vasculhando a internet achei o site da neapay, conforme referência.

Baixei um download simulator_ISSUER_ISO8583_host_auth_PRO.

A aplicação trabalha com JAVA, e é uma boa pedida quem deseja testar e aprender mais sobre o protocolo.

Simulação de ISO8583

Referências

C/C++
Threads para Raiz – Parte 2 – Programação C

Neste segundo artigo, utilizaremos o exemplo apresentado e iremos desenvolver nossa aplicação em C para linux.

Github

Para ilustrar nosso exemplo criei uma pasta C no nosso projeto git.

Threads para Raiz – Parte 1

Todos os itens deste artigos estão na pasta C.

Makefile

Para quem acompanha meu canal, o nosso bem amado Makefile é o contrutor da aplicação.

PROGRAMA=threads
PROGRAMA32=threads32

LIBS= -lpthread

CC=gcc

SOURCE= threads.c

all32: clean compile32


all: clean compile

clean:
	rm -f *.o
	rm -f $(PROGRAMA)

compile32:
	$(CC) -m32  $(SOURCE) $(LIBS) -o $(PROGRAMA32)

compile:
	$(CC) $(SOURCE) $(LIBS) -o $(PROGRAMA)

Aqui criamos um construtor para ambas as arquituras 32 e 64bits.

Por padrão iremos manter o uso em 64bits que é a plataforma nativa do meu linux.

Criando a base

O primeiro passo é criar a base do nosso chamador.

Para tanto iremos no main, inicializar as variáveis e chamar os chamadores das threads.

void main(void){
	int rstatus = 0;
	srand(time(NULL));
	pthread_t  pidRecepcao = 0;
	pthread_t  pidControlador = 0;
	pthread_t  pidExecutor = 0;
	//mutex = PTHREAD_MUTEX_INITIALIZER;

	printf("\nBem vindo ao programa das threads\n");
	printf("Este programa faz parte do artigo:\n");
	printf("http://maurinsoft.com.br/index.php/2022/07/02/threads-para-raiz-parte-1/\n\n");
	printf("Inicializando vetor\n\n");
	StartVetor();
	printf("Iniciando Recepcao\n");
	pidRecepcao = Start_Recepcao();
	printf("Iniciando Controlador\n");
	pidControlador = Start_Controlador();
	printf("Iniciando Executor\n");
	pidExecutor = Start_Executor();
	//bool flag = ((pidRecepcao!=0)||(pidControlador!=0)||(pidExecutor!=0));
	flgTerminou = false;
	while(!flgTerminou){
		printf("Status:");

		if(rstatus != 0)
		{
			printf ("Erro ao aguardar finalização do thread A.\n");

		}

		flgTerminou = flgTerminouRecepcao && flgTerminouExecucao;
		sleep(1);
	}
	printf("\n\nFila ordenada:");
	for(int cont = 0;cont<=MAXITEMS-1;cont++)
	{
	  printf("%i ",fila[cont]);
	}
	printf("\n\n");

	printf("\n\nFila executada:");
	for(int cont = 0;cont<=MAXITEMS-1;cont++)
	{
	  printf("%i ",executada[cont]);
	}
	printf("\n\n");

}

As funcoes Start_Recepcao, Start_Controlador, Start_Executor chamam as threads, que serão apresentadas em um próximo momento.

A variavel pidRecepcao , do tipo pthread_t que indica cada uma das threads criadas.

Inicialização de variáveis

A inicialização da fila é feita pela função StartVetor, conforme fonte abaixo:

void StartVetor(){
		for (int cont = 0;cont<=MAXITEMS;cont++)
		{
			fila[cont] = 0;
			executada[cont]=0;

		}
}

Temos também a variável executada, que será utilizada na marcação dos itens executados.

Sessão Crítica

O controle da sessão crítica é feita nas funções IniciaSessaoCritica e TerminaSessaoCritica, conforme fonte abaixo:


int IniciaSessaoCritica()
{
	int  rc;
	int cont = 0;
	while ( (rc = pthread_mutex_lock(&mutex))!=0)
	{
		usleep(100); /*Aguarda um pouco*/
		cont++;
		printf("Sessao critica não conseguida\n");
		if (cont>3) break;
	}
	return rc;


}

int TerminaSessaoCritica()
{
	int  rc;
	int cont = 0;
	while ( (rc = pthread_mutex_unlock(&mutex))!=0)
	{
		usleep(100); /*Aguarda um pouco*/
		cont++;
		printf("Sessao critica não liberada\n");
		if (cont>3) break;
	}
	return rc;


}

Ambas as funções controlam a sessão criada mutex, declarada, conforme fonte abaixo:

pthread_mutex_t    mutex = PTHREAD_MUTEX_INITIALIZER;

Para cada sessão critica, deve-se criar uma variável de controle. Como usaremos apenas a fila, como variável de troca de dados, utilizaremos uma única sessão critica.

Criando a thread da recepção

Agora iremos dar inicio a criação da thread da recepção, na qual incluiremos o inicio da thread.

pthread_t  Start_Recepcao()
{
	pthread_t  pid	= 0;
	int ret;
	ret=pthread_create(&pid,NULL,&threadRecepcao,NULL);

	return pid;
}

Neste segmento do código, podemos acompanhar que criamos a thread com pthread_create, passando o ponteiro da função que será criada a thread threadRecepcao. Desta forma a função threadRecepcao, não mais seguirá na thread pai. O ultimo parâmetro é utilizado para passagens de argumentos, no nosso caso, não será usado.

Por fim a função da thread

/*thread function definition*/
void* threadRecepcao(void* args)
{
	for(int cont = 0;cont<=MAXITEMS;cont++)
	{

		printf("Criando pacote nro %d\n",cont);
		if(IniciaSessaoCritica()==0)
		{
			int Valor = (rand()% 100);
			fila[cont] = Valor; /*Grava Valor na fila*/
			printf("Registrou na fila[%d] = %d\n",cont,Valor);
			TerminaSessaoCritica();
			usleep(1000);
		} else {
				printf("Falha na Recepcao nro:\n",cont);
		}

	}
	printf("Recepcao terminou atendimento\n");

}

Podemos observar que a threadRecepção requisita a sessão crítica para movimentar a fila, apenas depois alocando valor para ela. Também podemos perceber que após seu uso, o mesmo é descartado.

Criando o Controlador

O controlador, é executado, em seguida, ordenando as informações.

void ordenacao()
{
int i, x;
bool flgordenado = false;
while((!flgordenado)&&(!flgTerminou))
{
	//flgordenado = true;
	for (i=0; i<=MAXITEMS-1; i++)
	{
		if ((fila[i]!=0)&&(fila[i+1]!=0))
		{
		  if(fila[i]>fila[i+1])
		  {
		    x = fila[i];
            fila[i] = fila[i+1];
		    fila[i+1] = x;
		    flgordenado = false;
		    //printf("Ordenando os pacote nro %d\n",i);
		  }
		}	else {
			//printf("Posicao vazia %d\n",i);
			flgordenado = false;
		}
    }


  }
}

/*thread function definition*/
void* threadControlador(void* args)
{
  int oldValue, newValue;
  int flgOrdenado = false;
  printf("Iniciou o controlador\n");
  while(!flgTerminou) /*Faz enquanto nao terminar e nao ordenao*/
  {
	if(IniciaSessaoCritica()==0)
	{
		ordenacao();

		TerminaSessaoCritica();
		//usleep(200);

	}
  }

  printf("Terminou ordenação de todos os itens\n");
}

Neste bloco temos duas funções:

A thread em si é a threadControlador, que chama a função de ordenação ordenacao.

A ordenação só para quando for atendida duas condições:

Tiver terminado as demais threads, indicado pelo flag flgTerminou e quando tiver sido totalmente ordenado flgordenado.

Executor

A thread do Executor, pode ser vista conforme apresentado a seguir:

/*thread function definition*/
void* threadExecutor(void* args)
{
  int oldValue, newValue;
  bool flgExecutado = false;
  printf("Iniciou o controlador\n");
  int posicao = 0;
  //while(!flgExecutado)
  while((!flgExecutado)&&(!flgTerminou))
  {
    //printf("Pesquisando Posicao %d\n",posicao);

	//printf("Entrou na sessao critica\n");
	if  (fila[posicao]==0) /*Fila nao foi preenchida*/
	{
		printf("posicao vazia %d\n",posicao);
		posicao = posicao;

	} else
	{
	   executada[posicao] = fila[posicao];
	   printf("Executou[%d] = %d\n",posicao,fila[posicao]);
	   if(posicao==MAXITEMS-1)
	   {
		   printf("Chegou ao fim\n ");
		   flgExecutado= true; /*Finaliza executor*/
	   } else
	   {
		   posicao ++;
	   }

	}
	//printf("terminou while\n");
  }

  flgTerminouExecucao = true;

  printf("Terminou ordenação de todos os itens\n");

}

Ela irá rodar até que duas condições sejam satisfeitas:

  • flgExecutado – Controla a execução de todas as tarefas da lista
  • flgTerminou – Controla o fim de todas as demais threads do sistema

Testando programa

Compilando programa

A compilação em um Raspberry PI ocorreu com sucesso.

Executando o programa

root@raspberrypi:/home/mmm/projetos/Threads-para-Raiz/c# ./threads

Bem vindo ao programa das threads
Este programa faz parte do artigo:
http://maurinsoft.com.br/index.php/2022/07/02/threads-para-raiz-parte-1/

Inicializando vetor

Iniciando Recepcao
Iniciando Controlador
Registrou na fila[0] = 80
Registrou na fila[1] = 11
Registrou na fila[2] = 76
Registrou na fila[3] = 71
Registrou na fila[4] = 27
Registrou na fila[5] = 23
Registrou na fila[6] = 33
Registrou na fila[7] = 68
Registrou na fila[8] = 32
Registrou na fila[9] = 92
Recepcao terminou atendimento
Iniciou o controlador
Iniciando Executor
Status:Iniciou o controlador
Executou[0] = 11
Executou[1] = 23
Executou[2] = 27
Executou[3] = 32
Executou[4] = 33
Executou[5] = 68
Executou[6] = 71
Executou[7] = 76
Executou[8] = 80
Executou[9] = 92
Chegou ao fim
 Terminou ordenação de todos os itens
Status:Terminou ordenação de todos os itens


Fila ordenada:11 23 27 32 33 68 71 76 80 92



Fila executada:11 23 27 32 33 68 71 76 80 92

Como a execução ficou um pouco longa, resolvi jogar como código.

Foto da execução

Conclusão

Podemos perceber que a execução de threads pode ser facilitada com uso de flags externos, que facilitam a comunicação entre os processos. Pudemos ver tambem, que a programação em threads é de fato, um estado de arte da programação C, pois envolve alem de um algoritmo bem desenvolvido, a elaboração de estratégias pensando em que uma thread não sabe exatamente quando a outra irá cumprir suas atividades.

Desta forma cada thread tem que ser pensada em aguardar e esperar as informações das threads que colaboraram com esta.

Desta forma o controle interno das informações é bem mais complicado.

Pudemos ver por ultimo o uso dos sinalizadores, que visão identificar o uso de uma sessão crítica do sistema. Que nada mais é que um recurso compartilhado.

Espero que tenham gostado do artigo.

Em caso de dúvidas ou sugestões, fico a disposição como sempre.

marcelomaurinmartins@gmail.com

Bibliografia

C/C++ Delphi Java Python
Threads para Raiz – Parte 1

Neste primeiro artigo, iremos estabelecer um projeto, que iremos desenvolver nos demais artigos.

Objetivo

A intenção deste projeto é apresentar solução em diversos linguagens de controle de threads com compartilhamento de informações entre elas.

Proposta de projeto

Imagine que temos 3 funcionários em um departamento público.

  • Recepção de Protocolo – Ele recebe os protocolos de serviço dos clientes.
  • Controlador de Serviço – Ele recebe os protocolos da recepção, colocando em ordem numérica em uma fila de execução. Por controlar e ordenar, sua atividade demora tempo mediano.
  • Executor de Serviço – Ele pega o serviço, por executar o serviço é o mais demorado de todos.
Fluxo de execução

Tempo de execução

Ao analisarmos o departamento, fizemos a seguinte constatação:

  • A recepção de protocolo, é o departamento mais rápido.
  • O Controlador de Serviço tem um tempo médio que é o dobro da recepção.
  • Executor de Serviço – Demora o dobro do tempo do controlador de serviço.

Agora que temos o projeto, podemos no próximo artigo começar sua implementação.

Espero voces no próximo artigo 😉

wordpress
Chatbot no wordpress

[chatbot]

Objetivo

Neste tópico incluiremos o chatbot no wordpress, atraves de um shortcode.

Projeto GIT

O código deste projeto pode ser visto no git abaixo:

https://github.com/marcelomaurin/chatbot-wordpress

Ele permite criar um shortcode, que pode ser incluido nos posts.

Onde aparece uma pagina de chat, para que possa interagir.

[chatbot]

O chatbot pode ser incluído na página conforme o link acima.

O chatbot ja incluído nesta página

Nos próximos artigos iremos dar inicio ao código do chatbot, pois este esta apenas com a parte web atualmente.

pt_BRPortuguese