UI Frameworks for Enterprise AI Apps
1. Framework Selection Guide
| Framework | Best For | Streaming | Auth | Custom UI |
|---|---|---|---|---|
| Streamlit | Internal tools, prototypes, data apps | st.write_stream() | Limited (streamlit-authenticator) | Low |
| Chainlit | Production chat apps, agent UIs | Native SSE | Built-in OAuth | Medium |
| Gradio | ML demos, model showcases | Generator functions | Hugging Face Spaces | Low–Medium |
| Next.js + Vercel AI SDK | Enterprise web apps, full custom UI | useChat / useCompletion hooks | NextAuth.js / Auth0 | Full |
| HTMX | Lightweight server-rendered UIs | SSE extension | Any backend auth | Medium |
| Open WebUI | Self-hosted ChatGPT-like interface | Native | Built-in user management | Via plugins |
2. Streamlit Chat App
Streamlit provides a simple chat component with native message history. It's ideal for rapid internal tools but runs as a Python server — not suitable for high-concurrency production.
pip install streamlit langchain-openai
streamlit run app.py
import streamlit as st
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage
st.title("LangChain Chat")
llm = ChatOpenAI(model="gpt-4o-mini", streaming=True)
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
MessagesPlaceholder("history"),
("human", "{question}"),
])
chain = prompt | llm
# Session state persists across Streamlit reruns
if "history" not in st.session_state:
st.session_state.history = []
# Display chat history
for msg in st.session_state.history:
role = "user" if isinstance(msg, HumanMessage) else "assistant"
with st.chat_message(role):
st.write(msg.content)
# Chat input
if question := st.chat_input("Ask me anything..."):
with st.chat_message("user"):
st.write(question)
with st.chat_message("assistant"):
# Stream tokens into the chat bubble
response_text = st.write_stream(
chain.stream({
"question": question,
"history": st.session_state.history,
})
)
# Update history
st.session_state.history.append(HumanMessage(content=question))
st.session_state.history.append(AIMessage(content=response_text))
3. Chainlit Production Chat App
Chainlit is purpose-built for LLM applications — it provides streaming, file uploads, step visualisation (showing agent thinking), OAuth, and user session management out of the box.
pip install chainlit langchain-openai
chainlit run app.py -w
import chainlit as cl
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
from langchain_core.messages import HumanMessage
@tool
def search_kb(query: str) -> str:
"""Search the knowledge base."""
return f"Found: {query} is explained in the documentation."
llm = ChatOpenAI(model="gpt-4o-mini", streaming=True)
agent = create_react_agent(llm, tools=[search_kb])
@cl.on_chat_start
async def start():
cl.user_session.set("history", [])
await cl.Message(content="Hello! I'm your AI assistant. How can I help?").send()
@cl.on_message
async def handle_message(message: cl.Message):
history = cl.user_session.get("history", [])
msg = cl.Message(content="")
# Stream agent response token by token
async for chunk, metadata in agent.astream(
{"messages": history + [HumanMessage(content=message.content)]},
stream_mode="messages",
):
if hasattr(chunk, "content") and chunk.content:
await msg.stream_token(chunk.content)
elif hasattr(chunk, "tool_calls") and chunk.tool_calls:
for tc in chunk.tool_calls:
async with cl.Step(name=f"Tool: {tc['name']}") as step:
step.input = str(tc["args"])
await msg.send()
cl.user_session.set(
"history",
history + [HumanMessage(content=message.content)],
)
# Welcome to My AI Assistant 🤖
This assistant can:
- Answer questions about our product
- Search the knowledge base
- Help with technical support
Ask me anything!
4. Gradio Chat Interface
Gradio's ChatInterface component provides a chat UI in ~10 lines. It can be deployed to Hugging Face Spaces for free.
import gradio as gr
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
llm = ChatOpenAI(model="gpt-4o-mini", streaming=True)
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
MessagesPlaceholder("history"),
("human", "{question}"),
])
chain = prompt | llm
def chat(message: str, gradio_history: list) -> str:
# Convert Gradio history format to LangChain messages
lc_history = []
for human, ai in gradio_history:
lc_history.append(HumanMessage(content=human))
lc_history.append(AIMessage(content=ai))
# Stream response
full_response = ""
for chunk in chain.stream({"question": message, "history": lc_history}):
full_response += chunk.content
yield full_response # Gradio expects generator for streaming
demo = gr.ChatInterface(
fn=chat,
title="AI Assistant",
description="Powered by LangChain + GPT-4o-mini",
examples=["What is RAG?", "Explain LangGraph", "How does LangSmith work?"],
type="messages",
)
if __name__ == "__main__":
demo.launch(share=False, server_port=7860)
5. Next.js + Vercel AI SDK
For enterprise-grade custom UIs, Next.js with the Vercel AI SDK provides React hooks that handle streaming, message state, and loading indicators — connecting to your LangServe backend.
npx create-next-app@latest my-ai-app --typescript --tailwind
cd my-ai-app
npm install ai @langchain/core
import { StreamingTextResponse } from "ai";
import { RemoteRunnable } from "@langchain/core/runnables/remote";
const chain = new RemoteRunnable({
url: process.env.LANGSERVE_URL + "/chat",
});
export async function POST(req: Request) {
const { messages } = await req.json();
const lastMessage = messages[messages.length - 1];
const stream = await chain.stream({ question: lastMessage.content });
// Convert LangChain stream to Vercel AI SDK ReadableStream
const readableStream = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
if (chunk && typeof chunk === "string") {
controller.enqueue(new TextEncoder().encode(chunk));
}
}
controller.close();
},
});
return new StreamingTextResponse(readableStream);
}
"use client";
import { useChat } from "ai/react";
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } =
useChat({ api: "/api/chat" });
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">AI Assistant</h1>
{/* Message list */}
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{messages.map((m) => (
<div
key={m.id}
className={`p-3 rounded-lg ${
m.role === "user"
? "bg-blue-100 ml-8"
: "bg-gray-100 mr-8"
}`}
>
<span className="font-semibold">
{m.role === "user" ? "You" : "AI"}:
</span>{" "}
{m.content}
</div>
))}
{isLoading && (
<div className="bg-gray-100 p-3 rounded-lg mr-8 animate-pulse">
Thinking...
</div>
)}
</div>
{/* Input form */}
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Ask me anything..."
className="flex-1 border rounded-lg p-2"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading}
className="bg-blue-500 text-white px-4 py-2 rounded-lg disabled:opacity-50"
>
Send
</button>
</form>
</div>
);
}
useChat hook manages message state, loading state, and streaming automatically. Tokens appear in the UI as they arrive — no manual SSE parsing required.
6. HTMX + FastAPI Streaming Chat
HTMX enables server-side streaming into existing HTML pages without a JavaScript framework — ideal for teams with server-rendering skills.
from fastapi import FastAPI, Form
from fastapi.responses import HTMLResponse, StreamingResponse
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
app = FastAPI()
llm = ChatOpenAI(model="gpt-4o-mini", streaming=True)
chain = (
ChatPromptTemplate.from_messages([("human", "{q}")])
| llm
| StrOutputParser()
)
@app.get("/", response_class=HTMLResponse)
def index():
return """
<html>
<head>
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/sse.js"></script>
</head>
<body>
<form hx-post="/ask" hx-target="#response" hx-swap="innerHTML">
<input name="q" placeholder="Ask a question..." />
<button type="submit">Ask</button>
</form>
<div id="response"></div>
</body>
</html>
"""
async def sse_generator(question: str):
async for token in chain.astream({"q": question}):
if token:
# HTMX SSE swap appends each token to the target div
yield f"event: message\ndata: <span>{token}</span>\n\n"
yield "event: message\ndata: <hr/>\n\n"
@app.post("/ask")
async def ask(q: str = Form(...)):
# Return SSE stream that HTMX sse-swap extension processes
return StreamingResponse(
sse_generator(q),
media_type="text/event-stream",
)
htmx.org/ext/sse.js extension lets HTMX swap HTML fragments streamed via SSE — each data: event replaces or appends to the target element, enabling progressive rendering without JavaScript.
7. Open WebUI — Self-Hosted ChatGPT Interface
Open WebUI is a feature-complete self-hosted web interface compatible with OpenAI-format APIs. Connect your LangServe backend by configuring it as a custom model endpoint.
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
ports:
- "3000:8080"
environment:
OPENAI_API_BASE_URL: http://langserve-app:8000/chat # your LangServe endpoint
OPENAI_API_KEY: dummy-key # required but unused if your API is open
WEBUI_SECRET_KEY: change_me_in_production
volumes:
- open-webui-data:/app/backend/data
langserve-app:
build: ./langserve-app
ports:
- "8000:8000"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
volumes:
open-webui-data:
/v1/chat/completions format. Libraries like litellm or a custom FastAPI wrapper can bridge the two formats.
8. Choosing the Right Stack
Is this an internal tool or demo?
├── YES → Streamlit (fastest to build, Python-only team)
│ Gradio (ML demo, HuggingFace deployment)
└── NO (production app)
├── Need full custom UI / enterprise auth / design system?
│ └── Next.js + Vercel AI SDK + LangServe backend
├── Need chat UI fast with built-in auth and agent step visualisation?
│ └── Chainlit
├── Server-rendered with minimal JS (Django/Flask team)?
│ └── HTMX + FastAPI SSE + LangChain
└── Self-hosted ChatGPT replacement for internal users?
└── Open WebUI + LangServe or Ollama backend
| Team Size | Timeline | Recommended Stack |
|---|---|---|
| Solo developer | Days | Streamlit or Chainlit |
| Small team (2–5) | Weeks | Chainlit + LangServe |
| Product team (5+) | Months | Next.js + Vercel AI SDK + LangServe |
| Enterprise (internal) | Self-service | Open WebUI + LangServe |