Introducing Genkin - No more spreadsheets for tracking cash flow
Tired of wrestling with spreadsheets to keep tabs on your money? Say goodbye to endless rows and columns.
Hey everyone, I'm Georgey. I've been blogging on Hashnode for about two years now. Life got in the way, especially when I jumped into the corporate world. But I realized I was missing the thrill of coding and creating content. So, here I am, ready to dive back into what I love! Let's get started.
Why did I build Genkin?
Ever since I started earning a handful, I wanted to track it. I used to make spreadsheets for the same. Honestly it was boring, and after quite sometime, these spreadsheets simply became obsolete. This was the idea behind genkin. I wanted to track money without the formulas, easily and effortlessly. I could have built genkin a long time ago without AI, but then I stumbled across Vercel AI SDK. It was amazing how React Server components and Server actions could be used to stream React components onto the client directly from the server, along with tool calling ability from the model.
Development process
Stage 1 - Research
It was clear that I have to use AI to make the app more engaging. Vercel AI SDK documentation is pretty much a good place to get started. There were three tool calls I had decided to put into the model -
listTransactions
: To list the transactions added by the user which will take in two parameters for the model to interpret from the user prompt. The date range to fetch the transactions,fromDate
andtoDate
.createTransactions
: To save transaction(s) entered by the user. This tool call will require four parameters - date of transaction, short description, category, and amount. The model will automatically determine if it's income or expense based on the short description.analyseTransactions
: The category parameter in each transactions will help the user to individually analyze each category. For eg: "How much did I spend on fuel this month?" would then return all transactions related to that category "fuel". Although I couldn't complete this feature in time for the hackathon, I would love to work on it after the results are announced.
Stage 2 - Gathering technologies
To build a banger application, you need some banger modern technologies. I'm no UI/UX expert. During my break, I was hearing a lot about Shadcn.ui, so that was definitely my first round pick for building the UI for the application. The components are customizable and can be used along with TailwindCSS.
I chose Gemini as the AI model for this project because it's free to start with and really easy to use. The Vercel AI SDK made it simple to get going, thanks to their clear documentation. Plus, the Gemini Chat example on Next.js gave me a great starting point.
When its comes to database, I always wanted to build using Supabase because of three reasons
RLS support : Short for "Row Level Security", which helps you add policies to your table. For example, if you want certain data to be accessed only by certain roles, that's where RLS comes into picture. RLS acts as a security layer over your postgres instance and protects your data from malicious requests.
Supabase Auth : RLS can be combined with RLS to let users only access the data created by them and also create data, only if they are authenticated.
App router support : Supabase is completely compatible with the app router of Next.js, and works well both in client and server flawlessly.
Stage 3 - Building the UI
I'm no web designer. I'm not good with colors, and I like minimalistic design. Shadcn works well considering this. Preliminary skeleton came from v0. It does a pretty job in creating simple designs, and then its easy to branch out from that starting point.
For the charts again, Shadcn recently introduced the Charts segment, that uses re-charts behind the scenes. There are several components from the Shadcn library that plays an important role in some of the genkin pages. For instance, the history page uses their Data-Table component, which is built on top of TanStack Table. This gives us powerful tools for sorting and filtering data.
Stage 4 - Integrating Vercel AI SDK
For a complete in-depth understanding of how to use the Vercel AI SDK, there is no place better than the documentation itself.
Here's how the model works to deliver the correct response back to the client.
System Prompt : this prompt is first passed to the model (pre-written) that ensures that the model delivers the correct response by clearly mentioning what to do and what not to do. Prompt engineering is crucial for getting the desired results from the AI model. It helps us avoid confusing or nonsensical answers. The parameters for each tool-call (earlier mentioned in the article) are clearly defined with the help of
zod
library.User then enters the prompt upon which a server action is invoked.
The model then processes the user prompt and takes a decision on whether to execute a tool-call or deliver a normal response from the gemini model.
If its a normal response, a streamed response is delivered to client using the
createStreamableValue()
function.If its a tool-call, the UI state is updated on the client using
createStreamableUI().update()
function. The page on which the response is received has to be a dynamic page for streaming to work properly. At the end, the stream is closed using thecreateStreamableUI().done()
function.
Stage 5 - Authentication
Since Supabase provides RLS (Row Level Security) support, and also supports the app router, the @supabase/ssr
package comes into clutch. Hence, the package can be used to run on the middleware to check incoming request and forward the request if user session exists. There are four events happening during authentication,
User enters GitHub credentials. GitHub is being used as an auth provider for genkin.
supabase.auth.signInWithOAuth()
function is invoked for the same.Inside the callback route, the code generated is exchanged for a session token.
The user is redirected to the dashboard page on successful authentication.
A middleware runs on every page request to update the session, and redirect the user if the session expires/doesn't exist.
To check if user accesses only their own data, Supabase RLS comes handy. To enable RLS, you simply have to go into your project dashboard and enable it on the table settings. Next, add a policy that clearly defines a certain logic for different SQL table operations. For example, if I want only authenticated users to view my data, I would add the following policy
alter policy "Enable read access for all users"
on "public"."main"
to authenticated
using (
true
);
Stage 6 - Working on the anlysing transactions
I wanted the analysis page to give users a deep dive into their transactions, and full control of their data. It's divided into two main sections.
Weekly cashflow analysis
The timelines in this section cannot be changed. The section is built to display your current week cashflows. In the coming update, I intend to make it more flexible to display analysis of any week. The current week's start day and end day is determined by a custom helper function.
export function getCurrentWeekDates() {
let today = new Date();
let currentDay = today.getDay();
let diff = today.getDate() - currentDay;
let startOfWeek = new Date(today.setDate(diff)).toISOString();
let endOfWeek = new Date(today.setDate(diff + 6)).toISOString();
return {
startOfWeek,
endOfWeek,
};
}
today
returns the current datecurrentDay
hold the current day (0 for Sunday, 1 for Monday and likewise)diff
returns back the date of the previous Sunday or current day if its Sunday.Next, the
startOfWeek
returns back the date of the first Sunday of the current week, and likewiseendOfWeek
holds the date for the last day of the week.
The startOfWeek
and endOfWeek
is passed on to query data from the table, to be displayed on to the chart.
Category Analysis
The model automatically assigns a category each time the user enters in a transaction to save, using the defined zod
schema.
export const categorySchema = z.enum([
'Investment',
'Food',
'Cloth & Props',
'Groceries & Supplies',
'Charity',
'Salary',
'Subscription',
'Fuel',
'Other',
]);
This data is used to categorize the transactions entered by the user in the category analysis section.
Problems encountered and fix arounds
There were few problems I had encountered myself in the process of developing genkin. But like they say, the key to solving it is a patient and steady mind. There was this one particular abnormality that I found when I had deployed genkin to production. The tool-call ability to list transactions in the chat was working fine in development, but it seemed like the component was not getting streamed while in production. Now it's always frustrating when something works in development but not in production.
else if (toolName === 'listTransactions') {
const { endDate, fromDate } = args;
console.log('from date', fromDate);
console.log('end date', endDate);
const from = new Date(fromDate);
const to = new Date(endDate);
// interacting with the supabase instance to get the transaction
// data given the from and to date
aiState.done({
...aiState.get(),
interactions: [],
messages: [
...aiState.get().messages,
{
id: nanoid(),
role: 'assistant',
content: `Heres the list of transactions from ${fromDate} to ${endDate}`,
display: {
name: 'listTransactions',
props: {
// you may have to change this afterwards !not for author!
data: args,
},
},
},
],
});
uiStream.update(<ListofTransactions from={from} to={to} />);
}
At first the above piece of code might look a bit mind-boggling to understand but here's what happening. In Vercel AI SDK, there's a concept called AI and UI State. The Ui state is what is being rendered on to the client from the server, and AI state is sort of like an instruction manual for the model to generate its response, the next time when the user continues the conversation. In other words, UI state contains the actual react elements and AI state is the same but in serialized format. For some reason, this particular line of code was working well in development but was breaking in production. As any other developer would do, I went straight to Vercel AI SDK's GitHub repository and searched for an answer. Apparently it was a problem faced by some other developers as well. The likely problem is because, for react streaming, the page has to be a dynamic route. But in my case, since middleware runs on each route, every page is by default, dynamically rendered. Again just to be on the safer side I added the dynamic export on my route, but the issue was still persisting.
export const dynamic = 'force-dynamic';
I lost three days trying to find a fix around this bug. But fortunately I found the error on the last day of the hackathon and on the same day I wrote this article.
I shifted the entire data fetching part from server to client, also converting the existing react server component that gets streamed on listTransaction
to a client component. Which meant that there was some problem sending the data as props through react streaming from the server. The entire load was now shifted to client. Again this is just my theory, and hopefully this issue is solved in the next update of Next.js with Next15.
Project Link
Conclusion
Genkin is my finest build till now. I think to come back from a break and develop something like this, I'm impressed by myself. There were hurdles on the way, but I had some special people backing me up as well.
Finally if I could build this application, so could anyone. All that requires is researching, practicing, and sheer persistence. So here's some useful links to get started with building full stack apps like Genkin.