feat: overhaul chat architecture with persistent JdbcChatMemory, reactive UI components, and improved conversation management

This commit is contained in:
2026-07-23 19:20:28 +07:00
parent 2bbbff1ec9
commit 506008c69f
79 changed files with 5058 additions and 1193 deletions

View File

@@ -4,150 +4,96 @@ trigger: always_on
# Project Context: Loyalty Agent Service
This repository is a local development workspace for a loyalty AI assistant. It has a React chat UI at the repository root and two Spring Boot backend modules managed by a parent Maven project.
An AI Assistant system designed to manage and operate Loyalty business domain tasks (Campaigns, Rules, Point Pools, Tiers, and Members) for enterprise applications. The repository is structured as a monorepo containing 3 distinct applications communicating via WebSocket STOMP, HTTP REST APIs, and MCP SSE.
## Architecture
---
### Frontend: root directory
## 1. Architecture Overview
- Stack: React 19, Vite, TypeScript, Tailwind CSS, shadcn-style UI primitives, Zustand, `@stomp/stompjs`, `react-markdown`, `remark-gfm`.
- Entry point: `src/main.tsx` renders `src/App.tsx`, which renders `ChatLayout`.
- Dev port: `9333` from `vite.config.ts`.
- Main UI flow:
- `src/store/useChatStore.ts` owns chat state and STOMP connection.
- WebSocket URL is currently hard-coded as `ws://localhost:9332/ws`.
- User messages are published to `/app/chat`.
- Agent events are received from `/user/queue/chat-events`.
- `CampaignList` fetches campaign data from `http://localhost:9332/api/v1/agent/campaigns` and campaign rules from `http://localhost:9332/api/v1/agent/campaigns/{campaignId}/rules`.
```
┌──────────────────────────────────┐
│ Frontend (React 19 + Vite) │
│ Port: 9333 (Dev Server) │
└─────────────────┬────────────────┘
WebSocket │ REST Proxy
(STOMP /ws) │ (/api/v1/agent/...)
┌──────────────────────────────────┐
│ `loyalty-agent` (Spring Boot) │
│ Port: 9332 | BFF & AI Service │
└─────────┬───────────────┬────────┘
│ │
Ollama │ │ MCP SSE
(qwen3.5:4b) │ │ (http://localhost:9331/sse)
▼ ▼
┌──────────────────┐ ┌──────────────────────────────────┐
│ Ollama LLM Server│ │ `loyalty-mcp-server` │
│ 192.168.99.10 │ │ Port: 9331 | MCP Tool Server │
└──────────────────┘ └────────────────┬────────────────┘
│ OAuth2 Client Credentials
┌──────────────────────────────────┐
│ Loyalty Core API (Spring Boot) │
│ 192.168.99.242:8081 │
└──────────────────────────────────┘
```
### `loyalty-agent`
---
- Spring Boot application: `dev.sonpx.loyalty.agent.LoyaltyAgentApplication`.
- Runtime port: `9332`.
- Role: BFF and AI orchestration layer for the frontend.
- Key files:
- `controller/AgentController.java`: STOMP chat endpoint and REST campaign proxy endpoints.
- `service/LoyaltyAgentService.java`: builds a Spring AI `ChatClient`, streams LLM output, wraps tool calls, and emits tool status events.
- `config/WebSocketConfig.java`: STOMP endpoint `/ws`, application prefix `/app`, user destination prefix `/user`, broker prefixes `/topic` and `/queue`.
- `config/AgentConfig.java`: OAuth2-enabled `RestClient` for the loyalty core API.
- `controller/ConversationController.java`: simple in-memory conversation/message REST endpoints.
- AI configuration:
- Uses Ollama at `http://192.168.99.10:11434`.
- Default model in `application.yml`: `qwen3.5:4b`.
- MCP client connects to `${MCP_SERVER_URL:http://localhost:9331/sse}`.
- Agent event types sent to the UI: `TOKEN`, `TOOL_STATUS`, `DONE`, `ERROR`.
## 2. Applications & Dedicated Context Files
### `loyalty-mcp-server`
Each application has a dedicated context file containing detailed architecture documentation, message contracts, and development guidelines:
- Spring Boot application: `dev.sonpx.loyalty.mcp.McpServerApplication`.
- Runtime port: `9331`.
- Role: MCP tool server for loyalty operations. The agent calls this service through Spring AI MCP SSE.
- MCP endpoints from `application.yml`:
- SSE endpoint: `/sse`
- Message endpoint: `/message`
- Key files:
- `config/McpServerConfig.java`: registers MCP tool objects.
- `tool/LoyaltyAgentTools.java`: member tiers, point pools, campaign creation, static attributes.
- `tool/reward/CampaignTools.java`: campaign listing and campaign draft lifecycle.
- `tool/reward/CampaignRuleTools.java`: campaign rule listing, creation, editing, and draft rule attachment.
- `tool/pool/PointPoolTools.java`: pool and SOP draft tools.
- `draft/DraftSessionManager.java`: in-memory draft sessions keyed by `conversationId`, with a 1-hour TTL.
- `draft/DependencyValidator.java`: validates draft dependencies before tools mutate or submit drafts.
- API clients call the loyalty core service with OAuth2 client credentials.
1. **`loyalty-agent` (BFF & AI Orchestrator)**:
- **Port**: `9332`
- **Role**: Manages Ollama LLM interactions, STOMP WebSocket streaming to the UI, MCP Client execution, and conversation history.
- **Detailed Specs**: [context-agent.md](./context-agent.md) (`@context-agent.md`)
## External Services And Environment
2. **`loyalty-mcp-server` (MCP Tool Server)**:
- **Port**: `9331`
- **Role**: Exposes `@McpTool` endpoints for loyalty business domain operations (Campaigns, Rules, Pools), interacting with Loyalty Core API via OAuth2.
- **Detailed Specs**: [context-mcp-server.md](./context-mcp-server.md) (`@context-mcp-server.md`)
Create `.env` from `.env.example` for local development.
3. **Frontend (React Chat UI)**:
- **Port**: `9333`
- **Role**: User interface for the AI Chatbot, handling STOMP stream events, rendering Markdown, displaying animated tool status badges, and rendering side panels.
- **Detailed Specs**: [context-fe.md](./context-fe.md) (`@context-fe.md`)
Important variables:
---
- `KEYCLOAK_CLIENT_SECRET`
- `KEYCLOAK_TOKEN_URI`
- `LOYALTY_CORE_BASE_URL`
- `MCP_SERVER_URL`
- `VITE_AGENT_HTTP_URL`
- `VITE_AGENT_WS_URL`
Current defaults:
- Keycloak token URI: `http://192.168.99.235/auth/realms/ols-cn-sit/protocol/openid-connect/token`
- Loyalty core base URL: `http://192.168.99.242:8081`
- MCP server URL: `http://localhost:9331`
- Agent HTTP URL: `http://localhost:9332`
- Agent WS URL: `ws://localhost:9332`
Note: some frontend code still uses hard-coded localhost URLs instead of the `VITE_*` variables.
## Local Startup
Use the root script for the full local stack:
## 3. Quick Startup & Commands
### Launch Entire Stack (Root Script)
```bash
./start-all.sh
```
*Automatically starts MCP Server (`9331`), Agent Service (`9332`), and Frontend (`9333`).*
What the script does:
1. Loads `.env` if present.
2. Kills processes on ports `9331`, `9332`, and `9333`.
3. Starts `loyalty-mcp-server` and waits for port `9331`.
4. Starts a `nodemon` compile watcher for the MCP server.
5. Starts `loyalty-agent` and waits for port `9332`.
6. Starts a `nodemon` compile watcher for the agent.
7. Starts the frontend with `pnpm dev` on port `9333`.
8. Writes logs to `log/`.
Manual startup:
### Manual Application Startup
```bash
# Terminal 1: MCP Server
./mvnw spring-boot:run -pl loyalty-mcp-server
# Terminal 2: Agent Service
./mvnw spring-boot:run -pl loyalty-agent
# Terminal 3: Frontend
pnpm dev
```
The script currently invokes `-Pgen`, but the checked-in POM files do not define a `gen` Maven profile. Do not assume OpenAPI generation is wired unless you verify or add that profile.
### Build & Test Commands
- **Frontend**: `pnpm install` | `pnpm lint` | `pnpm build`
- **Backend (Spring Boot)**: `./mvnw test` (Run all) | `./mvnw test -pl loyalty-agent` | `./mvnw test -pl loyalty-mcp-server`
## Build, Lint, And Tests
---
Frontend:
## 4. Ports & Environment Variables Reference
```bash
pnpm install
pnpm lint
pnpm build
```
Backend:
```bash
./mvnw test
./mvnw test -pl loyalty-agent
./mvnw test -pl loyalty-mcp-server
```
Relevant tests:
- `loyalty-agent/src/test/java/dev/sonpx/loyalty/agent/controller/AgentControllerTest.java`
- `loyalty-agent/src/test/java/dev/sonpx/loyalty/agent/domain/AgentEventTest.java`
- `loyalty-mcp-server/src/test/java/dev/sonpx/loyalty/mcp/tool/McpOrchestratorTest.java`
- `loyalty-mcp-server/src/test/java/dev/sonpx/loyalty/mcp/tool/reward/CampaignRuleMapperTest.java`
- `loyalty-mcp-server/src/test/java/dev/sonpx/loyalty/mcp/tool/LoyaltyAgentToolsRealApiTest.java`
`LoyaltyAgentToolsRealApiTest` may depend on real external API availability and credentials.
## Development Guidelines For Agents
- Keep the boundary clear:
- `loyalty-agent` owns frontend-facing chat orchestration and REST proxy endpoints.
- `loyalty-mcp-server` owns tool implementations and loyalty core API access.
- The frontend owns STOMP state, message rendering, chat layout, and campaign context panels.
- Preserve the STOMP contract unless intentionally changing both sides:
- Client publishes to `/app/chat`.
- Server handles `@MessageMapping("/chat")`.
- Server sends events to `/user/queue/chat-events`.
- Preserve `conversationId` propagation. MCP draft tools depend on it to keep draft campaign/pool/rule state in `DraftSessionManager`.
- Tool responses may contain `presentation.content`; the agent system prompt requires displaying that content exactly.
- Reuse existing UI primitives in `src/components/ui` and existing Tailwind conventions.
- Do not introduce persistent storage assumptions. Conversation repositories and draft sessions are currently in memory.
- Do not hard-code new service URLs when a matching environment variable already exists.
- Be careful with ports: MCP is `9331`, agent is `9332`, frontend is `9333`.
| Component | Port | Default Endpoint | Key Environment Variables |
| :--- | :--- | :--- | :--- |
| **Frontend UI** | `9333` | `http://localhost:9333` | `VITE_AGENT_HTTP_URL`, `VITE_AGENT_WS_URL` |
| **Loyalty Agent** | `9332` | `http://localhost:9332`, `ws://localhost:9332/ws` | `MCP_SERVER_URL`, `LOYALTY_CORE_BASE_URL` |
| **Loyalty MCP Server** | `9331` | `http://localhost:9331/sse` | `KEYCLOAK_TOKEN_URI`, `KEYCLOAK_CLIENT_SECRET` |
| **Ollama LLM** | `11434` | `http://192.168.99.10:11434` | Defined in `loyalty-agent/application.yml` |
| **Loyalty Core API** | `8081` | `http://192.168.99.242:8081` | `LOYALTY_CORE_BASE_URL` |