<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aldrin Navarro</title>
    <description>The latest articles on DEV Community by Aldrin Navarro (@aldnav).</description>
    <link>https://dev.to/aldnav</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F25136%2F1be6344c-c25f-4795-a982-611d73f923b1.jpg</url>
      <title>DEV Community: Aldrin Navarro</title>
      <link>https://dev.to/aldnav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aldnav"/>
    <language>en</language>
    <item>
      <title>Exploring Large Language Model with Slack Bot</title>
      <dc:creator>Aldrin Navarro</dc:creator>
      <pubDate>Fri, 19 Jan 2024 14:01:55 +0000</pubDate>
      <link>https://dev.to/aldnav/exploring-large-language-model-with-slack-bot-fip</link>
      <guid>https://dev.to/aldnav/exploring-large-language-model-with-slack-bot-fip</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This mini-project is heavily inspired by a guide written by Dave Ebbelaar titled &lt;a href="https://docs.datalumina.io/3y3XPD66nBJaub" rel="noopener noreferrer"&gt;"Slack AI Assistant with Python &amp;amp; LangChain"&lt;/a&gt; and from his YouTube video &lt;a href="https://youtu.be/3jFXRNn2Bu8?si=AaT2DJ995bJRlFpx" rel="noopener noreferrer"&gt;"How I Supercharge Slack With Custom AI Bots"&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've been wanting to create my own Slack Chat bot with some type of intelligence behind it and not only restricted to the algorithms I implement. I also wanted to explore the capabilities of GPT and see how it can be used in a real-world application.&lt;/p&gt;

&lt;p&gt;So I decided to create a Slack bot that uses GPT to generate a more naturally sounding response given a simple prompt as a personality, provide data gathered from Slack, and a very crude template of what the response could be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the AI Slack Bot
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objectives
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Give a summary on the pending code review requests. A code review request is a simple message from a Slack channel. Usually the format is &lt;code&gt;"[Brief description]\nPR: [link to the PR]\nTarget: [release version]"&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;Identify high priority. It is high priority when it includes some keywords or explicitly says high priority.&lt;/li&gt;
&lt;li&gt;Rank requests based on age. There is a unix timestamp on messages.&lt;/li&gt;
&lt;li&gt;Rank requests based on target release. A target release starts with the text "Target: "&lt;/li&gt;
&lt;li&gt;Here is an Example of the output:
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello there fellas!
We have 3 requests that are high priority:
1. Request 1
2. Request 2
3. Request 3
And these requests are pending for review for quite some time now 
1. Request 9
2. Request 10
As we are approaching the next release 1.99, let's try our best to spend time reviewing these tickets:
1. Request 11
2. Request 12

A review a day keeps the bugs away! Or something like that ;-)

Have a nice day!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;The message is a pinned message from a Slack channel.&lt;/li&gt;
&lt;li&gt;Here is an example of the message input: &lt;code&gt;"Hi guys. This is John's PR to fix the bug in the UI.\nPR: [link to the PR]\nTarget: [release version]"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A release version is a simple text &lt;code&gt;"1.10", "1.10.1", "1.11", "1.11.1", "1.12", or "1.12.1"&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;For this quick project, I used the following tech stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flask&lt;/li&gt;
&lt;li&gt;Langchain&lt;/li&gt;
&lt;li&gt;Slack SDK and Slack Bolt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a general outline of the TODOs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setup Slack&lt;/li&gt;
&lt;li&gt;Setup Server&lt;/li&gt;
&lt;li&gt;Custom functions&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Setup Slack
&lt;/h4&gt;

&lt;p&gt;This one is pretty straightforward. Here are the things we need to setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Slack workspace to test the application&lt;/li&gt;
&lt;li&gt;A Slack application&lt;/li&gt;
&lt;li&gt;A Slack bot to respond in behalf of the application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxdd96q9k4a4ai3b9hk3a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxdd96q9k4a4ai3b9hk3a.png" alt="Slack App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you may have guessed, I used AI to generate the application's image.&lt;/p&gt;

&lt;p&gt;The prompt is as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An illustration of an Owl dressed as a doctor with a stethoscope around its neck. The Owl should be wearing a white lab coat and a pair of small round glasses. The background should be a shade that complements the color blue, creating a calming and professional atmosphere. Ensure that the image is suitable for a Slack profile picture, with dimensions of 512x512 pixels and a resolution of 72 pixels per inch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk024fv2ljlss7yktjuqt.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk024fv2ljlss7yktjuqt.jpeg" alt="Generated Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As with any early generative AI, it's not perfect but it's good enough for me. It did not follow the last bit of instruction to constraint the image to 512x512 pixels. But that can be easily fixed with a simple image editor.&lt;/p&gt;

