**Creating a knowledgebase with Search Functionality in a React Native App with Strapi and AI** Comment Suggest edit Please copy-paste this in a new HackMD file and share it with dessire.ugarte-amaya@strapi.io. meta-title: under 65 characters meta-description: under 155 characters Figma images: Use this figma file Publish date: Reviewers: **Introduction** in today's world, you can get information about almost anything online. as a product developer, you want to control the quality of your product's information and the best way to do this is by creating a knowledgebase. This is a place where users and employee can seek for information regarding your project. In this article, you will learn how to create a knowledgebase using React Native and Strapi, and how to create a search functionality using AI. Here is an overview of the application's architecture: ![strapi.drawio](https://hackmd.io/_uploads/ry6GclIIR.png) **Prerequisites** * Basic knowledge of React Native * Node.js: * Visual studio code * Basic Knowledge of Strapi * Knowledge of LangChain * Basic knowledge of Python **Setting Up Strapi** before you create your Strapi application, ensure that you have the latest Strapi installed on your computer using the following command: `npm install -g strapi@latest` To start creating your Strapi application using NPM, open your VScode terminal and add the following code: ``` npx create-strapi-app@latest my-project # 'npx' runs a command from an npm package # 'create-strapi-app' is the Strapi package # '@latest' indicates that the latest version of Strapi is used # 'my-project' is the name of your Strapi project ``` On your installation process, you will have the following prompts: | Prompt | Choose option | Explaination | | ----------------------------- | ------------- | ------------------------------------------------- | | Choose your installation type | Quickstart | quick installation with a default SQLite database | | Please log in or sign up. | Skip | not deploying to cloud. development stage. | when the installation is done, you will see this response on your terminal: ![Screenshot (55)](https://hackmd.io/_uploads/rk8U99eLA.png) Strapi will also redirect you to a browser signin page as an admin: ![Screenshot (54)](https://hackmd.io/_uploads/BklPnqlU0.png) After logging, follow the next steps to setup your backend. **Step 1: creating a collection for your articles** collections are a set a grouped content, collections create API endpoints for the content also. since we are creating a knowledgebase, we will start by creating a collection for our articles. 1. navigate to **Content Type Builder** on the sidebar. click on the **Create new collection type** button and on the pop-up modal, add the collection name and click on **Continue**: ![Screenshot (67)](https://hackmd.io/_uploads/ry6muhxLA.png) After, you click on the Continue button, you will get another modal to add content fields. first, select the field: ![Screenshot (59)](https://hackmd.io/_uploads/rJo7h2lLR.png) Then, add the name and other details, and click on the **Add another field** button to add more fields: ![Screenshot (62)](https://hackmd.io/_uploads/rJtLi2x8C.png) we will create a collection for the articles with the following content fields: **Short Text**: *title* **Short Text**: *summary* **Rich Text (Blocks)**: *body* **Media**: *cover* once you are done adding all these fields, your collection should look like this: ![Screenshot (68)](https://hackmd.io/_uploads/B18Sphx8A.png) **Step 2: adding content to your collection** Strapi enables non-technical users or marketers to add content to the knowledgebase seamlessly without any technical requirements. 1. to start adding article entries, you have to navigate to **Content Manager** on the sidebar, click on the collection you want to use and click on the **Create New Entrie** button ![Screenshot (69)](https://hackmd.io/_uploads/HkZrJplIA.png) this will lead you to the collection inputs you can add content to, after adding the content, click on **Save**: ![Screenshot (70)](https://hackmd.io/_uploads/S1-N-6xU0.png) After saving, to add more entries, click on the **Create new entry** button and add content: ![Screenshot (76)](https://hackmd.io/_uploads/SJDwAjMUC.png) **Step 3: creating the API** before you enter more entries, lets test the API to see if it behaves the way we expected. 1. first you need to generate your API key. to do this, Navigate to **Settings** on the sidebar, click on **API tokens** on the settings menu and click on the **Add new API Token** button: ![Screenshot (71)](https://hackmd.io/_uploads/ByHPz6gLR.png) this will lead you to a page you can add your API name, duration and type: ![Screenshot (75)](https://hackmd.io/_uploads/B10IuYb8A.png) **Setting Up the React Native App** to start creating your React Native application, open a different folder ouside your Strapi root folder on your VScode terminal and add the following command to create a new application: `npx create-expo-app kb-react-native --template blank` then install web and other dependencies on your application's root folder in order to run the application on your web browser: `npx expo install react-dom react-native-web @expo/metro-runtime` `npm install @react-navigation/native @react-navigation/stack` `npm install axios` `npm i react-native-modal` after installations, run the following code to run your application: `npx expo start` this will show you the following page on your http://localhost/8081 by default. Now that you have confirmed your expo React Native application is running, its time to do some editing. first, create a file named ***api.js*** on your root folder to handle your api calls: ``` // api.js import axios from 'axios'; const API_URL = 'http://localhost:1337/api'; const API_KEY = '<your API key>'; const getArticles = async () => { try { const response = await axios.get(`${API_URL}/knowledgebase-articles?populate=*`, { headers: { Authorization: `Bearer ${API_KEY}`, }, }); return response.data; } catch (error) { console.error('Error fetching articles:', error); throw error; } }; export { getArticles }; ``` Then, on your root folder, create a folder named ***components*** and add the ***article_card.js*** file and add the following code to it: ``` import React, { useState } from 'react'; import { View, Text, StyleSheet, Image, Pressable } from 'react-native'; import { useNavigation } from '@react-navigation/native'; const ArticleCard = ({ article }) => { const navigation = useNavigation(); const API_URL = 'http://localhost:1337'; return ( <View style={styles.card}> <Pressable onPress={() => navigation.navigate('FullArticle', { article:article })}> <Text style={styles.title}>{article.attributes.title}</Text> <Image source={{ uri: API_URL + article.attributes.cover.data.attributes.formats.thumbnail.url }} style={styles.cover} /> <Text style={styles.summary}>{article.attributes.summary}</Text> </Pressable> </View> ); }; const styles = StyleSheet.create({ card: { padding: 20, flexGrow: 1, margin: 10, backgroundColor: '#fff', borderRadius: 10, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, width: '40%', alignSelf:'center' }, title: { fontSize: 18, fontWeight: 'bold', width: '90%', textAlign:'center' }, summary:{ fontSize: 14, width: '90%', padding:10, textAlign:'center' }, cover: { width: 250, height:150, backgroundColor:'black', alignSelf:'center', margin:10 } }); export default ArticleCard; ``` Then create another file named ***full_article.js*** under the ***components*** folder and add the following code: ``` // components/full_article.js import React from 'react'; import { View, Text, ScrollView, StyleSheet, Image } from 'react-native'; const FullArticle = ({ route }) => { const { article } = route.params; const API_URL = 'http://localhost:1337'; if (!article || !article.attributes) { return ( <View style={styles.container}> <Text>Article details not available.</Text> </View> ); } const { title, cover, body, summary } = article.attributes; const renderChildren = (children) => { return children.map((child, index) => { if (typeof child === 'string') { return <Text key={index} style={styles.body}>{child}</Text>; } else if (child.text) { return <Text key={index} style={styles.body}>{child.text}</Text>; } return null; }); }; const renderBody = (body) => { return body.map((element, index) => { switch (element.type) { case 'heading': return ( <Text key={index} style={styles[`heading${element.level}`]}> {renderChildren(element.children)} </Text> ); case 'paragraph': return ( <Text key={index} style={styles.body}> {renderChildren(element.children)} </Text> ); case 'list': return ( <View key={index} style={styles.list}> {element.children.map((listItem, listIndex) => ( <Text key={listIndex} style={styles.listItem}> - {renderChildren(listItem.children)} </Text> ))} </View> ); default: return null; } }); }; return ( <ScrollView contentContainerStyle={styles.container}> <Text style={styles.title}>{title}</Text> <Image source={{ uri: API_URL + cover.data.attributes.url }} style={styles.cover} /> <Text style={styles.summary}>{summary}</Text> {renderBody(body)} </ScrollView> ); }; const styles = StyleSheet.create({ container: { flexGrow: 1, backgroundColor: '#fff', padding: 15, width:'65%', textAlign:'center', alignSelf:'center', height:1500, }, title: { fontSize: 44, fontWeight: 'bold', marginBottom: 16, textAlign:'center' }, summary:{ fontSize: 16, margin: 16, textAlign:'center' }, cover: { width: '90%', height:500, borderRadius: 8, marginBottom: 16, textAlign:'center', alignSelf:'center' }, heading1: { fontSize: 22, fontWeight: 'bold', marginBottom: 8, }, heading2: { fontSize: 20, fontWeight: 'bold', marginBottom: 8, }, heading3: { fontSize: 18, fontWeight: 'bold', marginBottom: 8, }, heading4: { fontSize: 16, fontWeight: 'bold', marginBottom: 8, }, heading5: { fontSize: 14, fontWeight: 'bold', marginBottom: 8, }, heading6: { fontSize: 12, fontWeight: 'bold', marginBottom: 8, }, body: { fontSize: 16, lineHeight: 24, marginBottom: 10, }, list: { marginBottom: 10, }, listItem: { fontSize: 16, lineHeight: 24, marginLeft: 20, }, }); export default FullArticle; ``` after you code the components, go to your ***app.js*** file and add the following code: ``` // App.js import React, { useEffect, useState } from 'react'; import { Button, View, FlatList, StyleSheet, ScrollView } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { getArticles } from './api'; import ArticleCard from './components/article_card'; import FullArticle from './components/full_article'; import Modal from "react-native-modal"; import Searchbox from './components/search_box'; const Stack = createStackNavigator(); const ArticleList = () => { const [articles, setArticles] = useState([]); const [isModalVisible, setModalVisible] = useState(false); const toggleModal = () => { setModalVisible(!isModalVisible); }; useEffect(() => { const fetchArticles = async () => { try { const data = await getArticles(); setArticles(data.data); console.log(data); } catch (error) { console.error('Error fetching articles:', error); } }; fetchArticles(); }, []); return ( <ScrollView style={styles.container}> <Button title="search the knowledgebase" onPress={toggleModal} style={styles.btn} /> <Modal isVisible={isModalVisible} hasBackdrop={true} backdropColor='white' backdropOpacity={0.94}> <View style={{ flex: 1 }}> <Searchbox article={articles} /> <Button title="close" onPress={toggleModal} /> </View> </Modal> <FlatList data={articles} renderItem={({ item }) => <ArticleCard article={item} />} keyExtractor={(item) => item.id.toString()} /> </ScrollView> ); }; export default function App() { return ( <NavigationContainer> <Stack.Navigator initialRouteName="ArticleList"> <Stack.Screen name="ArticleList" component={ArticleList} options={{ title: 'Articles' }} /> <Stack.Screen name="FullArticle" component={FullArticle} options={{ title: 'Full article'}} /> </Stack.Navigator> </NavigationContainer> ); } const styles = StyleSheet.create({ container: { flexGrow: 1, backgroundColor: '#fff', paddingTop: 50, minHeight:1700 }, btn:{ margin:20, width: 200 } }); ``` great work, you have covered the core of your frontend code. next we have to create the search function and UI. **Creating the search function and UI** In this section, you use AI to search your knowledgebase. we will use LangChain as the AI tool and python to interact with our LLM model which will use Faiss as the vectorstore, then create a UI component on our React Native application. for your python code, you have to ensure you have the following installed or install them using pip: `pip install Flask[async]` `pip install faiss-cpu` `pip install flask-cors` `pip install langchain_cohere` `pip install langchain_community` after installing all the requirements, create a python file named ***llm_backend.py*** and add the following code: ``` from flask import Flask, request, jsonify from dotenv import load_dotenv import os from flask_cors import CORS import requests from langchain.indexes import VectorstoreIndexCreator from langchain_community.docstore.document import Document from langchain_cohere import CohereEmbeddings, CohereRagRetriever, ChatCohere from langchain_text_splitters import CharacterTextSplitter from langchain_community.vectorstores import FAISS from langchain_community.document_loaders import TextLoader from langchain_core.messages import HumanMessage, SystemMessage load_dotenv() app = Flask(__name__) CORS(app) # Allow CORS for all routes cohere_api_key = os.getenv('COHERE_API_KEY') API_KEY = '<strapi api key>' if not cohere_api_key: raise ValueError("COHERE_API_KEY environment variable not set.") # Function to fetch articles from API endpoint and map to Document objects def fetch_articles_and_map_to_documents(): try: api_url = 'http://localhost:1337/api/knowledgebase-articles?populate=*' headers = {'Authorization': f'Bearer {API_KEY}'} # Fetch data from API endpoint response = requests.get(api_url, headers=headers) data = response.json().get('data', []) # Map data to Document objects documents = [] for item in data: document = Document( page_content=format_content(item.get('attributes', {}).get('body', [])), metadata={"source": item.get('attributes', {}).get('title', '')} ) documents.append(document) return documents except Exception as e: print('Error fetching articles from API endpoint:', e) raise # Function to format content from API response def format_content(content): # Customize as per your content structure formatted_content = [] for item in content: if item.get('type') == 'paragraph': formatted_content.append(item.get('children')[0].get('text')) elif item.get('type') == 'list': # Handle list formatting if needed pass # Add more conditions as per your content structure return '\n\n'.join(formatted_content) embedding_function = CohereEmbeddings(cohere_api_key=cohere_api_key) # Route to handle RAG QA queries @app.route('/rag-qa', methods=['POST']) async def rag_qa(): data = request.get_json() user_query = data['question'] chat_llm = ChatCohere(model="command-r") print(user_query) try: # Fetch articles from API endpoint and map to Document objects print("fetchting documents") documents = fetch_articles_and_map_to_documents() print("got the documents") print("Faissing documents") try: db = FAISS.from_documents(documents, embedding_function) print("similarity search") docs = db.similarity_search(user_query) # Query the vector store index for relevant documents results = docs[0].page_content results_metadata = docs[0].metadata['source'] messages = [ SystemMessage(content=f'please keep the response very short. {results}'), HumanMessage(content=user_query), ] llm_response = chat_llm.invoke(messages) print(f'llm_response: {llm_response}') print(results_metadata) except Exception as e: print(e) results = results[:200] return jsonify({'response': llm_response.content, 'metadata_title': results_metadata}) except Exception as e: return jsonify({'error': str(e)}), 500 if __name__ == '__main__': app.run(debug=True) ``` after you are done, use the following code to run your python application: `python llm_backend.py` after this, you should create a React native component to handle the search UI. create a file named ***search_box.js*** under your ***components*** folder and add the following code: ``` import React, { useState, useEffect } from 'react'; import { View, Text, TextInput, Button, StyleSheet, FlatList, ScrollView } from 'react-native'; import axios from 'axios'; import ArticleCard from './article_card'; const API_URL = 'http://localhost:5000'; const Searchbox = ({article}) => { const [question, setQuestion] = useState(''); const [response, setResponse] = useState(''); const [sourceTitle, setSourceTitle] = useState(''); const handleQuery = async () => { try { const response = await axios.post(`${API_URL}/rag-qa`, { question: question,}); console.log(response.data); setResponse(response.data.response); setSourceTitle(response.data.metadata_title); console.log(sourceTitle) } catch (error) { console.error('Error querying Cohere RAG QA:', error.message); setResponse('Error querying Cohere RAG QA'); } }; return ( <View style={styles.container}> <Text style={styles.title}>Search with AI</Text> <TextInput style={styles.input} placeholder="Enter your question" value={question} onChangeText={text => setQuestion(text)} /> <Button title="Ask" onPress={handleQuery} /> {response ? ( <View style={styles.responseContainer}> <Text style={styles.responseTitle}>AI Response:</Text> <Text style={styles.responseText}>{response}</Text> <Text style={styles.articlesTitle}>Source Articles:</Text> <FlatList data={article.filter(article => article.attributes.title === sourceTitle)} renderItem={({ item }) => <ArticleCard article={item} />} keyExtractor={(item) => item.id.toString()} /> </View> ) : null} </View> ); }; const styles = StyleSheet.create({ container: { flexGrow: 1, justifyContent: 'center', padding: 20, alignItems: 'center', fontSize:10 }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20, }, input: { borderWidth: 1, borderColor: '#ccc', padding: 10, marginBottom: 10, width: '100%', }, articlesTitle: { fontSize: 18, fontWeight: 'bold', marginTop: 20, marginBottom: 10, }, articleItem: { padding: 10, borderWidth: 1, borderColor: '#ccc', marginBottom: 10, width: '100%', }, responseContainer: { marginTop: 20, alignItems: 'center', }, responseTitle: { fontWeight: 'bold', marginBottom: 10, }, responseText: { fontSize: 16, }, }); export default Searchbox; ``` **results** to see the results, ensure all your servers are running and open http://localhost:8081/ on your browser. this is a demo of how the application works: ![Articlesand6morepages-Personal-MicrosoftEdge2024-06-2321-00-05-ezgif.com-video-to-gif-converter](https://hackmd.io/_uploads/BJJmgZUL0.gif) this is the search results: ![Screenshot (77)](https://hackmd.io/_uploads/HJuwZbUIA.png) **Conclusion** Strapi enables you create a headless CMS with API endpoints that make it easy to integrate your application with different software tools. as shown on this tutorial, Strapi connected to React Native and LangChain seamlessly.