A pre-configured, ready-to-use Jakarta EE application that serves as the starting point for building apps with TabForge AI.
It ships with a modern UI (PF Modern Template) and a working AI assistant wired end-to-end — chat and a tool-using agent, streamed live to the browser over REST + Server-Sent Events. Clone it, drop in your AI provider key, add your business logic, and start building.
- PF Modern Template UI — topbar, collapsible sidebar, light/dark/dim themes, command palette
(
Ctrl+K), status bar, and a built-in AI Assistant panel (Chat + live Activity timeline). - DynTabs multi-tab PrimeFaces UI (pre-configured).
- Working AI backend wiring — the AI panel is connected to a real backend:
POST /api/ai/chat— receives the typed message (outbound).GET /api/ai/stream— Server-Sent Events channel that streams the reply and live agent activity back to the panel (inbound).- Powered by EasyAI (
tabforge-ai), with per-session chat memory.
- Demo agent + tools (
DemoToolService) — prefix a message with/agentand the agent calls real tools (getCurrentDateTime,add,getWeather); each call shows up live in the Activity tab. - Jakarta EE 11 structure, CDI, JSF navigation, and a Jakarta Security custom-form login.
easyai.propertiestemplate for AI provider configuration.- Logback for logging.
- Java 21+
- Jakarta EE 11 application server (GlassFish 8, Payara 7, WildFly with EE 11 support)
- PrimeFaces 15+ (bundled via Maven)
-
Clone or download this project.
-
Configure your AI provider in
src/main/resources/easyai.properties(see below). For a quick no-key start, point it at a local Ollama instance. -
Build and deploy:
mvn clean packageDeploy
target/tabforge_ai_starter_2.0.warto your application server. (SeeBUILD_DEPLOY_UNDEPLOY.TXTfor ready-made GlassFishasadmincommands.) -
Log in, open the AI Assistant panel (sparkle icon, top-right), and send a message.
The UI is backend-agnostic — it emits the user's message and renders whatever events the
backend streams back. All the wiring lives in the api/ package and one script block in
template.xhtml; the template itself knows nothing about EasyAI.
Under the hood it uses EasyAI's live observability API (2.1): the chat and agent are built
with a single extra call — .withEventListener(bridge) — and EasyAI then emits an EasyAIEvent
for every moment of the operation. One small adapter, EasyAiActivityBridge, translates those
events into the panel's JSON and pushes them over SSE. The library never imports anything about
SSE or the UI; that mapping lives entirely here in the app.
| Path | What happens |
|---|---|
| Chat (any message) | POST /api/ai/chat → ChatService (EasyAI chat, session memory, bridge attached) → EasyAI emits started → reply → finished → rendered as Activity rows + a chat bubble. |
Agent demo (/agent …) |
EasyAI.agent().withEventListener(bridge) runs over DemoToolService; each tool call surfaces live as a tool_call → tool_result pair in the Activity tab. Example: /agent what is 23 + 19 and what time is it? |
Two channels (outbound POST + inbound SSE) are correlated by HTTP session. Key classes:
api/RestApplication.java @ApplicationPath("/api") — activates JAX-RS
api/AiChatResource.java POST /api/ai/chat → ChatProcessor (returns 202)
api/AiStreamResource.java GET /api/ai/stream → SSE subscription
api/AiEventChannel.java session → SseBroadcaster registry; emits events to a browser
api/EasyAiActivityBridge.java the ONE adapter: EasyAIEvent → AgentEvent (the only EasyAI↔UI glue)
api/AgentEvent.java the JSON event shape sent over SSE (the template's wire format)
api/ChatProcessor.java routes chat vs /agent; attaches the bridge via .withEventListener(..)
api/ChatService.java @SessionScoped EasyAI chat with memory + bridge
api/DemoToolService.java sample tools the agent can call
You do not rewrite any of the api/ classes above — clone this starter and they are already
there. To make your business logic stream into the Activity panel, the whole recipe is three
lines anywhere you build an EasyAI capability (a CDI bean, a service, a JSF backing bean):
@Inject AiEventChannel channel; // already in the starter
String sessionId = request.getSession(true).getId(); // or FacesContext → HttpServletRequest
var bridge = new EasyAiActivityBridge(channel, sessionId); // already in the starter
EasyAgent agent = EasyAI.agent()
.withServices(myOrderService, myPaymentService)
.withEventListener(bridge) // ← the one line you add
.build();
agent.execute(userTask); // every tool call now streams into the Activity panel, liveThe same .withEventListener(bridge) works on EasyAI.chat(), EasyAI.indexer(), and
EasyAI.extract(). Adding live observability to an existing app (not this starter) is just as
easy: copy the five files RestApplication, AiStreamResource, AiEventChannel,
EasyAiActivityBridge, and AgentEvent from api/, then apply the three-line recipe.
Edit src/main/resources/easyai.properties:
easyai.provider=openai
easyai.api-key=sk-YOUR-KEY
easyai.model-name=gpt-4o-miniFor local Ollama (free, no key needed):
easyai.provider=ollama
easyai.model-name=llama3Without a key the wiring still works end-to-end — you'll see a clear "no API key" message in the chat, which confirms the request reached the provider and came back through the full stack.
- Add a tab, AI assistant, RAG, or autonomous agent: see
llms.txtin this repo and the TabForge AI guide. - Customize the UI (logo, menu, themes): see the PF Modern Template installation guide.