&lt;p&gt;From the application itself we needed a few things to take note of for the later implementation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Slack Bot Token. This is the token that will be used by the bot to respond to messages. Get this from App &amp;gt; OAuth &amp;amp; Permissions &amp;gt; Bot User OAuth Token (starts with &lt;code&gt;xoxb-&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Slack Signing Secret. This is the token that will be used to verify that the request is coming from Slack. Get this from App &amp;gt; Basic Information &amp;gt; Signing Secret.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The bot itself also needs some permissions to be able to read messages and send messages, and gather pinned messages. This can be configured in App &amp;gt; OAuth &amp;amp; Permissions &amp;gt; Scopes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tv5rcaz0wg1k27ijwb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tv5rcaz0wg1k27ijwb6.png" alt="Scopes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup Server
&lt;/h4&gt;

&lt;p&gt;Before diving into the code, it is important we understand how data flows in and out of the application. Here is a simple diagram of the flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1kg1pshkf7n9j7reipk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1kg1pshkf7n9j7reipk.png" alt="Flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Slack sends a request to the application&lt;/li&gt;
&lt;li&gt;The application responds ACK to Slack. A small time window to respond is enforced by Slack. If the application does not respond within the time window, Slack will consider it as an error. Our application will still be able to respond at a later time with the context provided the channel ID.&lt;/li&gt;
&lt;li&gt;The application pre-process the message, and sends instructions to the LLM.&lt;/li&gt;
&lt;li&gt;The LLM generates a response.&lt;/li&gt;
&lt;li&gt;The application post-processes the response.&lt;/li&gt;
&lt;li&gt;And finally, sends a response to Slack.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From this bit, we only have one thing to go back to Slack.&lt;br&gt;&lt;br&gt;
We need to know the URL of the running application to direct our&lt;br&gt;
commands. On to the next section!&lt;/p&gt;
&lt;h4&gt;
  
  
  The Server Code
&lt;/h4&gt;

&lt;p&gt;The server code is pretty straightforward. It is a simple Flask application that uses the Slack Bolt SDK to handle the Slack events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;find_dotenv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;slack_bolt&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;slack_bolt.adapter.flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SlackRequestHandler&lt;/span&gt;

&lt;span class="c1"&gt;# Load environment variables from .env file
&lt;/span&gt;&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;find_dotenv&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;# Slack API credentials
&lt;/span&gt;&lt;span class="n"&gt;SLACK_BOT_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;SLACK_SIGNING_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_SIGNING_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;SLACK_BOT_USER_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_BOT_USER_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize the Slack app
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ... and the Slack request handler
&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SlackRequestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Initialize the Flask app
&lt;/span&gt;&lt;span class="n"&gt;flask_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Listen to incoming HTTP requests
&lt;/span&gt;&lt;span class="nd"&gt;@flask_app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/summarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;listen_to_command_summarize&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Route for handling Slack events.
    This function passes the incoming HTTP request to the SlackRequestHandler for processing.

    Returns:
        Response: The result of handling the request.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Relay request to SlackRequestHandler
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Let SlackRequestHandler handle /summarize command
&lt;/span&gt;&lt;span class="nd"&gt;@app.command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/summarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize_pr_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;summarize_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Run the Flask app
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;flask_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, run the Flask app and spin up NGROK to expose the application to the internet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on a separate terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ngrok http 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the URL from NGROK and paste it in the Slack App &amp;gt; Slash Commands &amp;gt; Request URL &amp;gt; Create New Command&lt;/p&gt;

&lt;p&gt;Fill-in the fields&lt;/p&gt;

&lt;p&gt;Command: &lt;code&gt;/summarize&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Request URL: (Paste the URL provided by NGROK)  &lt;/p&gt;

&lt;p&gt;Feel free to fill in the rest of the fields.&lt;/p&gt;
&lt;h4&gt;
  
  
  LangChain
&lt;/h4&gt;

&lt;p&gt;LLM is new to me. I've had Machine Learning classes back in college but I never really dived into NLP and its applications. So for me exploring &lt;a href="https://python.langchain.com/docs/get_started/introduction" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; rekindles the experience of learning AI again and it's pretty exciting.&lt;/p&gt;

&lt;p&gt;According to the documentation, &lt;em&gt;"LangChain is a framework for developing applications powered by language models."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the application, LLM is to be used for a singular goal: summarize the pending code review requests in a natural sounding way.&lt;/p&gt;

&lt;p&gt;The first step is to install LangChain and since the model I'd be using is &lt;code&gt;gpt-3.5-turbo&lt;/code&gt; by OpenAI, I'd be installing openai too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;langchain openai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to prepare a few things for the LLM to work. Particularly for OpenAI. For other LLMs you may need to do other things (next up we are going to use Ollama).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an OpenAI account&lt;/li&gt;
&lt;li&gt;Create an OpenAI API key&lt;/li&gt;
&lt;li&gt;Save the API key in our .env file
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENAI_API_KEY=sk-**********
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, for the core. In the &lt;code&gt;summarize_requests&lt;/code&gt; function, we need to do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an LLM instance&lt;/li&gt;
&lt;li&gt;Create a prompt&lt;/li&gt;
&lt;li&gt;Provide the prompt to the LLM instance&lt;/li&gt;
&lt;li&gt;Get the response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A very good example that demonstrates a similar flow is &lt;a href="https://python.langchain.com/docs/integrations/document_loaders/figma" rel="noopener noreferrer"&gt;Figma's document loader example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For us though, it'd be very simple. First, we import everything we need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# functions.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;find_dotenv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMChain&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.chat_models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.prompts.chat&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="n"&gt;SystemMessagePromptTemplate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Load environment variables from .env file
&lt;/span&gt;&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;find_dotenv&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define the chat model and give it a "personality".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# functions.py
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cooler_summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are a funny and cool Project Manager. Your name is Review Doctor.

So make this message cool sounding to request for code review 
to the programmers. And at the end of the message insert a short joke about 
the topics: programming, coding, and code review. 
Note, do not loose any links. A link in Slack mkdwn format is
&amp;lt;http://www.example.com|This message *is* a link&amp;gt;
So make sure to keep the link and the text.

{summary}
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;system_message_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SystemMessagePromptTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we provide the prompt to the chain and get the response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# functions.py
&lt;/span&gt;
    &lt;span class="n"&gt;chat_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;system_message_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLMChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chat_prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the AI Slack Bot
&lt;/h2&gt;

&lt;p&gt;For now, I am able to interact with the bot using a slash command &lt;code&gt;/summarize&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I've invited it to a test channel on my test Slack workspace and it's able to respond to my commands with input fetched from pinned messages.&lt;/p&gt;

&lt;p&gt;Some of the pinned messages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxldaya198osk4wi47yzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxldaya198osk4wi47yzi.png" alt="Pinned messages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here are the responses of the Slack Bot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8jqhv7cwr212bgzgp28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8jqhv7cwr212bgzgp28.png" alt="Example of a response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's another one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fckhoz85r5vp942vsm575.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fckhoz85r5vp942vsm575.png" alt="Another response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a video of the bot in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;There are a lot of things that can be improved on this project. Here are some of the things I can think of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exploring more conversational chains&lt;/li&gt;
&lt;li&gt;Deploying a serverless/function type of application rather than a Flask application&lt;/li&gt;
&lt;li&gt;Using a different LLM (via Ollama)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Definitely stay tuned for more updates on these explorations!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Github Repo: &lt;a href="https://github.com/aldnav/review-doctor-slack-bot" rel="noopener noreferrer"&gt;https://github.com/aldnav/review-doctor-slack-bot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.datalumina.io/3y3XPD66nBJaub" rel="noopener noreferrer"&gt;"Slack AI Assistant with Python &amp;amp; LangChain"&lt;/a&gt; by Dave Ebbelaar&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gpt</category>
      <category>python</category>
      <category>slack</category>
      <category>openai</category>
    </item>
    <item>
      <title>Making the Escapists Crafting Guide App  2 — SliverList and Sticky Headers</title>
      <dc:creator>Aldrin Navarro</dc:creator>
      <pubDate>Fri, 25 Dec 2020 12:12:07 +0000</pubDate>
      <link>https://dev.to/aldnav/making-the-escapists-crafting-guide-app-2-o8e</link>
      <guid>https://dev.to/aldnav/making-the-escapists-crafting-guide-app-2-o8e</guid>
      <description>&lt;p&gt;On &lt;a href="https://dev.to/blog/making-the-escapists-crafting-guide-app"&gt;Part 1&lt;/a&gt;, I made the working version of the Escapists Crafting Guide app using &lt;a href="https://flutter.dev/" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt;. I used &lt;code&gt;ListView&lt;/code&gt; widget to show the different tools and items available for crafting.&lt;/p&gt;

&lt;p&gt;Today I am going to improve the app slightly by showing on the list the category where these tools belong. I am also going to replace the &lt;code&gt;ListView&lt;/code&gt; with &lt;a href="https://api.flutter.dev/flutter/widgets/SliverList-class.html" rel="noopener noreferrer"&gt;&lt;code&gt;SliverList&lt;/code&gt;&lt;/a&gt; — I describe it as a fancier list view. I am also exploring &lt;code&gt;flutter_sticky_header&lt;/code&gt;&lt;a href="https://github.com/letsar/flutter_sticky_header" rel="noopener noreferrer"&gt;[repository]&lt;/a&gt; &lt;a href="https://pub.dev/packages/flutter_sticky_header" rel="noopener noreferrer"&gt;[pub.dev]&lt;/a&gt; to achieve the behavior when the user scrolls and the category sticks to the top until the next category is in the current view. I will also be polishing the design along the way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvg9qexss6wxs0xk99wm2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvg9qexss6wxs0xk99wm2.png" alt="alt Categories of tools"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Categories of tools &lt;a href="https://theescapists.gamepedia.com/Crafting" rel="noopener noreferrer"&gt;source: Gamepedia The Escapists Crafting Guide&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SliverList
&lt;/h2&gt;

&lt;p&gt;To use the SliverList, it needs to be wrapped on a &lt;code&gt;CustomScrollView&lt;/code&gt; which comes with the familiar API that we already learned from using &lt;code&gt;ListView&lt;/code&gt; with slight changes. This widget requires a list of children widgets called &lt;code&gt;slivers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://flutter.dev/docs/development/ui/advanced/slivers" rel="noopener noreferrer"&gt;A sliver&lt;/a&gt; is a portion of a scrollable area.&lt;/em&gt; A sliver could be a &lt;a href="https://youtu.be/R9C5KMJKluE" rel="noopener noreferrer"&gt;SliverAppBar&lt;/a&gt;, &lt;a href="https://api.flutter.dev/flutter/widgets/SliverList-class.html" rel="noopener noreferrer"&gt;SliverList&lt;/a&gt;, or a &lt;a href="https://api.flutter.dev/flutter/widgets/SliverGrid-class.html" rel="noopener noreferrer"&gt;SliverGrid&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Data structure
&lt;/h3&gt;

&lt;p&gt;Previously, I was just working with a linear list of all items. Right now I need to refactor it to group the tools by categories.&lt;/p&gt;

&lt;p&gt;My input data contains a list of items, and for each item it has a category.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Flimsy Pickaxe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tools"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lightweight Pickaxe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cup of Molten Chocolate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Weapon"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A function is required to create a &lt;a href="https://dart.dev/guides/language/language-tour#maps" rel="noopener noreferrer"&gt;&lt;code&gt;map&lt;/code&gt;&lt;/a&gt; data structure of the tools. The map will then be used to build the slivers. The slivers will contain the category as the header — the one that sticks to the top when scrolled, and then followed by the rest of the items belonging to the category.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;  &lt;span class="kt"&gt;Map&lt;/span&gt; &lt;span class="nf"&gt;categorizeItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;containsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Sticky header
&lt;/h3&gt;

&lt;p&gt;I am using &lt;a href="https://github.com/letsar/flutter_sticky_header" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_sticky_header&lt;/code&gt;&lt;/a&gt; to achieve the sticky header effect. First, update  &lt;code&gt;pubspec.yaml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter_sticky_header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.5.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then run &lt;code&gt;flutter pub get&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The basic structure looks like below. Each one of this is a child of the &lt;code&gt;CustomScrollView.slivers&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;SliverStickyHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;header:&lt;/span&gt; &lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;sliver:&lt;/span&gt; &lt;span class="n"&gt;SliverList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;delegate:&lt;/span&gt; &lt;span class="n"&gt;SliverChildBuilderDelegate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;InkWell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ItemCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
                    &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{...}&lt;/span&gt;
                &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nl"&gt;childCount:&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// SliverChildBuilderDelegate&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// SliverList&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The resulting code looks like the following.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Now, I have finally ticked off an improvement. Will continue to do more.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;A separator between categories on the list view&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/494628992" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Next, I will be using another sliver to achieve a feature that I always wanted to have — a complete spread or a tree, not just the immediate requirements, to show all components required to craft a tool.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;

&lt;h4&gt;
  
  
  References
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;SliverList class &lt;a href="https://api.flutter.dev/flutter/widgets/SliverList-class.html" rel="noopener noreferrer"&gt;https://api.flutter.dev/flutter/widgets/SliverList-class.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Using slivers to achieve fancy scrolling &lt;a href="https://flutter.dev/docs/development/ui/advanced/slivers" rel="noopener noreferrer"&gt;https://flutter.dev/docs/development/ui/advanced/slivers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dart Maps &lt;a href="https://dart.dev/guides/language/language-tour#maps" rel="noopener noreferrer"&gt;https://dart.dev/guides/language/language-tour#maps&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Making the Escapists Crafting Guide App — Foundation</title>
      <dc:creator>Aldrin Navarro</dc:creator>
      <pubDate>Thu, 17 Dec 2020 09:44:45 +0000</pubDate>
      <link>https://dev.to/aldnav/making-the-escapists-crafting-guide-app-n0b</link>
      <guid>https://dev.to/aldnav/making-the-escapists-crafting-guide-app-n0b</guid>
      <description>&lt;p&gt;&lt;strong&gt;Original Post&lt;/strong&gt;: &lt;a href="https://aldnav.com/blog/making-the-escapists-crafting-guide-app/"&gt;https://aldnav.com/blog/making-the-escapists-crafting-guide-app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a part of a series of my journey into mobile application development. I am learning Flutter and I enjoy using it to make apps. The widgets are just amazing. Right off the bat, one can easily focus on creating apps, and making it beautiful, with very minimum learning curve.&lt;/p&gt;

&lt;p&gt;Revisiting Steam games I bought but played only a little, &lt;em&gt;I know I'm not the only one&lt;/em&gt;, I scroll through my library and found &lt;strong&gt;The Escapists 2&lt;/strong&gt;. TE2 is a surprisingly fun game. You play as a prisoner making plans to escape. You also do daily routine as an inmate, jobs, and favors included. And of course, find the right tools to help you escape. Some items can be bought from shops while others can be looted, and crafted to make more sophisticated tools.&lt;/p&gt;

&lt;p&gt;In game, you can view the crafting menu under your profile. Most of the times, I list down which tools I need to craft and the take note of the intelligence level required. I found an opportunity to write an app for it so it's easily accessible to me while I go around looting. It will save me time to not drop off items only to realize I need it at a later time, or found it on a desk and ignore it. Eventually, I would memorize all the components. &lt;em&gt;Naah, seriously I just want to create the app also while learning Flutter&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Things the app should do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;List all tools that can be crafted&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Searching by entering some keyword matching the name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;View details of the tool&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That should be fine for now. Remember, I am just saving some papers.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. All tools
&lt;/h2&gt;

&lt;p&gt;I won't be building the whole library but will build on top of &lt;a href="https://theescapists.gamepedia.com/Crafting"&gt;Gamepedia's The Escapists Wiki on Crafting&lt;/a&gt;. A python script scrapes this page for the info found on the table and crawls on the individual tool's pages to scrape more information. The output is a JSON file which our app will be reading for data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h6qcEJ44--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9o25kibo0yqbciv99ge8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h6qcEJ44--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9o25kibo0yqbciv99ge8.png" alt="Wiki page as input and JSON file as output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What the script will do is to save information about the tool/item, and the requirements, and the intelligence required. The tables are not at all uniform but more info is good. For each tool, the scraper will also fetch some more information on the individual pages.&lt;/p&gt;

&lt;p&gt;The script will improve as I get more information or fix issues.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;I can now use this as a data asset.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jU7iWMDw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2mwo7dv7qep66bxsmrjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jU7iWMDw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2mwo7dv7qep66bxsmrjf.png" alt="Using our scraped data in the assets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Search
&lt;/h2&gt;

&lt;p&gt;For this functionality, I will be using two widgets. A &lt;code&gt;ListView&lt;/code&gt; and a &lt;code&gt;TextField&lt;/code&gt; with a &lt;code&gt;TextEditingController&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ListView&lt;/code&gt; will display the list of items. This is suitable for small as well as long lists as the widget uses lazy loading.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now I need a text field for search, and update the list while the text is being typed.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Here's what the list view looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BBUwfmP0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cbiixldnrnpninpqlvur.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BBUwfmP0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cbiixldnrnpninpqlvur.jpg" alt="List view"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Details
&lt;/h2&gt;

&lt;p&gt;Now the app needs to add a detail screen of the tool where we display the information.&lt;/p&gt;

&lt;p&gt;First, update the &lt;code&gt;ListView&lt;/code&gt; to navigate to the detail screen when tapped.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Then make the actual detail page. It's just a simple &lt;code&gt;SingleChildScrollView&lt;/code&gt; with many text field.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Above is just a sample and not styled yet. Here's what the app looks like with some styling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q6OGfEVn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9dghx276avfywwstad6u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q6OGfEVn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9dghx276avfywwstad6u.jpg" alt="Detail view"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The app is far from complete. Improvements I am considering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A separator between categories on the list view&lt;/li&gt;
&lt;li&gt;Improve the scraper to parse and get more of the details correctly&lt;/li&gt;
&lt;li&gt;Improve the data structure to include relationships between components&lt;/li&gt;
&lt;li&gt;Ability to view a component tree on the detail page using the relationships from the aforementioned improvement&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tapping the image of a component from the detail page to also navigate to it's own detail page&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Firebase and fetch updates to data over the network&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transitions, animations, and theming!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the next part, I will be sharing what I learned while making improvements to The Escapists Craft app. ¡Hasta la próxima!&lt;/p&gt;

&lt;h4&gt;
  
  
  References
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Gamepedia The Escapists Crafting Guide &lt;a href="https://theescapists.gamepedia.com/Crafting"&gt;https://theescapists.gamepedia.com/Crafting&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flutter cookbook: Handle changes to a text field &lt;a href="https://flutter.dev/docs/cookbook/forms/text-field-changes"&gt;https://flutter.dev/docs/cookbook/forms/text-field-changes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flutter cookbook: Working with long lists &lt;a href="https://flutter.dev/docs/cookbook/lists/long-lists"&gt;https://flutter.dev/docs/cookbook/lists/long-lists&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dart</category>
      <category>flutter</category>
      <category>python</category>
      <category>guide</category>
    </item>
    <item>
      <title>Building a Python wrapper of C libgeohash using CFFI</title>
      <dc:creator>Aldrin Navarro</dc:creator>
      <pubDate>Sat, 08 Jun 2019 03:46:38 +0000</pubDate>
      <link>https://dev.to/aldnav/building-a-python-wrapper-of-c-libgeohash-using-cffi-4727</link>
      <guid>https://dev.to/aldnav/building-a-python-wrapper-of-c-libgeohash-using-cffi-4727</guid>
      <description>&lt;p&gt;First post for DEV.TO 🎊️&lt;/p&gt;

&lt;p&gt;I didn't know what a &lt;a href="http://geohash.org"&gt;Geohash&lt;/a&gt; is before and was blown away by it's concept. I found an implementation of it written in C. Well I always have been wanting to experiment with foreign functions and found &lt;a href="https://cffi.readthedocs.io"&gt;CFFI&lt;/a&gt; suitable for my case. On that same afternoon I wrote a simple wrapper of libgeohash in Python.&lt;/p&gt;

&lt;p&gt;Check this out! &lt;a href="https://aldnav.com/blog/writing-a-geohash-wrapper-using-cffi/"&gt;https://aldnav.com/blog/writing-a-geohash-wrapper-using-cffi/&lt;/a&gt;&lt;br&gt;
What's that? Want to jump in to the source code instead? Ok! &lt;a href="https://github.com/aldnav/geohash"&gt;https://github.com/aldnav/geohash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a preview.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# geohash_build.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;cffi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FFI&lt;/span&gt;
&lt;span class="n"&gt;ffi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FFI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cdef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"""
    char* geohash_encode(double lat, double lng, int precision);
    GeoCoord geohash_decode(char* hash);
"""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"geohash.so"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_geohash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simple testing
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;_geohash&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geohash_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;35.689487&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;139.691706&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;#&amp;gt;&amp;gt; xn774c
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Use geohash module
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;geohash&lt;/span&gt;
&lt;span class="n"&gt;geohash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geohash_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;48.856614&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.352222&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# u09tvw for Paris! 
&lt;/span&gt;&lt;span class="n"&gt;geohash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geohash_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"wy7b1h"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# lat: 35.179554, long: 129.075642
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check my notes to see how I build the wrapper. Plus some more references that links to more comprehensive notes from the respective authors.&lt;/p&gt;

&lt;p&gt;Let me know your thoughts!&lt;/p&gt;

</description>
      <category>python</category>
      <category>cffi</category>
      <category>geohash</category>
    </item>
  </channel>
</rss>
