Monday.com + GPT app tutorial: How to make a debug assistant
Let's build with Monday.com and GPT!
Every programmer knows the pain of debugging. From trying to identify the culprit to navigating old Stack Overflow posts, the process can be long and frustrating. We're here to help! We are going to build a NextJS app that integrates with Monday's Incoming Bugs board, reads the description of the problem and then automatically populates another column with suggestions on how to fix it.
What is Monday.com?
Monday.com is a customizable web and mobile work management platform, designed to help teams and organizations with operational efficiency by tracking projects and workflows, visualizing data, and team collaboration. It includes automation capabilities and supports integrations with other work apps.
Setting up your Monday App
First, create a Monday.com developer account if you haven't already.
After finishing setting your account, log in to your Monday.com dashboard and press the blue button on the left panel to add a new item to your workspace. Click on "Choose from template" and then select the Product Development template. Click on "Use template".
This should add the template on your dashboard, with a Bug Queue board, which we will use later to fetch Bug descriptions and send them to GPT. Add a new column to the Incoming Bugs table and call it "Suggestions". Your dashboard should look like this:
Now, click on your avatar -> Developers and press the "Create app" button. Let's call it Debug Assistant.
Go to the OAuth section of your app, and add add the following permissions: "me:read", "boards:read" and "boards:write"
We can now proceed to the installation.
π€ Installation
To get a head start, we are going to use a NextJS app template from Monday.com that provides ready-to-use React components and context. You can later modify these to improve the UI/UX.
Clone and install the template:
git clone https://github.com/yuhgto/monday-ai-prompt-template
cd monday-ai-prompt-template
npm i
Open your .env file and paste your OPENAI_API_KEY values.
We can now start our development server running:
npm run dev
# or
yarn dev
# or
pnpm dev
In order to connect this app to Monday, you need to create a tunnel to your local environment.
[Install ngrok and sign up for an account (https://ngrok.com/download)].
Then, run the following command:
ngrok http 3000
This will expose your local server trough a public URL. Save that URL.
Connecting to Monday
Go back to your Monday app and add a new feature. Choose the Dashboard Widgets option, then "Start from scratch".
Let's call our feature "Debug Widget" and go to the "Widget Setup" tab. Select "Custom URL" as source, paste your ngrok URL and click on "View URL".
Note: If you restart the ngrok server, the URL will change and you will have to change it here too.
You should be able to see your local app running inside an iframe. This means the app is connected to Monday.com!
Now change the source to Published Build, click on the "New build" button, and add your ngrok URL.
Note: This build is intended for development only, and it will work as long your ngrok is running with the same URL. In other words, everytime you restart your ngrok server, you will have to create a new build with the new URL.
Go now to your app's dashboard, click on App Versions. Click on the three dots of your app's first version, and publish it.
Then, on your app's dashboard again, click on "Install" on the left menu and then on the "Install app" button.
Your should now be able to add your app to your Monday board. Go back to your workspace, and open the Bug Insights view on the Bug Queue board, then click on Add Widget -> Apps.
Go to Installed apps and add the Debug Widget to the board.
You should see the app running inside the widget. Awesome, we are finaly ready to start coding!
π Developing the app
Go back to your code editor, and copy the boilerplate file located at monday-ai-prompt-template/src/examples/livestream-example/boilerplate.tsx, then paste it in the same directory and rename it to "debug-assistant.tsx". We will write most of our code here.
Change the component function name from "LivestreamExample" to "DebugAssistant" in line 64 and don't forget to rename the export function too at the end of the file:
const DebugAssistant = ({ initialInput = "" }: Props): JSX.Element => {
....
}
export default DebugAssistant;
Now open the app's main page located at monday-ai-prompt-template/src/app/page.tsx since NextJS v13, comment out or remove ContextExplorerExample Component, and add the DebugAssistant component.
...
import DebugAssistant from '@/examples/livestream-example/debug-assistant';
export default function Home() {
return (
<div className={styles.App}>
<AppContextProvider>
{/* <ContextExplorerExample /> */}
<DebugAssistant />
{/* <LivestreamExampleFinal /> */}
{/* <BasePromptLayout /> */}
</AppContextProvider>
</div>
)
}
We are going to reuse a lot of code from Monday's template, adding some tweaks to get it working on our development server.
Go to debug-assistant.tsx, then import dynamic from next and also Monday's Dropdown component:
...
import dynamic from "next/dynamic";
...
const Dropdown = dynamic(
() => import("monday-ui-react-core").then((mod) => mod.Dropdown),
{
ssr: false,
}
);
Add 2 Dropdown's to your render, one for selecting a board, and the other one for selecting a column. Your rendering section should look like this:
...
return (
<div className={classes.main}>
<div className={classes.dropdownContainer}>
<Dropdown
options={boardGroupsForDropdownComponent}
onChange={handleGroupSelect}
placeholder="Select a group"
size="small"
/>
<Dropdown
options={boardColumnsForDropdownComponent}
onChange={handleColumnSelect}
placeholder={"Select an output column"}
size="small"
/>
</div>
{canRenderInput && (
<TextInputWithTagsAndSend
className={classes.inputContainer}
onSend={handleSend}
validTags={boardColumnsForTagsComponent ?? []}
initialInput={initialInput}
mode={mode}
setMode={setMode}
loading={loading}
success={success}
error={error}
/>
)}
<div className={classes.footer}>
<AiAppFooter />
</div>
</div>
);
In the getItemsAndColumnValues(), add context?.iframeContext?.boardIds as a fallback value for boardId:
...
async function getItemsAndColumnValues(selectedGroup, context, columnIds) {
return await executeMondayApiCall(
`query ($boardId:[Int], $columnIds:[String], $groupId: [String]) { boards (ids:$boardId) { groups(ids:$groupId) { items { id column_values (ids:$columnIds) { text id } } } } }`,
{
variables: {
columnIds,
boardId:
context?.iframeContext?.boardId ??
context?.iframeContext?.boardIds ?? // <-- ADD THIS
[],
groupId: selectedGroup,
},
}
);
}
...
Now write the code for the handleSend() function:
const handleSend = useCallback(
async (input: string) => {
setMode(Modes.response);
const columnIds = getColumnIdsFromInputTags(input);
var itemColumnValuesFromMonday = await getItemsAndColumnValues(
selectedGroup,
context,
columnIds
);
if (itemColumnValuesFromMonday.is_success) {
const { items } = itemColumnValuesFromMonday.data.boards[0].groups[0];
var prompts: string[] = items.map((item: any) => {
return replaceTagsWithColumnValues(input, item.column_values);
// return input
});
var itemIdsList: { id: string }[] =
itemColumnValuesFromMonday?.data.boards[0].groups[0].items.map(
(x: any) => x.id
);
} else {
showErrorMessage("Failed getting column values from monday", 3000);
setMode(Modes.request);
return null;
}
const aiApiResponse = await aiApiStatus.fetchData({
prompts: prompts, // or promptsWithColumnValues,
items: itemIdsList,
n: 1, // or itemsFromMonday.length
});
if (aiApiResponse.length === 0 || error) {
showErrorMessage("Something went wrong", 3000);
setMode(Modes.request);
} else {
// avoid GraphQL type errors on dev
let boardId =
!context?.iframeContext?.boardId &&
context?.iframeContext?.boardIds !== null &&
context?.iframeContext?.boardIds !== undefined
? context?.iframeContext?.boardIds[0]
: context?.iframeContext?.boardId ??
context?.iframeContext?.boardIds ??
[];
// update items on board
let itemUpdates = aiApiResponse.map(
async (result: { item: string, result: string }) => {
return await executeMondayApiCall(
`mutation ($column:String!,$boardId:Int!, $itemId:Int!, $value:String!) {change_simple_column_value (column_id:$column, board_id:$boardId, item_id:$itemId, value:$value) {id }}`,
{
variables: {
column: outputColumn,
boardId: boardId,
itemId: parseInt(result.item),
value: result.result,
},
}
);
}
);
let success = await Promise.all(itemUpdates);
setSuccess(!!success);
setMode(Modes.request);
}
},
[aiApiStatus, selectedGroup, context, outputColumn, error]
);
This will send the prompts to OpenAI and then make the GraphQL mutations to update the column we want.
We are almost done. There is a bug on newer versions of NextJS (~13.4.x) related to fetching requests on localhost. To circumvent this, open the app/api/openai/route.ts file, and add this at the end:
...
export const dynamic = "force-static";
This will fix the error, but it will remove the headers from our requests. Lastly, to make our lifes easier during development, we'll skip authentication for now. On route.ts, inside the POST function, comment out the authentication verification:
...
export async function POST(req: NextRequest, res: NextResponse) {
const reqJson = await req.json();
console.log(`A request was made. \nRequest:${JSON.stringify(reqJson)}`)
/*if (!isAuthorized(req)) {
return NextResponse.json({ message: "Not authorized", success: false },
{status:401});
} else*/ if (!reqJson.items) {
return NextResponse.json({'message': 'No items array supplied'}, {
status: 400,
})
}
else if ...
...
}
This is fine during development, but you must remember to add the verification again before pushing to product. The changes we just did on route.ts are only necessary to make it all work with our local context.
We are ready to start testing our app! Go back to Bug Queue -> Bug Insights board on Monday.com, and open the Debug Widget. The new UI should be running inside it. You might need to reload the page.
On the first dropdown select "Incoming Bugs" group, and on the second dropdown select "Suggestions" as output. Then write the following on the text input: "Write a short technical solution for this bug description: @Bug Description" (You should be able to select "Bug Description" after the @). Send the request.
π There you go! You should see the Suggestions column being filled up after a few seconds.
This template is using GPT3.5 (text-davinci-003 model) by default. If you want to use another one, like GPT4, open src/services/openai-service.ts and change the name of the model there.
π€ Final Thoughts
We learned how to create a Monday app and connect it to an LLM through a local development server. Now, you are free to change the UI, refine the prompts and play with it as much as you want. The sky is the limit! Whenever your app is ready, you should deploy it to a production server and configure it accordingly. That is all for this tutorial, I hope it will help you navigate all the different parts of the Monday.com app framework. Feel free to reach out to me on LinkedIn or Twitter if you have any questions.
And practice what you've learn here during our amazing AI Hackathons! Join the AI revolution!