I have always been fascinated by frontend development, if not particularly good at it. In the sophomore year of my Bachelor’s degree, I had a web development course that required me to build a website for one of the department stores of my country (think Haitian IKEA). I got the grade, passed the course, but it was a singularly hideous website that proved more technical ability than taste. After that, I told myself frontend wasn’t really for me and stayed in the backend and data side of things for years.
When building projects at work or during my free time, I go to the Python library of perfection: Streamlit. It’s easy to start with, quick to ship, especially for GenAI and Machine Learning tools. For quite some time, whenever I needed a chatbot-like interface, I would rely on Streamlit, design a few things, and call it done.
But fairly quickly, Streamlit’s ceiling in production would be reached. The entire app re-executes on every interaction. Session state becomes a tangled mess. Real-time streaming requires hacks and patches. And when you need something beyond basic layouts—drag-and-drop, custom widgets, fine-grained animations—you’re fighting the framework instead of building with it.
This is not to hit on Streamlit, just recognizing its strengths and weaknesses. When the time came recently to work on a new project, I knew I wanted to go in another direction and try my hand at proper frontend development—Next.js, React, the whole ecosystem I’d been avoiding.
That’s how I stumbled onto CopilotKit, an open-source framework for building AI copilots into React apps. What caught my attention was the AG-UI protocol—a way for agents to not just respond with text, but actually interact with the frontend. The agent can read UI state, trigger actions, push updates. It’s a two-way street between the AI and the interface.
I didn’t know exactly what I wanted to build yet, but the possibilities were already spinning in my head.
Around the same time, I was playing Clair Obscur: Expedition 33. The game blends turn-based combat with real-time Souls-like mechanics—two things that shouldn’t work together, but absolutely do. It feels familiar and completely fresh at the same time.
One evening I was on my couch, controller in hand, when it hit me. What if I did the same thing with my project? Take a traditional BI dashboard—bar charts, heatmaps, filters—and fuse it with conversational AI. Not a chatbot next to a dashboard. A chatbot inside the dashboard, building and modifying visualizations on the fly based on natural language. Two paradigms forced together, each making the other better.
That became the concept. Now I just had to build it.
Once I got past the initial learning curve, so much just made sense. Components that update independently. State that flows predictably. Streaming responses that don’t fight you. Things that required workarounds in Streamlit were just… how the framework worked. I’d write something and think, “Wait, that’s it?” No st.rerun() gymnastics. No session state voodoo. Just code that did what I expected.
But the learning curve was steep. Even with Claude Code by my side—and I leaned on it heavily—some bugs took hours to crack.
The code interpreter tool, for example. It would work fine, then suddenly crash. No clear error. I refactored, checked permissions, combed through logs. Turned out the data payload I was passing was too large. That’s it. A five-second fix after hours of digging.
Then there was the persistence bug.
I could save chat messages to the backend perfectly. But restoring them to the UI? That’s where things fell apart. Imagine a TV that only wants to show the live football game. You plug in your HDMI cable to watch a movie, but the TV refuses—it only accepts the live broadcast signal. That was CopilotKit’s sidebar. It would display messages streaming in real-time, but it had no pathway to show messages I’d saved and was trying to feed back in. The data was there. The TV just wouldn’t play it.
This is what building on the bleeding edge looks like. “Bleeding edge” means the technology is so new that you might get cut—bugs, missing features, sparse documentation. The community hasn’t built up yet. When you hit a wall, there’s no Stack Overflow answer. You file an issue and wait, or you figure it out yourself.
The fix I landed on was a workaround. CopilotKit lets you override how messages are rendered, so instead of fighting the internal plumbing, I wrote a custom component that combines restored history with the live stream. It’s not elegant, but it ships.
For those curious, here’s my GitHub issue where I explain the bug in depth. Hoping for a quick resolution!
The dashboard/agent is coming along nicely now. Users can chat with the agent, ask questions about the data/dashboard, and watch visualizations appear in real time. It’s rough around the edges, but the core idea—conversation and dashboard as one—actually came together. I am currently working on authentication and will ship soon.
Years ago, I built a not so great website and told myself frontend wasn’t for me. This project proved that wrong. The learning curve was steep, the bugs were brutal, but I came out the other side with a working product and a growing new set of skills. Sometimes you just have to jump in.

Leave a comment