Repository files navigation

Next Chats

中文版 README(简体中文)

Chat window

Chat window (conversation / thinking / tools / topic rail)


Admin console

Admin console (LLM providers / MCP / RBAC / internal auth / approvals / audit / usage)

A B/S multimodal AI chat platform based on the DeepSeek Harness principles: LLM + MCP (Model Context Protocol) + Skill plugin orchestration, with a Cordis-driven, Three.js-powered 3D frontend.

Reference paper: A Programming Paradigm for Spatiotemporal Composability — everything is a plugin, driven by Cordis.

Multilingual UI: English (default) and 简体中文 — switch from the top bar (🌐 EN / 中文); your choice is remembered in localStorage.


Architecture & Flows

Data Model (ER)

erDiagram
APP_USER {
Guid id PK
string auth_type "default=password account; acs/ucs…=internal auth"
string username "unique together with auth_type"
string display_name
string email
string password_hash
string password_salt
string status
datetime created_at
datetime last_login_at
}
APP_ROLE {
Guid id PK
string name
string code UK
string description
bool is_system
datetime created_at
}
CHAT_SESSION {
Guid id PK
Guid user_id FK
string title
string status
Guid llm_provider_id
string context_json
datetime created_at
datetime updated_at
datetime last_message_at
}
CHAT_MESSAGE {
Guid id PK
Guid session_id FK
Guid user_id FK
string role
text content
text reasoning
string tool_calls_json
string status
string model
int prompt_tokens
int completion_tokens
string trace_id
string client_message_id UK
datetime created_at
}
LLM_PROVIDER {
Guid id PK
string name
string kind
string base_url
string api_key_encrypted
int priority
bool enabled
bool is_healthy
datetime created_at
datetime updated_at
}
LLM_MODEL {
Guid id PK
Guid provider_id FK
string name
bool enabled
bool is_vision
int context_window
decimal price_in_per_1k
decimal price_out_per_1k
int priority
datetime created_at
}
INTERNAL_AUTH_PROVIDER {
Guid id PK
string name "acs / ucs…, unique"
string api "auth center URL"
string http_method
string request_format "body(application/json)"
string username_field
string password_field
bool enabled
int timeout_seconds
datetime created_at
}
INTERNAL_AUTH_SUCCESS_RULE {
Guid id PK
Guid provider_id FK
string field "JSON path, e.g. sessionID"
string operator "NotEmpty / Equals"
string expected_value
}
MCP_SERVER {
Guid id PK
string name
string transport
string endpoint
string headers_json
string stdio_command
bool enabled
bool is_vision
int timeout_seconds
string description
datetime created_at
}
MCP_CATALOG_ITEM {
Guid id PK
Guid mcp_server_id FK
string kind
string name
string description
string schema_json
bool enabled
}
PROMPT {
Guid id PK
string name
string description
text content
bool enabled
int version
datetime created_at
}
SKILL {
Guid id PK
string name
string meta_tool_name
text instruction
bool enabled
string model_override
int max_nested_steps
}
TOOL_APPROVAL {
Guid id PK
string trace_id
Guid user_id FK
Guid session_id FK
string mcp_server_name
string tool_name
string arguments_json
string status
datetime created_at
datetime decided_at
datetime expires_at
}
AUDIT_LOG {
Guid id PK
string trace_id
Guid user_id FK
string category
string action
string target
string detail_json
string ip
bool is_suspicious
datetime created_at
}
USER_SETTING {
Guid id PK
Guid user_id FK
string key
string value_json
}
TOKEN_USAGE_RECORD {
Guid id PK
string trace_id UK
Guid user_id FK
Guid session_id FK
string provider_name
string model
int total_tokens
decimal cost
int ttft_ms
datetime created_at
}
APP_USER ||--o{ CHAT_SESSION : "owns"
APP_USER ||--o{ CHAT_MESSAGE : "sends"
APP_USER }o--o{ APP_ROLE : "member_of (user_roles)"
APP_USER ||--o{ USER_SETTING : "has"
APP_ROLE }o--o{ MCP_SERVER : "can_use (role_mcp_servers)"
APP_ROLE }o--o{ PROMPT : "can_use (role_prompts)"
APP_ROLE }o--o{ SKILL : "can_use (role_skills)"
APP_ROLE }o--o{ LLM_MODEL : "can_use (role_model_bindings)"
APP_ROLE }o--o{ INTERNAL_AUTH_PROVIDER : "default roles (internal_auth_provider_roles)"
LLM_PROVIDER ||--o{ LLM_MODEL : "models"
INTERNAL_AUTH_PROVIDER ||--o{ INTERNAL_AUTH_SUCCESS_RULE : "success rules"
MCP_SERVER ||--o{ MCP_CATALOG_ITEM : "catalog"
CHAT_SESSION ||--o{ CHAT_MESSAGE : "contains"
CHAT_SESSION }o--o| LLM_PROVIDER : "uses"
TOOL_APPROVAL }o--|| APP_USER : "requested_by"
TOOL_APPROVAL }o--|| CHAT_SESSION : "in"
AUDIT_LOG }o--o| APP_USER : "actor"
TOKEN_USAGE_RECORD }o--o| APP_USER : "accrued_by"
Loading

System Architecture

flowchart LR
subgraph Web["Web (Vue 3 + Cordis)"]
UI[Chat UI / 3D Theme] --> K[Kernel Plugins]
K -->|SSE / REST| HTTP[Vite proxy → /api]
end
subgraph Api["NextChats.Api (ASP.NET Core)"]
Ctl[Controllers / RBAC / Audit] --> Mod[Error Localization Middleware]
Ctl --> Orch{{ChatOrchestrator}}
Ctl --> Adm[Admin: Providers / MCP / Prompts / Skills / RBAC / Internal Auth / Approvals]
Ctl --> Auth[Auth: default password / internal auth]
Auth -->|HTTP| IA[(Internal Auth Center acs / ucs…)]
end
Orch --> Loop{{AgentLoopEngine}}
Loop -->|ReAct think→act→observe| Router[LLM Router]
Router --> OpenAI[OpenAI-Compatible Client]
Router --> Mock[Mock Client: localized output]
Orch --> Driver[MCP Driver]
Driver --> MCP1[(MCP Server: Demo 5300)]
Driver --> MCP2[(MCP Server: more…)]
Orch --> Skills[Skill Execution Engine]
Orch --> Policy[Policy Engine / Approval Coordinator]
Api --> DB[(SQLite: config / chat / audit)]
UI --> V3[Three.js scenes]
style Orch fill:#e3f2fd
style Loop fill:#fff3e0
Loading

Chat (ReAct) Flow — SSE streaming

sequenceDiagram
participant U as User (Browser)
participant K as Cordis Kernel
participant C as ChatController
participant O as ChatOrchestrator
participant L as AgentLoopEngine
participant LLM as LLM Client
participant M as MCP Driver / Skills
U->>K: type message (+ optional images)
K->>C: POST /api/chat/stream (SSE)
C->>O: ChatStreamRequest (lang, images)
O-->>C: SESSION_NOT_FOUND? / INPUT_FLAGGED? (error event)
O->>L: AgentRunRequest (tools, context window)
loop ReAct round
L->>LLM: stream prompt + tools
LLM-->>L: reasoning / text / tool calls chunks
L-->>U: thinking_start · thinking_delta · text_delta (SSE)
alt tool call
L->>Policy: evaluate (allow/deny/approval)
Policy-->>U: approval_updated (pending) → user approves
L->>M: execute tool (approval / args)
M-->>L: tool result
L-->>U: tool_start · tool_result (SSE)
end
end
L-->>U: done (usage: tokens / ttft / cost)
O->>DB: persist assistant message + usage + audit (trace_id)
O-->>C: SSE end event
C-->>K: event stream → UI renders
Loading

Image / Vision Flow (image_source standard base64)

sequenceDiagram
participant U as User (Browser)
participant B as ChatInputBar
participant C as ChatController
participant O as ChatOrchestrator
participant V as Vision MCP Tool (image_source)
participant LLM as LLM (Mock / real)
participant P as Provider · MCP Vision Flags
U->>C: GET /api/chat/vision-config
C->>P: provider.IsVision ∨ bound MCP.IsVision
P-->>U: supported: true → show upload button + Ctrl+V
U->>B: paste (Ctrl+V) or upload N images
B->>B: validate type/size → dataURL → base64
B->>C: POST /api/chat/stream { message, images[] }
C->>O: ChatStreamRequest.Images (standard base64)
loop each image (multi-image recognized one by one)
O->>V: call vision tool { image_source: base64 }
V-->>O: recognition text (per image)
end
O->>LLM: system + history + [vision texts] + user(message + <image_source> blocks)
LLM-->>U: streamed answer (SSE) using the recognized text
Loading

Tech Stack

LayerTechnology
Backend.NET 10 · ASP.NET Core · EF Core (SQLite now → MySQL 8 ready)
MCPModelContextProtocol SDK 2.2.0 (latest stable) · Streamable HTTP transport · STDIO extensible
FrontendVue 3 · TypeScript · Vite · Element Plus · Three.js · Cordis ^3.18.1 (plugin kernel)
SecurityJWT dual-token (30-min access + rotating refresh, revoked on disable/logout) · PBKDF2-SHA256(210k) password hashing · AES-256-GCM secret encryption · sanitized audit logs
i18nvue-i18n 10 (en / zh-CN, English default) · X-Lang error localization on the API

Repository Layout

next-chats/
├── src/
│ ├── NextChats.Core/ # Domain model, orchestration, engines, drivers (no ASP.NET dependency)
│ ├── NextChats.Infrastructure/ # EF Core data, caching, security services (swappable)
│ └── NextChats.Api/ # Web API (SSE streaming, admin, RBAC, audit, metrics, i18n)
├── samples/McpDemoServer/ # Demo MCP Server (Streamable HTTP)
├── e2e/ # Internal-auth E2E (mock auth center + PowerShell suite)
└── web/ # Vue 3 + Cordis frontend
├── src/i18n/ # en / zh dictionaries (per-domain locale files)
└── scripts/smoke*.mjs # End-to-end smoke scripts (Node fetch streaming)

Quick Start

# 1. Start the demo MCP Server (port 5300)
dotnet run --project samples/McpDemoServer -c Debug
# 2. Start the API (port 5210; DB is auto-created and seeded on first boot)
dotnet run --project src/NextChats.Api -c Debug
# 3. Start the frontend (Vite proxies /api → 5210)cd web && npm install && npm run dev
# Open http://localhost:5173 (seeded admin: admin / admin123)

Core Design

Authentication (default accounts + internal auth)

  • default(system accounts): local password accounts (PBKDF2-SHA256), created by an admin in User Management; the login page defaults to default.
  • Internal auth (acs / ucs…): admins maintain auth-center configs in Internal Auth Management — API URL, HTTP method, the request-body field names for the username/password (body(application/json)), success-response rules (one or more fields that must be not empty or equal a fixed value, dotted paths such as data.sessionID supported), and the category's default roles (multi-select).
  • The login page shows a sign-in-method radio group (default + enabled auth centers, from GET /api/auth/providers); choosing an internal method makes the backend validate the credentials against the configured auth center.
  • After a successful validation the user is auto-provisioned or reused: internal users are unique by (AuthType, Username) (a default and an acs account may share a username); when the user already exists their roles are read directly, otherwise a user is created (no password, displayName = username, status Active, bound to the provider's default roles).
  • Internal-auth users share the same JWT mechanism as default accounts: chat / catalog / permissions behave identically afterwards (role → model/MCP/Prompt/Skill bindings decide the visible scope).
  • Dual-token session (double-token): login issues a short-lived access token (30 min) plus a persisted refresh token (7 days, stored as SHA-256 hash). When the access token expires, the frontend silently exchanges the refresh token (POST /api/auth/refresh) and replays the request — active users are never logged out mid-work, idle sessions expire after 7 days. Every refresh rotates the token (the old one is revoked and recorded as replaced, so replays are rejected). Re-login, logout, and user disable revoke that user's refresh tokens immediately (a disabled account can no longer renew, and its access token dies within 30 min).
sequenceDiagram
participant U as User (Browser)
participant C as AuthController
participant P as InternalAuthProvider config
participant A as Auth Center (external)
participant DB as Users table
U->>C: POST /api/auth/login { authType: "acs", username, password }
C->>P: load provider (api / fields / success rules / default roles)
C->>A: HTTP POST api { username_field: …, password_field: … }
A-->>C: response JSON
C->>C: evaluate success rules (field not empty / equals value)
alt rule satisfied
C->>DB: find user by (AuthType=acs, Username)
alt not exists
C->>DB: auto-create (no password, default roles)
end
C-->>U: JWT (same mechanism as default accounts) + user info
else rule failed / auth center error
C-->>U: 401 (AUTH_INVALID_CREDENTIALS / AUTH_PROVIDER_ERROR)
end
Loading

Orchestration (Core)

  • Effective tool intersection: role binding ∩ user selection ∩ MCP global enabled per user, with data isolation.
  • Active Skill matching: Skills are exposed to the model as meta tools (skill_<slug>); instructions are lazy-loaded to avoid token explosion.
  • Prompt building: a template engine ({{var}} / #if / #each / #section) renders system prompts.
  • ReAct loop (AgentLoopEngine): producer–consumer Channel architecture decouples LLM streaming from the main loop; safe interruption.

Server-side Unified Configuration (Admin)

  • LLM providers (enable flag / model / endpoint / encrypted api-key / temperature / context-window)
  • MCP servers (Name / Endpoint / Headers JSON encrypted; "Fetch" auto-discovers tools/prompts/resources with schemas; per-item disable)
  • Multiple Prompts & Skills (pluggable, lazy-load)
  • RBAC: users / roles; role ↔ MCP / Prompt / Skill / LLM model bindings —— bound models are the only models the role's users can see/select, and direct API calls with an unauthorized model id are rejected server-side (MODEL_NOT_AUTHORIZED)
  • Internal Auth: auth-center configs (acs / ucs…) with success-response rules and default roles (see Authentication above)

Engines (Core)

  • LLM Router: multi-vendor priority + round-robin + failover (mark-unhealthy circuit breaking); OpenAI-compatible & Mock clients.
  • MCP driver: strictly follows the latest MCP spec (see MCP 2026-07 revision); connection pooling; call retry with backoff. MCP errors go into the loop — tool errors are fed back to the model as tool results for retry/explanation; sessions never break because of an MCP error.
  • Policy engine: Allow → Deny → RequireApproval with dangerous-operation heuristics (delete_all etc.); approvals support pending/approved/rejected/expired.
  • Context manager: token estimate → compress (LLM summary) → truncate; never exceeds the model length limit and never interrupts the session.

Observability & Cost

  • trace_id (trc_...) flows through Orchestration / LLM / MCP / Audit; token accounting in/out; metrics: TTFT, tokens, cost, tool latency, approval count (/api/admin/metrics/usage).
  • Idempotent writes: unique (UserId, ClientMessageId) key; key chat logs persist across restarts.

Logging, three channels

  • To the user: friendly message + error code (no stack / endpoint / header)
  • To the model: tool errors are re-fed as tool results (retryable, explainable)
  • To the logs: full context (sanitized)

Internationalization

  • Frontend: vue-i18n with per-domain locale files under web/src/i18n/locales/{en,zh}/; English is the default locale.
  • Backend: every user-visible message lives in one dictionary (src/NextChats.Core/Localization/Texts.cs, en/zh) — no hard-coded UI text in code. The API localizes errors via the X-Lang request header (or Accept-Language), so non-browser clients get the same language, e.g. curl -H "X-Lang: en"; seeded content (roles, prompts, skills, demo provider) is English.
  • README: this English file and README.zh.md are linked at the top.

Smoke Verification (web/scripts/ + e2e/)

node scripts/smoke.mjs # login → create session → SSE streaming chat → persistence → metrics
node scripts/smoke-tools.mjs # ReAct tool call (tool:echo) → dangerous approval flow (danger:delete_all)# Internal-auth E2E (mock auth center + PowerShell suite, see e2e/README.md)
node e2e/mock-auth.js # start mock auth center (127.0.0.1:53131)
powershell -File e2e/run-internal-auth-e2e.ps1 # assert CRUD / providers / auto-provision / uniqueness / role-based catalog

MCP References

About

What's not here is all in next ...

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Next Chats

中文版 README(简体中文)

Chat window

Chat window (conversation / thinking / tools / topic rail)


Admin console

Admin console (LLM providers / MCP / RBAC / internal auth / approvals / audit / usage)

A B/S multimodal AI chat platform based on the DeepSeek Harness principles: LLM + MCP (Model Context Protocol) + Skill plugin orchestration, with a Cordis-driven, Three.js-powered 3D frontend.

Reference paper: A Programming Paradigm for Spatiotemporal Composability — everything is a plugin, driven by Cordis.

Multilingual UI: English (default) and 简体中文 — switch from the top bar (🌐 EN / 中文); your choice is remembered in localStorage.


Architecture & Flows

Data Model (ER)

erDiagram
APP_USER {
Guid id PK
string auth_type "default=password account; acs/ucs…=internal auth"
string username "unique together with auth_type"
string display_name
string email
string password_hash
string password_salt
string status
datetime created_at
datetime last_login_at
}
APP_ROLE {
Guid id PK
string name
string code UK
string description
bool is_system
datetime created_at
}
CHAT_SESSION {
Guid id PK
Guid user_id FK
string title
string status
Guid llm_provider_id
string context_json
datetime created_at
datetime updated_at
datetime last_message_at
}
CHAT_MESSAGE {
Guid id PK
Guid session_id FK
Guid user_id FK
string role
text content
text reasoning
string tool_calls_json
string status
string model
int prompt_tokens
int completion_tokens
string trace_id
string client_message_id UK
datetime created_at
}
LLM_PROVIDER {
Guid id PK
string name
string kind
string base_url
string api_key_encrypted
int priority
bool enabled
bool is_healthy
datetime created_at
datetime updated_at
}
LLM_MODEL {
Guid id PK
Guid provider_id FK
string name
bool enabled
bool is_vision
int context_window
decimal price_in_per_1k
decimal price_out_per_1k
int priority
datetime created_at
}
INTERNAL_AUTH_PROVIDER {
Guid id PK
string name "acs / ucs…, unique"
string api "auth center URL"
string http_method
string request_format "body(application/json)"
string username_field
string password_field
bool enabled
int timeout_seconds
datetime created_at
}
INTERNAL_AUTH_SUCCESS_RULE {
Guid id PK
Guid provider_id FK
string field "JSON path, e.g. sessionID"
string operator "NotEmpty / Equals"
string expected_value
}
MCP_SERVER {
Guid id PK
string name
string transport
string endpoint
string headers_json
string stdio_command
bool enabled
bool is_vision
int timeout_seconds
string description
datetime created_at
}
MCP_CATALOG_ITEM {
Guid id PK
Guid mcp_server_id FK
string kind
string name
string description
string schema_json
bool enabled
}
PROMPT {
Guid id PK
string name
string description
text content
bool enabled
int version
datetime created_at
}
SKILL {
Guid id PK
string name
string meta_tool_name
text instruction
bool enabled
string model_override
int max_nested_steps
}
TOOL_APPROVAL {
Guid id PK
string trace_id
Guid user_id FK
Guid session_id FK
string mcp_server_name
string tool_name
string arguments_json
string status
datetime created_at
datetime decided_at
datetime expires_at
}
AUDIT_LOG {
Guid id PK
string trace_id
Guid user_id FK
string category
string action
string target
string detail_json
string ip
bool is_suspicious
datetime created_at
}
USER_SETTING {
Guid id PK
Guid user_id FK
string key
string value_json
}
TOKEN_USAGE_RECORD {
Guid id PK
string trace_id UK
Guid user_id FK
Guid session_id FK
string provider_name
string model
int total_tokens
decimal cost
int ttft_ms
datetime created_at
}
APP_USER ||--o{ CHAT_SESSION : "owns"
APP_USER ||--o{ CHAT_MESSAGE : "sends"
APP_USER }o--o{ APP_ROLE : "member_of (user_roles)"
APP_USER ||--o{ USER_SETTING : "has"
APP_ROLE }o--o{ MCP_SERVER : "can_use (role_mcp_servers)"
APP_ROLE }o--o{ PROMPT : "can_use (role_prompts)"
APP_ROLE }o--o{ SKILL : "can_use (role_skills)"
APP_ROLE }o--o{ LLM_MODEL : "can_use (role_model_bindings)"
APP_ROLE }o--o{ INTERNAL_AUTH_PROVIDER : "default roles (internal_auth_provider_roles)"
LLM_PROVIDER ||--o{ LLM_MODEL : "models"
INTERNAL_AUTH_PROVIDER ||--o{ INTERNAL_AUTH_SUCCESS_RULE : "success rules"
MCP_SERVER ||--o{ MCP_CATALOG_ITEM : "catalog"
CHAT_SESSION ||--o{ CHAT_MESSAGE : "contains"
CHAT_SESSION }o--o| LLM_PROVIDER : "uses"
TOOL_APPROVAL }o--|| APP_USER : "requested_by"
TOOL_APPROVAL }o--|| CHAT_SESSION : "in"
AUDIT_LOG }o--o| APP_USER : "actor"
TOKEN_USAGE_RECORD }o--o| APP_USER : "accrued_by"
Loading

System Architecture

flowchart LR
subgraph Web["Web (Vue 3 + Cordis)"]
UI[Chat UI / 3D Theme] --> K[Kernel Plugins]
K -->|SSE / REST| HTTP[Vite proxy → /api]
end
subgraph Api["NextChats.Api (ASP.NET Core)"]
Ctl[Controllers / RBAC / Audit] --> Mod[Error Localization Middleware]
Ctl --> Orch{{ChatOrchestrator}}
Ctl --> Adm[Admin: Providers / MCP / Prompts / Skills / RBAC / Internal Auth / Approvals]
Ctl --> Auth[Auth: default password / internal auth]
Auth -->|HTTP| IA[(Internal Auth Center acs / ucs…)]
end
Orch --> Loop{{AgentLoopEngine}}
Loop -->|ReAct think→act→observe| Router[LLM Router]
Router --> OpenAI[OpenAI-Compatible Client]
Router --> Mock[Mock Client: localized output]
Orch --> Driver[MCP Driver]
Driver --> MCP1[(MCP Server: Demo 5300)]
Driver --> MCP2[(MCP Server: more…)]
Orch --> Skills[Skill Execution Engine]
Orch --> Policy[Policy Engine / Approval Coordinator]
Api --> DB[(SQLite: config / chat / audit)]
UI --> V3[Three.js scenes]
style Orch fill:#e3f2fd
style Loop fill:#fff3e0
Loading

Chat (ReAct) Flow — SSE streaming

sequenceDiagram
participant U as User (Browser)
participant K as Cordis Kernel
participant C as ChatController
participant O as ChatOrchestrator
participant L as AgentLoopEngine
participant LLM as LLM Client
participant M as MCP Driver / Skills
U->>K: type message (+ optional images)
K->>C: POST /api/chat/stream (SSE)
C->>O: ChatStreamRequest (lang, images)
O-->>C: SESSION_NOT_FOUND? / INPUT_FLAGGED? (error event)
O->>L: AgentRunRequest (tools, context window)
loop ReAct round
L->>LLM: stream prompt + tools
LLM-->>L: reasoning / text / tool calls chunks
L-->>U: thinking_start · thinking_delta · text_delta (SSE)
alt tool call
L->>Policy: evaluate (allow/deny/approval)
Policy-->>U: approval_updated (pending) → user approves
L->>M: execute tool (approval / args)
M-->>L: tool result
L-->>U: tool_start · tool_result (SSE)
end
end
L-->>U: done (usage: tokens / ttft / cost)
O->>DB: persist assistant message + usage + audit (trace_id)
O-->>C: SSE end event
C-->>K: event stream → UI renders
Loading

Image / Vision Flow (image_source standard base64)

sequenceDiagram
participant U as User (Browser)
participant B as ChatInputBar
participant C as ChatController
participant O as ChatOrchestrator
participant V as Vision MCP Tool (image_source)
participant LLM as LLM (Mock / real)
participant P as Provider · MCP Vision Flags
U->>C: GET /api/chat/vision-config
C->>P: provider.IsVision ∨ bound MCP.IsVision
P-->>U: supported: true → show upload button + Ctrl+V
U->>B: paste (Ctrl+V) or upload N images
B->>B: validate type/size → dataURL → base64
B->>C: POST /api/chat/stream { message, images[] }
C->>O: ChatStreamRequest.Images (standard base64)
loop each image (multi-image recognized one by one)
O->>V: call vision tool { image_source: base64 }
V-->>O: recognition text (per image)
end
O->>LLM: system + history + [vision texts] + user(message + <image_source> blocks)
LLM-->>U: streamed answer (SSE) using the recognized text
Loading

Tech Stack

LayerTechnology
Backend.NET 10 · ASP.NET Core · EF Core (SQLite now → MySQL 8 ready)
MCPModelContextProtocol SDK 2.2.0 (latest stable) · Streamable HTTP transport · STDIO extensible
FrontendVue 3 · TypeScript · Vite · Element Plus · Three.js · Cordis ^3.18.1 (plugin kernel)
SecurityJWT dual-token (30-min access + rotating refresh, revoked on disable/logout) · PBKDF2-SHA256(210k) password hashing · AES-256-GCM secret encryption · sanitized audit logs
i18nvue-i18n 10 (en / zh-CN, English default) · X-Lang error localization on the API

Repository Layout

next-chats/
├── src/
│ ├── NextChats.Core/ # Domain model, orchestration, engines, drivers (no ASP.NET dependency)
│ ├── NextChats.Infrastructure/ # EF Core data, caching, security services (swappable)
│ └── NextChats.Api/ # Web API (SSE streaming, admin, RBAC, audit, metrics, i18n)
├── samples/McpDemoServer/ # Demo MCP Server (Streamable HTTP)
├── e2e/ # Internal-auth E2E (mock auth center + PowerShell suite)
└── web/ # Vue 3 + Cordis frontend
├── src/i18n/ # en / zh dictionaries (per-domain locale files)
└── scripts/smoke*.mjs # End-to-end smoke scripts (Node fetch streaming)

Quick Start

# 1. Start the demo MCP Server (port 5300)
dotnet run --project samples/McpDemoServer -c Debug
# 2. Start the API (port 5210; DB is auto-created and seeded on first boot)
dotnet run --project src/NextChats.Api -c Debug
# 3. Start the frontend (Vite proxies /api → 5210)cd web && npm install && npm run dev
# Open http://localhost:5173 (seeded admin: admin / admin123)

Core Design

Authentication (default accounts + internal auth)

  • default(system accounts): local password accounts (PBKDF2-SHA256), created by an admin in User Management; the login page defaults to default.
  • Internal auth (acs / ucs…): admins maintain auth-center configs in Internal Auth Management — API URL, HTTP method, the request-body field names for the username/password (body(application/json)), success-response rules (one or more fields that must be not empty or equal a fixed value, dotted paths such as data.sessionID supported), and the category's default roles (multi-select).
  • The login page shows a sign-in-method radio group (default + enabled auth centers, from GET /api/auth/providers); choosing an internal method makes the backend validate the credentials against the configured auth center.
  • After a successful validation the user is auto-provisioned or reused: internal users are unique by (AuthType, Username) (a default and an acs account may share a username); when the user already exists their roles are read directly, otherwise a user is created (no password, displayName = username, status Active, bound to the provider's default roles).
  • Internal-auth users share the same JWT mechanism as default accounts: chat / catalog / permissions behave identically afterwards (role → model/MCP/Prompt/Skill bindings decide the visible scope).
  • Dual-token session (double-token): login issues a short-lived access token (30 min) plus a persisted refresh token (7 days, stored as SHA-256 hash). When the access token expires, the frontend silently exchanges the refresh token (POST /api/auth/refresh) and replays the request — active users are never logged out mid-work, idle sessions expire after 7 days. Every refresh rotates the token (the old one is revoked and recorded as replaced, so replays are rejected). Re-login, logout, and user disable revoke that user's refresh tokens immediately (a disabled account can no longer renew, and its access token dies within 30 min).
sequenceDiagram
participant U as User (Browser)
participant C as AuthController
participant P as InternalAuthProvider config
participant A as Auth Center (external)
participant DB as Users table
U->>C: POST /api/auth/login { authType: "acs", username, password }
C->>P: load provider (api / fields / success rules / default roles)
C->>A: HTTP POST api { username_field: …, password_field: … }
A-->>C: response JSON
C->>C: evaluate success rules (field not empty / equals value)
alt rule satisfied
C->>DB: find user by (AuthType=acs, Username)
alt not exists
C->>DB: auto-create (no password, default roles)
end
C-->>U: JWT (same mechanism as default accounts) + user info
else rule failed / auth center error
C-->>U: 401 (AUTH_INVALID_CREDENTIALS / AUTH_PROVIDER_ERROR)
end
Loading

Orchestration (Core)

  • Effective tool intersection: role binding ∩ user selection ∩ MCP global enabled per user, with data isolation.
  • Active Skill matching: Skills are exposed to the model as meta tools (skill_<slug>); instructions are lazy-loaded to avoid token explosion.
  • Prompt building: a template engine ({{var}} / #if / #each / #section) renders system prompts.
  • ReAct loop (AgentLoopEngine): producer–consumer Channel architecture decouples LLM streaming from the main loop; safe interruption.

Server-side Unified Configuration (Admin)

  • LLM providers (enable flag / model / endpoint / encrypted api-key / temperature / context-window)
  • MCP servers (Name / Endpoint / Headers JSON encrypted; "Fetch" auto-discovers tools/prompts/resources with schemas; per-item disable)
  • Multiple Prompts & Skills (pluggable, lazy-load)
  • RBAC: users / roles; role ↔ MCP / Prompt / Skill / LLM model bindings —— bound models are the only models the role's users can see/select, and direct API calls with an unauthorized model id are rejected server-side (MODEL_NOT_AUTHORIZED)
  • Internal Auth: auth-center configs (acs / ucs…) with success-response rules and default roles (see Authentication above)

Engines (Core)

  • LLM Router: multi-vendor priority + round-robin + failover (mark-unhealthy circuit breaking); OpenAI-compatible & Mock clients.
  • MCP driver: strictly follows the latest MCP spec (see MCP 2026-07 revision); connection pooling; call retry with backoff. MCP errors go into the loop — tool errors are fed back to the model as tool results for retry/explanation; sessions never break because of an MCP error.
  • Policy engine: Allow → Deny → RequireApproval with dangerous-operation heuristics (delete_all etc.); approvals support pending/approved/rejected/expired.
  • Context manager: token estimate → compress (LLM summary) → truncate; never exceeds the model length limit and never interrupts the session.

Observability & Cost

  • trace_id (trc_...) flows through Orchestration / LLM / MCP / Audit; token accounting in/out; metrics: TTFT, tokens, cost, tool latency, approval count (/api/admin/metrics/usage).
  • Idempotent writes: unique (UserId, ClientMessageId) key; key chat logs persist across restarts.

Logging, three channels

  • To the user: friendly message + error code (no stack / endpoint / header)
  • To the model: tool errors are re-fed as tool results (retryable, explainable)
  • To the logs: full context (sanitized)

Internationalization

  • Frontend: vue-i18n with per-domain locale files under web/src/i18n/locales/{en,zh}/; English is the default locale.
  • Backend: every user-visible message lives in one dictionary (src/NextChats.Core/Localization/Texts.cs, en/zh) — no hard-coded UI text in code. The API localizes errors via the X-Lang request header (or Accept-Language), so non-browser clients get the same language, e.g. curl -H "X-Lang: en"; seeded content (roles, prompts, skills, demo provider) is English.
  • README: this English file and README.zh.md are linked at the top.

Smoke Verification (web/scripts/ + e2e/)

node scripts/smoke.mjs # login → create session → SSE streaming chat → persistence → metrics
node scripts/smoke-tools.mjs # ReAct tool call (tool:echo) → dangerous approval flow (danger:delete_all)# Internal-auth E2E (mock auth center + PowerShell suite, see e2e/README.md)
node e2e/mock-auth.js # start mock auth center (127.0.0.1:53131)
powershell -File e2e/run-internal-auth-e2e.ps1 # assert CRUD / providers / auto-provision / uniqueness / role-based catalog

MCP References

About

What's not here is all in next ...

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Next Chats

中文版 README(简体中文)

Chat window

Chat window (conversation / thinking / tools / topic rail)


Admin console

Admin console (LLM providers / MCP / RBAC / internal auth / approvals / audit / usage)

A B/S multimodal AI chat platform based on the DeepSeek Harness principles: LLM + MCP (Model Context Protocol) + Skill plugin orchestration, with a Cordis-driven, Three.js-powered 3D frontend.

Reference paper: A Programming Paradigm for Spatiotemporal Composability — everything is a plugin, driven by Cordis.

Multilingual UI: English (default) and 简体中文 — switch from the top bar (🌐 EN / 中文); your choice is remembered in localStorage.


Architecture & Flows

Data Model (ER)

erDiagram
APP_USER {
Guid id PK
string auth_type "default=password account; acs/ucs…=internal auth"
string username "unique together with auth_type"
string display_name
string email
string password_hash
string password_salt
string status
datetime created_at
datetime last_login_at
}
APP_ROLE {
Guid id PK
string name
string code UK
string description
bool is_system
datetime created_at
}
CHAT_SESSION {
Guid id PK
Guid user_id FK
string title
string status
Guid llm_provider_id
string context_json
datetime created_at
datetime updated_at
datetime last_message_at
}
CHAT_MESSAGE {
Guid id PK
Guid session_id FK
Guid user_id FK
string role
text content
text reasoning
string tool_calls_json
string status
string model
int prompt_tokens
int completion_tokens
string trace_id
string client_message_id UK
datetime created_at
}
LLM_PROVIDER {
Guid id PK
string name
string kind
string base_url
string api_key_encrypted
int priority
bool enabled
bool is_healthy
datetime created_at
datetime updated_at
}
LLM_MODEL {
Guid id PK
Guid provider_id FK
string name
bool enabled
bool is_vision
int context_window
decimal price_in_per_1k
decimal price_out_per_1k
int priority
datetime created_at
}
INTERNAL_AUTH_PROVIDER {
Guid id PK
string name "acs / ucs…, unique"
string api "auth center URL"
string http_method
string request_format "body(application/json)"
string username_field
string password_field
bool enabled
int timeout_seconds
datetime created_at
}
INTERNAL_AUTH_SUCCESS_RULE {
Guid id PK
Guid provider_id FK
string field "JSON path, e.g. sessionID"
string operator "NotEmpty / Equals"
string expected_value
}
MCP_SERVER {
Guid id PK
string name
string transport
string endpoint
string headers_json
string stdio_command
bool enabled
bool is_vision
int timeout_seconds
string description
datetime created_at
}
MCP_CATALOG_ITEM {
Guid id PK
Guid mcp_server_id FK
string kind
string name
string description
string schema_json
bool enabled
}
PROMPT {
Guid id PK
string name
string description
text content
bool enabled
int version
datetime created_at
}
SKILL {
Guid id PK
string name
string meta_tool_name
text instruction
bool enabled
string model_override
int max_nested_steps
}
TOOL_APPROVAL {
Guid id PK
string trace_id
Guid user_id FK
Guid session_id FK
string mcp_server_name
string tool_name
string arguments_json
string status
datetime created_at
datetime decided_at
datetime expires_at
}
AUDIT_LOG {
Guid id PK
string trace_id
Guid user_id FK
string category
string action
string target
string detail_json
string ip
bool is_suspicious
datetime created_at
}
USER_SETTING {
Guid id PK
Guid user_id FK
string key
string value_json
}
TOKEN_USAGE_RECORD {
Guid id PK
string trace_id UK
Guid user_id FK
Guid session_id FK
string provider_name
string model
int total_tokens
decimal cost
int ttft_ms
datetime created_at
}
APP_USER ||--o{ CHAT_SESSION : "owns"
APP_USER ||--o{ CHAT_MESSAGE : "sends"
APP_USER }o--o{ APP_ROLE : "member_of (user_roles)"
APP_USER ||--o{ USER_SETTING : "has"
APP_ROLE }o--o{ MCP_SERVER : "can_use (role_mcp_servers)"
APP_ROLE }o--o{ PROMPT : "can_use (role_prompts)"
APP_ROLE }o--o{ SKILL : "can_use (role_skills)"
APP_ROLE }o--o{ LLM_MODEL : "can_use (role_model_bindings)"
APP_ROLE }o--o{ INTERNAL_AUTH_PROVIDER : "default roles (internal_auth_provider_roles)"
LLM_PROVIDER ||--o{ LLM_MODEL : "models"
INTERNAL_AUTH_PROVIDER ||--o{ INTERNAL_AUTH_SUCCESS_RULE : "success rules"
MCP_SERVER ||--o{ MCP_CATALOG_ITEM : "catalog"
CHAT_SESSION ||--o{ CHAT_MESSAGE : "contains"
CHAT_SESSION }o--o| LLM_PROVIDER : "uses"
TOOL_APPROVAL }o--|| APP_USER : "requested_by"
TOOL_APPROVAL }o--|| CHAT_SESSION : "in"
AUDIT_LOG }o--o| APP_USER : "actor"
TOKEN_USAGE_RECORD }o--o| APP_USER : "accrued_by"
Loading

System Architecture

flowchart LR
subgraph Web["Web (Vue 3 + Cordis)"]
UI[Chat UI / 3D Theme] --> K[Kernel Plugins]
K -->|SSE / REST| HTTP[Vite proxy → /api]
end
subgraph Api["NextChats.Api (ASP.NET Core)"]
Ctl[Controllers / RBAC / Audit] --> Mod[Error Localization Middleware]
Ctl --> Orch{{ChatOrchestrator}}
Ctl --> Adm[Admin: Providers / MCP / Prompts / Skills / RBAC / Internal Auth / Approvals]
Ctl --> Auth[Auth: default password / internal auth]
Auth -->|HTTP| IA[(Internal Auth Center acs / ucs…)]
end
Orch --> Loop{{AgentLoopEngine}}
Loop -->|ReAct think→act→observe| Router[LLM Router]
Router --> OpenAI[OpenAI-Compatible Client]
Router --> Mock[Mock Client: localized output]
Orch --> Driver[MCP Driver]
Driver --> MCP1[(MCP Server: Demo 5300)]
Driver --> MCP2[(MCP Server: more…)]
Orch --> Skills[Skill Execution Engine]
Orch --> Policy[Policy Engine / Approval Coordinator]
Api --> DB[(SQLite: config / chat / audit)]
UI --> V3[Three.js scenes]
style Orch fill:#e3f2fd
style Loop fill:#fff3e0
Loading

Chat (ReAct) Flow — SSE streaming

sequenceDiagram
participant U as User (Browser)
participant K as Cordis Kernel
participant C as ChatController
participant O as ChatOrchestrator
participant L as AgentLoopEngine
participant LLM as LLM Client
participant M as MCP Driver / Skills
U->>K: type message (+ optional images)
K->>C: POST /api/chat/stream (SSE)
C->>O: ChatStreamRequest (lang, images)
O-->>C: SESSION_NOT_FOUND? / INPUT_FLAGGED? (error event)
O->>L: AgentRunRequest (tools, context window)
loop ReAct round
L->>LLM: stream prompt + tools
LLM-->>L: reasoning / text / tool calls chunks
L-->>U: thinking_start · thinking_delta · text_delta (SSE)
alt tool call
L->>Policy: evaluate (allow/deny/approval)
Policy-->>U: approval_updated (pending) → user approves
L->>M: execute tool (approval / args)
M-->>L: tool result
L-->>U: tool_start · tool_result (SSE)
end
end
L-->>U: done (usage: tokens / ttft / cost)
O->>DB: persist assistant message + usage + audit (trace_id)
O-->>C: SSE end event
C-->>K: event stream → UI renders
Loading

Image / Vision Flow (image_source standard base64)

sequenceDiagram
participant U as User (Browser)
participant B as ChatInputBar
participant C as ChatController
participant O as ChatOrchestrator
participant V as Vision MCP Tool (image_source)
participant LLM as LLM (Mock / real)
participant P as Provider · MCP Vision Flags
U->>C: GET /api/chat/vision-config
C->>P: provider.IsVision ∨ bound MCP.IsVision
P-->>U: supported: true → show upload button + Ctrl+V
U->>B: paste (Ctrl+V) or upload N images
B->>B: validate type/size → dataURL → base64
B->>C: POST /api/chat/stream { message, images[] }
C->>O: ChatStreamRequest.Images (standard base64)
loop each image (multi-image recognized one by one)
O->>V: call vision tool { image_source: base64 }
V-->>O: recognition text (per image)
end
O->>LLM: system + history + [vision texts] + user(message + <image_source> blocks)
LLM-->>U: streamed answer (SSE) using the recognized text
Loading

Tech Stack

LayerTechnology
Backend.NET 10 · ASP.NET Core · EF Core (SQLite now → MySQL 8 ready)
MCPModelContextProtocol SDK 2.2.0 (latest stable) · Streamable HTTP transport · STDIO extensible
FrontendVue 3 · TypeScript · Vite · Element Plus · Three.js · Cordis ^3.18.1 (plugin kernel)
SecurityJWT dual-token (30-min access + rotating refresh, revoked on disable/logout) · PBKDF2-SHA256(210k) password hashing · AES-256-GCM secret encryption · sanitized audit logs
i18nvue-i18n 10 (en / zh-CN, English default) · X-Lang error localization on the API

Repository Layout

next-chats/
├── src/
│ ├── NextChats.Core/ # Domain model, orchestration, engines, drivers (no ASP.NET dependency)
│ ├── NextChats.Infrastructure/ # EF Core data, caching, security services (swappable)
│ └── NextChats.Api/ # Web API (SSE streaming, admin, RBAC, audit, metrics, i18n)
├── samples/McpDemoServer/ # Demo MCP Server (Streamable HTTP)
├── e2e/ # Internal-auth E2E (mock auth center + PowerShell suite)
└── web/ # Vue 3 + Cordis frontend
├── src/i18n/ # en / zh dictionaries (per-domain locale files)
└── scripts/smoke*.mjs # End-to-end smoke scripts (Node fetch streaming)

Quick Start

# 1. Start the demo MCP Server (port 5300)
dotnet run --project samples/McpDemoServer -c Debug
# 2. Start the API (port 5210; DB is auto-created and seeded on first boot)
dotnet run --project src/NextChats.Api -c Debug
# 3. Start the frontend (Vite proxies /api → 5210)cd web && npm install && npm run dev
# Open http://localhost:5173 (seeded admin: admin / admin123)

Core Design

Authentication (default accounts + internal auth)

  • default(system accounts): local password accounts (PBKDF2-SHA256), created by an admin in User Management; the login page defaults to default.
  • Internal auth (acs / ucs…): admins maintain auth-center configs in Internal Auth Management — API URL, HTTP method, the request-body field names for the username/password (body(application/json)), success-response rules (one or more fields that must be not empty or equal a fixed value, dotted paths such as data.sessionID supported), and the category's default roles (multi-select).
  • The login page shows a sign-in-method radio group (default + enabled auth centers, from GET /api/auth/providers); choosing an internal method makes the backend validate the credentials against the configured auth center.
  • After a successful validation the user is auto-provisioned or reused: internal users are unique by (AuthType, Username) (a default and an acs account may share a username); when the user already exists their roles are read directly, otherwise a user is created (no password, displayName = username, status Active, bound to the provider's default roles).
  • Internal-auth users share the same JWT mechanism as default accounts: chat / catalog / permissions behave identically afterwards (role → model/MCP/Prompt/Skill bindings decide the visible scope).
  • Dual-token session (double-token): login issues a short-lived access token (30 min) plus a persisted refresh token (7 days, stored as SHA-256 hash). When the access token expires, the frontend silently exchanges the refresh token (POST /api/auth/refresh) and replays the request — active users are never logged out mid-work, idle sessions expire after 7 days. Every refresh rotates the token (the old one is revoked and recorded as replaced, so replays are rejected). Re-login, logout, and user disable revoke that user's refresh tokens immediately (a disabled account can no longer renew, and its access token dies within 30 min).
sequenceDiagram
participant U as User (Browser)
participant C as AuthController
participant P as InternalAuthProvider config
participant A as Auth Center (external)
participant DB as Users table
U->>C: POST /api/auth/login { authType: "acs", username, password }
C->>P: load provider (api / fields / success rules / default roles)
C->>A: HTTP POST api { username_field: …, password_field: … }
A-->>C: response JSON
C->>C: evaluate success rules (field not empty / equals value)
alt rule satisfied
C->>DB: find user by (AuthType=acs, Username)
alt not exists
C->>DB: auto-create (no password, default roles)
end
C-->>U: JWT (same mechanism as default accounts) + user info
else rule failed / auth center error
C-->>U: 401 (AUTH_INVALID_CREDENTIALS / AUTH_PROVIDER_ERROR)
end
Loading

Orchestration (Core)

  • Effective tool intersection: role binding ∩ user selection ∩ MCP global enabled per user, with data isolation.
  • Active Skill matching: Skills are exposed to the model as meta tools (skill_<slug>); instructions are lazy-loaded to avoid token explosion.
  • Prompt building: a template engine ({{var}} / #if / #each / #section) renders system prompts.
  • ReAct loop (AgentLoopEngine): producer–consumer Channel architecture decouples LLM streaming from the main loop; safe interruption.

Server-side Unified Configuration (Admin)

  • LLM providers (enable flag / model / endpoint / encrypted api-key / temperature / context-window)
  • MCP servers (Name / Endpoint / Headers JSON encrypted; "Fetch" auto-discovers tools/prompts/resources with schemas; per-item disable)
  • Multiple Prompts & Skills (pluggable, lazy-load)
  • RBAC: users / roles; role ↔ MCP / Prompt / Skill / LLM model bindings —— bound models are the only models the role's users can see/select, and direct API calls with an unauthorized model id are rejected server-side (MODEL_NOT_AUTHORIZED)
  • Internal Auth: auth-center configs (acs / ucs…) with success-response rules and default roles (see Authentication above)

Engines (Core)

  • LLM Router: multi-vendor priority + round-robin + failover (mark-unhealthy circuit breaking); OpenAI-compatible & Mock clients.
  • MCP driver: strictly follows the latest MCP spec (see MCP 2026-07 revision); connection pooling; call retry with backoff. MCP errors go into the loop — tool errors are fed back to the model as tool results for retry/explanation; sessions never break because of an MCP error.
  • Policy engine: Allow → Deny → RequireApproval with dangerous-operation heuristics (delete_all etc.); approvals support pending/approved/rejected/expired.
  • Context manager: token estimate → compress (LLM summary) → truncate; never exceeds the model length limit and never interrupts the session.

Observability & Cost

  • trace_id (trc_...) flows through Orchestration / LLM / MCP / Audit; token accounting in/out; metrics: TTFT, tokens, cost, tool latency, approval count (/api/admin/metrics/usage).
  • Idempotent writes: unique (UserId, ClientMessageId) key; key chat logs persist across restarts.

Logging, three channels

  • To the user: friendly message + error code (no stack / endpoint / header)
  • To the model: tool errors are re-fed as tool results (retryable, explainable)
  • To the logs: full context (sanitized)

Internationalization

  • Frontend: vue-i18n with per-domain locale files under web/src/i18n/locales/{en,zh}/; English is the default locale.
  • Backend: every user-visible message lives in one dictionary (src/NextChats.Core/Localization/Texts.cs, en/zh) — no hard-coded UI text in code. The API localizes errors via the X-Lang request header (or Accept-Language), so non-browser clients get the same language, e.g. curl -H "X-Lang: en"; seeded content (roles, prompts, skills, demo provider) is English.
  • README: this English file and README.zh.md are linked at the top.

Smoke Verification (web/scripts/ + e2e/)

node scripts/smoke.mjs # login → create session → SSE streaming chat → persistence → metrics
node scripts/smoke-tools.mjs # ReAct tool call (tool:echo) → dangerous approval flow (danger:delete_all)# Internal-auth E2E (mock auth center + PowerShell suite, see e2e/README.md)
node e2e/mock-auth.js # start mock auth center (127.0.0.1:53131)
powershell -File e2e/run-internal-auth-e2e.ps1 # assert CRUD / providers / auto-provision / uniqueness / role-based catalog

MCP References

About

What's not here is all in next ...

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Next Chats

中文版 README(简体中文)

Chat window

Chat window (conversation / thinking / tools / topic rail)


Admin console

Admin console (LLM providers / MCP / RBAC / internal auth / approvals / audit / usage)

A B/S multimodal AI chat platform based on the DeepSeek Harness principles: LLM + MCP (Model Context Protocol) + Skill plugin orchestration, with a Cordis-driven, Three.js-powered 3D frontend.

Reference paper: A Programming Paradigm for Spatiotemporal Composability — everything is a plugin, driven by Cordis.

Multilingual UI: English (default) and 简体中文 — switch from the top bar (🌐 EN / 中文); your choice is remembered in localStorage.


Architecture & Flows

Data Model (ER)

erDiagram
APP_USER {
Guid id PK
string auth_type "default=password account; acs/ucs…=internal auth"
string username "unique together with auth_type"
string display_name
string email
string password_hash
string password_salt
string status
datetime created_at
datetime last_login_at
}
APP_ROLE {
Guid id PK
string name
string code UK
string description
bool is_system
datetime created_at
}
CHAT_SESSION {
Guid id PK
Guid user_id FK
string title
string status
Guid llm_provider_id
string context_json
datetime created_at
datetime updated_at
datetime last_message_at
}
CHAT_MESSAGE {
Guid id PK
Guid session_id FK
Guid user_id FK
string role
text content
text reasoning
string tool_calls_json
string status
string model
int prompt_tokens
int completion_tokens
string trace_id
string client_message_id UK
datetime created_at
}
LLM_PROVIDER {
Guid id PK
string name
string kind
string base_url
string api_key_encrypted
int priority
bool enabled
bool is_healthy
datetime created_at
datetime updated_at
}
LLM_MODEL {
Guid id PK
Guid provider_id FK
string name
bool enabled
bool is_vision
int context_window
decimal price_in_per_1k
decimal price_out_per_1k
int priority
datetime created_at
}
INTERNAL_AUTH_PROVIDER {
Guid id PK
string name "acs / ucs…, unique"
string api "auth center URL"
string http_method
string request_format "body(application/json)"
string username_field
string password_field
bool enabled
int timeout_seconds
datetime created_at
}
INTERNAL_AUTH_SUCCESS_RULE {
Guid id PK
Guid provider_id FK
string field "JSON path, e.g. sessionID"
string operator "NotEmpty / Equals"
string expected_value
}
MCP_SERVER {
Guid id PK
string name
string transport
string endpoint
string headers_json
string stdio_command
bool enabled
bool is_vision
int timeout_seconds
string description
datetime created_at
}
MCP_CATALOG_ITEM {
Guid id PK
Guid mcp_server_id FK
string kind
string name
string description
string schema_json
bool enabled
}
PROMPT {
Guid id PK
string name
string description
text content
bool enabled
int version
datetime created_at
}
SKILL {
Guid id PK
string name
string meta_tool_name
text instruction
bool enabled
string model_override
int max_nested_steps
}
TOOL_APPROVAL {
Guid id PK
string trace_id
Guid user_id FK
Guid session_id FK
string mcp_server_name
string tool_name
string arguments_json
string status
datetime created_at
datetime decided_at
datetime expires_at
}
AUDIT_LOG {
Guid id PK
string trace_id
Guid user_id FK
string category
string action
string target
string detail_json
string ip
bool is_suspicious
datetime created_at
}
USER_SETTING {
Guid id PK
Guid user_id FK
string key
string value_json
}
TOKEN_USAGE_RECORD {
Guid id PK
string trace_id UK
Guid user_id FK
Guid session_id FK
string provider_name
string model
int total_tokens
decimal cost
int ttft_ms
datetime created_at
}
APP_USER ||--o{ CHAT_SESSION : "owns"
APP_USER ||--o{ CHAT_MESSAGE : "sends"
APP_USER }o--o{ APP_ROLE : "member_of (user_roles)"
APP_USER ||--o{ USER_SETTING : "has"
APP_ROLE }o--o{ MCP_SERVER : "can_use (role_mcp_servers)"
APP_ROLE }o--o{ PROMPT : "can_use (role_prompts)"
APP_ROLE }o--o{ SKILL : "can_use (role_skills)"
APP_ROLE }o--o{ LLM_MODEL : "can_use (role_model_bindings)"
APP_ROLE }o--o{ INTERNAL_AUTH_PROVIDER : "default roles (internal_auth_provider_roles)"
LLM_PROVIDER ||--o{ LLM_MODEL : "models"
INTERNAL_AUTH_PROVIDER ||--o{ INTERNAL_AUTH_SUCCESS_RULE : "success rules"
MCP_SERVER ||--o{ MCP_CATALOG_ITEM : "catalog"
CHAT_SESSION ||--o{ CHAT_MESSAGE : "contains"
CHAT_SESSION }o--o| LLM_PROVIDER : "uses"
TOOL_APPROVAL }o--|| APP_USER : "requested_by"
TOOL_APPROVAL }o--|| CHAT_SESSION : "in"
AUDIT_LOG }o--o| APP_USER : "actor"
TOKEN_USAGE_RECORD }o--o| APP_USER : "accrued_by"
Loading

System Architecture

flowchart LR
subgraph Web["Web (Vue 3 + Cordis)"]
UI[Chat UI / 3D Theme] --> K[Kernel Plugins]
K -->|SSE / REST| HTTP[Vite proxy → /api]
end
subgraph Api["NextChats.Api (ASP.NET Core)"]
Ctl[Controllers / RBAC / Audit] --> Mod[Error Localization Middleware]
Ctl --> Orch{{ChatOrchestrator}}
Ctl --> Adm[Admin: Providers / MCP / Prompts / Skills / RBAC / Internal Auth / Approvals]
Ctl --> Auth[Auth: default password / internal auth]
Auth -->|HTTP| IA[(Internal Auth Center acs / ucs…)]
end
Orch --> Loop{{AgentLoopEngine}}
Loop -->|ReAct think→act→observe| Router[LLM Router]
Router --> OpenAI[OpenAI-Compatible Client]
Router --> Mock[Mock Client: localized output]
Orch --> Driver[MCP Driver]
Driver --> MCP1[(MCP Server: Demo 5300)]
Driver --> MCP2[(MCP Server: more…)]
Orch --> Skills[Skill Execution Engine]
Orch --> Policy[Policy Engine / Approval Coordinator]
Api --> DB[(SQLite: config / chat / audit)]
UI --> V3[Three.js scenes]
style Orch fill:#e3f2fd
style Loop fill:#fff3e0
Loading

Chat (ReAct) Flow — SSE streaming

sequenceDiagram
participant U as User (Browser)
participant K as Cordis Kernel
participant C as ChatController
participant O as ChatOrchestrator
participant L as AgentLoopEngine
participant LLM as LLM Client
participant M as MCP Driver / Skills
U->>K: type message (+ optional images)
K->>C: POST /api/chat/stream (SSE)
C->>O: ChatStreamRequest (lang, images)
O-->>C: SESSION_NOT_FOUND? / INPUT_FLAGGED? (error event)
O->>L: AgentRunRequest (tools, context window)
loop ReAct round
L->>LLM: stream prompt + tools
LLM-->>L: reasoning / text / tool calls chunks
L-->>U: thinking_start · thinking_delta · text_delta (SSE)
alt tool call
L->>Policy: evaluate (allow/deny/approval)
Policy-->>U: approval_updated (pending) → user approves
L->>M: execute tool (approval / args)
M-->>L: tool result
L-->>U: tool_start · tool_result (SSE)
end
end
L-->>U: done (usage: tokens / ttft / cost)
O->>DB: persist assistant message + usage + audit (trace_id)
O-->>C: SSE end event
C-->>K: event stream → UI renders
Loading

Image / Vision Flow (image_source standard base64)

sequenceDiagram
participant U as User (Browser)
participant B as ChatInputBar
participant C as ChatController
participant O as ChatOrchestrator
participant V as Vision MCP Tool (image_source)
participant LLM as LLM (Mock / real)
participant P as Provider · MCP Vision Flags
U->>C: GET /api/chat/vision-config
C->>P: provider.IsVision ∨ bound MCP.IsVision
P-->>U: supported: true → show upload button + Ctrl+V
U->>B: paste (Ctrl+V) or upload N images
B->>B: validate type/size → dataURL → base64
B->>C: POST /api/chat/stream { message, images[] }
C->>O: ChatStreamRequest.Images (standard base64)
loop each image (multi-image recognized one by one)
O->>V: call vision tool { image_source: base64 }
V-->>O: recognition text (per image)
end
O->>LLM: system + history + [vision texts] + user(message + <image_source> blocks)
LLM-->>U: streamed answer (SSE) using the recognized text
Loading

Tech Stack

LayerTechnology
Backend.NET 10 · ASP.NET Core · EF Core (SQLite now → MySQL 8 ready)
MCPModelContextProtocol SDK 2.2.0 (latest stable) · Streamable HTTP transport · STDIO extensible
FrontendVue 3 · TypeScript · Vite · Element Plus · Three.js · Cordis ^3.18.1 (plugin kernel)
SecurityJWT dual-token (30-min access + rotating refresh, revoked on disable/logout) · PBKDF2-SHA256(210k) password hashing · AES-256-GCM secret encryption · sanitized audit logs
i18nvue-i18n 10 (en / zh-CN, English default) · X-Lang error localization on the API

Repository Layout

next-chats/
├── src/
│ ├── NextChats.Core/ # Domain model, orchestration, engines, drivers (no ASP.NET dependency)
│ ├── NextChats.Infrastructure/ # EF Core data, caching, security services (swappable)
│ └── NextChats.Api/ # Web API (SSE streaming, admin, RBAC, audit, metrics, i18n)
├── samples/McpDemoServer/ # Demo MCP Server (Streamable HTTP)
├── e2e/ # Internal-auth E2E (mock auth center + PowerShell suite)
└── web/ # Vue 3 + Cordis frontend
├── src/i18n/ # en / zh dictionaries (per-domain locale files)
└── scripts/smoke*.mjs # End-to-end smoke scripts (Node fetch streaming)

Quick Start

# 1. Start the demo MCP Server (port 5300)
dotnet run --project samples/McpDemoServer -c Debug
# 2. Start the API (port 5210; DB is auto-created and seeded on first boot)
dotnet run --project src/NextChats.Api -c Debug
# 3. Start the frontend (Vite proxies /api → 5210)cd web && npm install && npm run dev
# Open http://localhost:5173 (seeded admin: admin / admin123)

Core Design

Authentication (default accounts + internal auth)

  • default(system accounts): local password accounts (PBKDF2-SHA256), created by an admin in User Management; the login page defaults to default.
  • Internal auth (acs / ucs…): admins maintain auth-center configs in Internal Auth Management — API URL, HTTP method, the request-body field names for the username/password (body(application/json)), success-response rules (one or more fields that must be not empty or equal a fixed value, dotted paths such as data.sessionID supported), and the category's default roles (multi-select).
  • The login page shows a sign-in-method radio group (default + enabled auth centers, from GET /api/auth/providers); choosing an internal method makes the backend validate the credentials against the configured auth center.
  • After a successful validation the user is auto-provisioned or reused: internal users are unique by (AuthType, Username) (a default and an acs account may share a username); when the user already exists their roles are read directly, otherwise a user is created (no password, displayName = username, status Active, bound to the provider's default roles).
  • Internal-auth users share the same JWT mechanism as default accounts: chat / catalog / permissions behave identically afterwards (role → model/MCP/Prompt/Skill bindings decide the visible scope).
  • Dual-token session (double-token): login issues a short-lived access token (30 min) plus a persisted refresh token (7 days, stored as SHA-256 hash). When the access token expires, the frontend silently exchanges the refresh token (POST /api/auth/refresh) and replays the request — active users are never logged out mid-work, idle sessions expire after 7 days. Every refresh rotates the token (the old one is revoked and recorded as replaced, so replays are rejected). Re-login, logout, and user disable revoke that user's refresh tokens immediately (a disabled account can no longer renew, and its access token dies within 30 min).
sequenceDiagram
participant U as User (Browser)
participant C as AuthController
participant P as InternalAuthProvider config
participant A as Auth Center (external)
participant DB as Users table
U->>C: POST /api/auth/login { authType: "acs", username, password }
C->>P: load provider (api / fields / success rules / default roles)
C->>A: HTTP POST api { username_field: …, password_field: … }
A-->>C: response JSON
C->>C: evaluate success rules (field not empty / equals value)
alt rule satisfied
C->>DB: find user by (AuthType=acs, Username)
alt not exists
C->>DB: auto-create (no password, default roles)
end
C-->>U: JWT (same mechanism as default accounts) + user info
else rule failed / auth center error
C-->>U: 401 (AUTH_INVALID_CREDENTIALS / AUTH_PROVIDER_ERROR)
end
Loading

Orchestration (Core)

  • Effective tool intersection: role binding ∩ user selection ∩ MCP global enabled per user, with data isolation.
  • Active Skill matching: Skills are exposed to the model as meta tools (skill_<slug>); instructions are lazy-loaded to avoid token explosion.
  • Prompt building: a template engine ({{var}} / #if / #each / #section) renders system prompts.
  • ReAct loop (AgentLoopEngine): producer–consumer Channel architecture decouples LLM streaming from the main loop; safe interruption.

Server-side Unified Configuration (Admin)

  • LLM providers (enable flag / model / endpoint / encrypted api-key / temperature / context-window)
  • MCP servers (Name / Endpoint / Headers JSON encrypted; "Fetch" auto-discovers tools/prompts/resources with schemas; per-item disable)
  • Multiple Prompts & Skills (pluggable, lazy-load)
  • RBAC: users / roles; role ↔ MCP / Prompt / Skill / LLM model bindings —— bound models are the only models the role's users can see/select, and direct API calls with an unauthorized model id are rejected server-side (MODEL_NOT_AUTHORIZED)
  • Internal Auth: auth-center configs (acs / ucs…) with success-response rules and default roles (see Authentication above)

Engines (Core)

  • LLM Router: multi-vendor priority + round-robin + failover (mark-unhealthy circuit breaking); OpenAI-compatible & Mock clients.
  • MCP driver: strictly follows the latest MCP spec (see MCP 2026-07 revision); connection pooling; call retry with backoff. MCP errors go into the loop — tool errors are fed back to the model as tool results for retry/explanation; sessions never break because of an MCP error.
  • Policy engine: Allow → Deny → RequireApproval with dangerous-operation heuristics (delete_all etc.); approvals support pending/approved/rejected/expired.
  • Context manager: token estimate → compress (LLM summary) → truncate; never exceeds the model length limit and never interrupts the session.

Observability & Cost

  • trace_id (trc_...) flows through Orchestration / LLM / MCP / Audit; token accounting in/out; metrics: TTFT, tokens, cost, tool latency, approval count (/api/admin/metrics/usage).
  • Idempotent writes: unique (UserId, ClientMessageId) key; key chat logs persist across restarts.

Logging, three channels

  • To the user: friendly message + error code (no stack / endpoint / header)
  • To the model: tool errors are re-fed as tool results (retryable, explainable)
  • To the logs: full context (sanitized)

Internationalization

  • Frontend: vue-i18n with per-domain locale files under web/src/i18n/locales/{en,zh}/; English is the default locale.
  • Backend: every user-visible message lives in one dictionary (src/NextChats.Core/Localization/Texts.cs, en/zh) — no hard-coded UI text in code. The API localizes errors via the X-Lang request header (or Accept-Language), so non-browser clients get the same language, e.g. curl -H "X-Lang: en"; seeded content (roles, prompts, skills, demo provider) is English.
  • README: this English file and README.zh.md are linked at the top.

Smoke Verification (web/scripts/ + e2e/)

node scripts/smoke.mjs # login → create session → SSE streaming chat → persistence → metrics
node scripts/smoke-tools.mjs # ReAct tool call (tool:echo) → dangerous approval flow (danger:delete_all)# Internal-auth E2E (mock auth center + PowerShell suite, see e2e/README.md)
node e2e/mock-auth.js # start mock auth center (127.0.0.1:53131)
powershell -File e2e/run-internal-auth-e2e.ps1 # assert CRUD / providers / auto-provision / uniqueness / role-based catalog

MCP References

About

What's not here is all in next ...

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Next Chats

中文版 README(简体中文)

Chat window

Chat window (conversation / thinking / tools / topic rail)


Admin console

Admin console (LLM providers / MCP / RBAC / internal auth / approvals / audit / usage)

A B/S multimodal AI chat platform based on the DeepSeek Harness principles: LLM + MCP (Model Context Protocol) + Skill plugin orchestration, with a Cordis-driven, Three.js-powered 3D frontend.

Reference paper: A Programming Paradigm for Spatiotemporal Composability — everything is a plugin, driven by Cordis.

Multilingual UI: English (default) and 简体中文 — switch from the top bar (🌐 EN / 中文); your choice is remembered in localStorage.


Architecture & Flows

Data Model (ER)

erDiagram
APP_USER {
Guid id PK
string auth_type "default=password account; acs/ucs…=internal auth"
string username "unique together with auth_type"
string display_name
string email
string password_hash
string password_salt
string status
datetime created_at
datetime last_login_at
}
APP_ROLE {
Guid id PK
string name
string code UK
string description
bool is_system
datetime created_at
}
CHAT_SESSION {
Guid id PK
Guid user_id FK
string title
string status
Guid llm_provider_id
string context_json
datetime created_at
datetime updated_at
datetime last_message_at
}
CHAT_MESSAGE {
Guid id PK
Guid session_id FK
Guid user_id FK
string role
text content
text reasoning
string tool_calls_json
string status
string model
int prompt_tokens
int completion_tokens
string trace_id
string client_message_id UK
datetime created_at
}
LLM_PROVIDER {
Guid id PK
string name
string kind
string base_url
string api_key_encrypted
int priority
bool enabled
bool is_healthy
datetime created_at
datetime updated_at
}
LLM_MODEL {
Guid id PK
Guid provider_id FK
string name
bool enabled
bool is_vision
int context_window
decimal price_in_per_1k
decimal price_out_per_1k
int priority
datetime created_at
}
INTERNAL_AUTH_PROVIDER {
Guid id PK
string name "acs / ucs…, unique"
string api "auth center URL"
string http_method
string request_format "body(application/json)"
string username_field
string password_field
bool enabled
int timeout_seconds
datetime created_at
}
INTERNAL_AUTH_SUCCESS_RULE {
Guid id PK
Guid provider_id FK
string field "JSON path, e.g. sessionID"
string operator "NotEmpty / Equals"
string expected_value
}
MCP_SERVER {
Guid id PK
string name
string transport
string endpoint
string headers_json
string stdio_command
bool enabled
bool is_vision
int timeout_seconds
string description
datetime created_at
}
MCP_CATALOG_ITEM {
Guid id PK
Guid mcp_server_id FK
string kind
string name
string description
string schema_json
bool enabled
}
PROMPT {
Guid id PK
string name
string description
text content
bool enabled
int version
datetime created_at
}
SKILL {
Guid id PK
string name
string meta_tool_name
text instruction
bool enabled
string model_override
int max_nested_steps
}
TOOL_APPROVAL {
Guid id PK
string trace_id
Guid user_id FK
Guid session_id FK
string mcp_server_name
string tool_name
string arguments_json
string status
datetime created_at
datetime decided_at
datetime expires_at
}
AUDIT_LOG {
Guid id PK
string trace_id
Guid user_id FK
string category
string action
string target
string detail_json
string ip
bool is_suspicious
datetime created_at
}
USER_SETTING {
Guid id PK
Guid user_id FK
string key
string value_json
}
TOKEN_USAGE_RECORD {
Guid id PK
string trace_id UK
Guid user_id FK
Guid session_id FK
string provider_name
string model
int total_tokens
decimal cost
int ttft_ms
datetime created_at
}
APP_USER ||--o{ CHAT_SESSION : "owns"
APP_USER ||--o{ CHAT_MESSAGE : "sends"
APP_USER }o--o{ APP_ROLE : "member_of (user_roles)"
APP_USER ||--o{ USER_SETTING : "has"
APP_ROLE }o--o{ MCP_SERVER : "can_use (role_mcp_servers)"
APP_ROLE }o--o{ PROMPT : "can_use (role_prompts)"
APP_ROLE }o--o{ SKILL : "can_use (role_skills)"
APP_ROLE }o--o{ LLM_MODEL : "can_use (role_model_bindings)"
APP_ROLE }o--o{ INTERNAL_AUTH_PROVIDER : "default roles (internal_auth_provider_roles)"
LLM_PROVIDER ||--o{ LLM_MODEL : "models"
INTERNAL_AUTH_PROVIDER ||--o{ INTERNAL_AUTH_SUCCESS_RULE : "success rules"
MCP_SERVER ||--o{ MCP_CATALOG_ITEM : "catalog"
CHAT_SESSION ||--o{ CHAT_MESSAGE : "contains"
CHAT_SESSION }o--o| LLM_PROVIDER : "uses"
TOOL_APPROVAL }o--|| APP_USER : "requested_by"
TOOL_APPROVAL }o--|| CHAT_SESSION : "in"
AUDIT_LOG }o--o| APP_USER : "actor"
TOKEN_USAGE_RECORD }o--o| APP_USER : "accrued_by"
Loading

System Architecture

flowchart LR
subgraph Web["Web (Vue 3 + Cordis)"]
UI[Chat UI / 3D Theme] --> K[Kernel Plugins]
K -->|SSE / REST| HTTP[Vite proxy → /api]
end
subgraph Api["NextChats.Api (ASP.NET Core)"]
Ctl[Controllers / RBAC / Audit] --> Mod[Error Localization Middleware]
Ctl --> Orch{{ChatOrchestrator}}
Ctl --> Adm[Admin: Providers / MCP / Prompts / Skills / RBAC / Internal Auth / Approvals]
Ctl --> Auth[Auth: default password / internal auth]
Auth -->|HTTP| IA[(Internal Auth Center acs / ucs…)]
end
Orch --> Loop{{AgentLoopEngine}}
Loop -->|ReAct think→act→observe| Router[LLM Router]
Router --> OpenAI[OpenAI-Compatible Client]
Router --> Mock[Mock Client: localized output]
Orch --> Driver[MCP Driver]
Driver --> MCP1[(MCP Server: Demo 5300)]
Driver --> MCP2[(MCP Server: more…)]
Orch --> Skills[Skill Execution Engine]
Orch --> Policy[Policy Engine / Approval Coordinator]
Api --> DB[(SQLite: config / chat / audit)]
UI --> V3[Three.js scenes]
style Orch fill:#e3f2fd
style Loop fill:#fff3e0
Loading

Chat (ReAct) Flow — SSE streaming

sequenceDiagram
participant U as User (Browser)
participant K as Cordis Kernel
participant C as ChatController
participant O as ChatOrchestrator
participant L as AgentLoopEngine
participant LLM as LLM Client
participant M as MCP Driver / Skills
U->>K: type message (+ optional images)
K->>C: POST /api/chat/stream (SSE)
C->>O: ChatStreamRequest (lang, images)
O-->>C: SESSION_NOT_FOUND? / INPUT_FLAGGED? (error event)
O->>L: AgentRunRequest (tools, context window)
loop ReAct round
L->>LLM: stream prompt + tools
LLM-->>L: reasoning / text / tool calls chunks
L-->>U: thinking_start · thinking_delta · text_delta (SSE)
alt tool call
L->>Policy: evaluate (allow/deny/approval)
Policy-->>U: approval_updated (pending) → user approves
L->>M: execute tool (approval / args)
M-->>L: tool result
L-->>U: tool_start · tool_result (SSE)
end
end
L-->>U: done (usage: tokens / ttft / cost)
O->>DB: persist assistant message + usage + audit (trace_id)
O-->>C: SSE end event
C-->>K: event stream → UI renders
Loading

Image / Vision Flow (image_source standard base64)

sequenceDiagram
participant U as User (Browser)
participant B as ChatInputBar
participant C as ChatController
participant O as ChatOrchestrator
participant V as Vision MCP Tool (image_source)
participant LLM as LLM (Mock / real)
participant P as Provider · MCP Vision Flags
U->>C: GET /api/chat/vision-config
C->>P: provider.IsVision ∨ bound MCP.IsVision
P-->>U: supported: true → show upload button + Ctrl+V
U->>B: paste (Ctrl+V) or upload N images
B->>B: validate type/size → dataURL → base64
B->>C: POST /api/chat/stream { message, images[] }
C->>O: ChatStreamRequest.Images (standard base64)
loop each image (multi-image recognized one by one)
O->>V: call vision tool { image_source: base64 }
V-->>O: recognition text (per image)
end
O->>LLM: system + history + [vision texts] + user(message + <image_source> blocks)
LLM-->>U: streamed answer (SSE) using the recognized text
Loading

Tech Stack

LayerTechnology
Backend.NET 10 · ASP.NET Core · EF Core (SQLite now → MySQL 8 ready)
MCPModelContextProtocol SDK 2.2.0 (latest stable) · Streamable HTTP transport · STDIO extensible
FrontendVue 3 · TypeScript · Vite · Element Plus · Three.js · Cordis ^3.18.1 (plugin kernel)
SecurityJWT dual-token (30-min access + rotating refresh, revoked on disable/logout) · PBKDF2-SHA256(210k) password hashing · AES-256-GCM secret encryption · sanitized audit logs
i18nvue-i18n 10 (en / zh-CN, English default) · X-Lang error localization on the API

Repository Layout

next-chats/
├── src/
│ ├── NextChats.Core/ # Domain model, orchestration, engines, drivers (no ASP.NET dependency)
│ ├── NextChats.Infrastructure/ # EF Core data, caching, security services (swappable)
│ └── NextChats.Api/ # Web API (SSE streaming, admin, RBAC, audit, metrics, i18n)
├── samples/McpDemoServer/ # Demo MCP Server (Streamable HTTP)
├── e2e/ # Internal-auth E2E (mock auth center + PowerShell suite)
└── web/ # Vue 3 + Cordis frontend
├── src/i18n/ # en / zh dictionaries (per-domain locale files)
└── scripts/smoke*.mjs # End-to-end smoke scripts (Node fetch streaming)

Quick Start

# 1. Start the demo MCP Server (port 5300)
dotnet run --project samples/McpDemoServer -c Debug
# 2. Start the API (port 5210; DB is auto-created and seeded on first boot)
dotnet run --project src/NextChats.Api -c Debug
# 3. Start the frontend (Vite proxies /api → 5210)cd web && npm install && npm run dev
# Open http://localhost:5173 (seeded admin: admin / admin123)

Core Design

Authentication (default accounts + internal auth)

  • default(system accounts): local password accounts (PBKDF2-SHA256), created by an admin in User Management; the login page defaults to default.
  • Internal auth (acs / ucs…): admins maintain auth-center configs in Internal Auth Management — API URL, HTTP method, the request-body field names for the username/password (body(application/json)), success-response rules (one or more fields that must be not empty or equal a fixed value, dotted paths such as data.sessionID supported), and the category's default roles (multi-select).
  • The login page shows a sign-in-method radio group (default + enabled auth centers, from GET /api/auth/providers); choosing an internal method makes the backend validate the credentials against the configured auth center.
  • After a successful validation the user is auto-provisioned or reused: internal users are unique by (AuthType, Username) (a default and an acs account may share a username); when the user already exists their roles are read directly, otherwise a user is created (no password, displayName = username, status Active, bound to the provider's default roles).
  • Internal-auth users share the same JWT mechanism as default accounts: chat / catalog / permissions behave identically afterwards (role → model/MCP/Prompt/Skill bindings decide the visible scope).
  • Dual-token session (double-token): login issues a short-lived access token (30 min) plus a persisted refresh token (7 days, stored as SHA-256 hash). When the access token expires, the frontend silently exchanges the refresh token (POST /api/auth/refresh) and replays the request — active users are never logged out mid-work, idle sessions expire after 7 days. Every refresh rotates the token (the old one is revoked and recorded as replaced, so replays are rejected). Re-login, logout, and user disable revoke that user's refresh tokens immediately (a disabled account can no longer renew, and its access token dies within 30 min).
sequenceDiagram
participant U as User (Browser)
participant C as AuthController
participant P as InternalAuthProvider config
participant A as Auth Center (external)
participant DB as Users table
U->>C: POST /api/auth/login { authType: "acs", username, password }
C->>P: load provider (api / fields / success rules / default roles)
C->>A: HTTP POST api { username_field: …, password_field: … }
A-->>C: response JSON
C->>C: evaluate success rules (field not empty / equals value)
alt rule satisfied
C->>DB: find user by (AuthType=acs, Username)
alt not exists
C->>DB: auto-create (no password, default roles)
end
C-->>U: JWT (same mechanism as default accounts) + user info
else rule failed / auth center error
C-->>U: 401 (AUTH_INVALID_CREDENTIALS / AUTH_PROVIDER_ERROR)
end
Loading

Orchestration (Core)

  • Effective tool intersection: role binding ∩ user selection ∩ MCP global enabled per user, with data isolation.
  • Active Skill matching: Skills are exposed to the model as meta tools (skill_<slug>); instructions are lazy-loaded to avoid token explosion.
  • Prompt building: a template engine ({{var}} / #if / #each / #section) renders system prompts.
  • ReAct loop (AgentLoopEngine): producer–consumer Channel architecture decouples LLM streaming from the main loop; safe interruption.

Server-side Unified Configuration (Admin)

  • LLM providers (enable flag / model / endpoint / encrypted api-key / temperature / context-window)
  • MCP servers (Name / Endpoint / Headers JSON encrypted; "Fetch" auto-discovers tools/prompts/resources with schemas; per-item disable)
  • Multiple Prompts & Skills (pluggable, lazy-load)
  • RBAC: users / roles; role ↔ MCP / Prompt / Skill / LLM model bindings —— bound models are the only models the role's users can see/select, and direct API calls with an unauthorized model id are rejected server-side (MODEL_NOT_AUTHORIZED)
  • Internal Auth: auth-center configs (acs / ucs…) with success-response rules and default roles (see Authentication above)

Engines (Core)

  • LLM Router: multi-vendor priority + round-robin + failover (mark-unhealthy circuit breaking); OpenAI-compatible & Mock clients.
  • MCP driver: strictly follows the latest MCP spec (see MCP 2026-07 revision); connection pooling; call retry with backoff. MCP errors go into the loop — tool errors are fed back to the model as tool results for retry/explanation; sessions never break because of an MCP error.
  • Policy engine: Allow → Deny → RequireApproval with dangerous-operation heuristics (delete_all etc.); approvals support pending/approved/rejected/expired.
  • Context manager: token estimate → compress (LLM summary) → truncate; never exceeds the model length limit and never interrupts the session.

Observability & Cost

  • trace_id (trc_...) flows through Orchestration / LLM / MCP / Audit; token accounting in/out; metrics: TTFT, tokens, cost, tool latency, approval count (/api/admin/metrics/usage).
  • Idempotent writes: unique (UserId, ClientMessageId) key; key chat logs persist across restarts.

Logging, three channels

  • To the user: friendly message + error code (no stack / endpoint / header)
  • To the model: tool errors are re-fed as tool results (retryable, explainable)
  • To the logs: full context (sanitized)

Internationalization

  • Frontend: vue-i18n with per-domain locale files under web/src/i18n/locales/{en,zh}/; English is the default locale.
  • Backend: every user-visible message lives in one dictionary (src/NextChats.Core/Localization/Texts.cs, en/zh) — no hard-coded UI text in code. The API localizes errors via the X-Lang request header (or Accept-Language), so non-browser clients get the same language, e.g. curl -H "X-Lang: en"; seeded content (roles, prompts, skills, demo provider) is English.
  • README: this English file and README.zh.md are linked at the top.

Smoke Verification (web/scripts/ + e2e/)

node scripts/smoke.mjs # login → create session → SSE streaming chat → persistence → metrics
node scripts/smoke-tools.mjs # ReAct tool call (tool:echo) → dangerous approval flow (danger:delete_all)# Internal-auth E2E (mock auth center + PowerShell suite, see e2e/README.md)
node e2e/mock-auth.js # start mock auth center (127.0.0.1:53131)
powershell -File e2e/run-internal-auth-e2e.ps1 # assert CRUD / providers / auto-provision / uniqueness / role-based catalog

MCP References

About

What's not here is all in next ...

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Next Chats

中文版 README(简体中文)

Chat window

Chat window (conversation / thinking / tools / topic rail)


Admin console

Admin console (LLM providers / MCP / RBAC / internal auth / approvals / audit / usage)

A B/S multimodal AI chat platform based on the DeepSeek Harness principles: LLM + MCP (Model Context Protocol) + Skill plugin orchestration, with a Cordis-driven, Three.js-powered 3D frontend.

Reference paper: A Programming Paradigm for Spatiotemporal Composability — everything is a plugin, driven by Cordis.

Multilingual UI: English (default) and 简体中文 — switch from the top bar (🌐 EN / 中文); your choice is remembered in localStorage.


Architecture & Flows

Data Model (ER)

erDiagram
APP_USER {
Guid id PK
string auth_type "default=password account; acs/ucs…=internal auth"
string username "unique together with auth_type"
string display_name
string email
string password_hash
string password_salt
string status
datetime created_at
datetime last_login_at
}
APP_ROLE {
Guid id PK
string name
string code UK
string description
bool is_system
datetime created_at
}
CHAT_SESSION {
Guid id PK
Guid user_id FK
string title
string status
Guid llm_provider_id
string context_json
datetime created_at
datetime updated_at
datetime last_message_at
}
CHAT_MESSAGE {
Guid id PK
Guid session_id FK
Guid user_id FK
string role
text content
text reasoning
string tool_calls_json
string status
string model
int prompt_tokens
int completion_tokens
string trace_id
string client_message_id UK
datetime created_at
}
LLM_PROVIDER {
Guid id PK
string name
string kind
string base_url
string api_key_encrypted
int priority
bool enabled
bool is_healthy
datetime created_at
datetime updated_at
}
LLM_MODEL {
Guid id PK
Guid provider_id FK
string name
bool enabled
bool is_vision
int context_window
decimal price_in_per_1k
decimal price_out_per_1k
int priority
datetime created_at
}
INTERNAL_AUTH_PROVIDER {
Guid id PK
string name "acs / ucs…, unique"
string api "auth center URL"
string http_method
string request_format "body(application/json)"
string username_field
string password_field
bool enabled
int timeout_seconds
datetime created_at
}
INTERNAL_AUTH_SUCCESS_RULE {
Guid id PK
Guid provider_id FK
string field "JSON path, e.g. sessionID"
string operator "NotEmpty / Equals"
string expected_value
}
MCP_SERVER {
Guid id PK
string name
string transport
string endpoint
string headers_json
string stdio_command
bool enabled
bool is_vision
int timeout_seconds
string description
datetime created_at
}
MCP_CATALOG_ITEM {
Guid id PK
Guid mcp_server_id FK
string kind
string name
string description
string schema_json
bool enabled
}
PROMPT {
Guid id PK
string name
string description
text content
bool enabled
int version
datetime created_at
}
SKILL {
Guid id PK
string name
string meta_tool_name
text instruction
bool enabled
string model_override
int max_nested_steps
}
TOOL_APPROVAL {
Guid id PK
string trace_id
Guid user_id FK
Guid session_id FK
string mcp_server_name
string tool_name
string arguments_json
string status
datetime created_at
datetime decided_at
datetime expires_at
}
AUDIT_LOG {
Guid id PK
string trace_id
Guid user_id FK
string category
string action
string target
string detail_json
string ip
bool is_suspicious
datetime created_at
}
USER_SETTING {
Guid id PK
Guid user_id FK
string key
string value_json
}
TOKEN_USAGE_RECORD {
Guid id PK
string trace_id UK
Guid user_id FK
Guid session_id FK
string provider_name
string model
int total_tokens
decimal cost
int ttft_ms
datetime created_at
}
APP_USER ||--o{ CHAT_SESSION : "owns"
APP_USER ||--o{ CHAT_MESSAGE : "sends"
APP_USER }o--o{ APP_ROLE : "member_of (user_roles)"
APP_USER ||--o{ USER_SETTING : "has"
APP_ROLE }o--o{ MCP_SERVER : "can_use (role_mcp_servers)"
APP_ROLE }o--o{ PROMPT : "can_use (role_prompts)"
APP_ROLE }o--o{ SKILL : "can_use (role_skills)"
APP_ROLE }o--o{ LLM_MODEL : "can_use (role_model_bindings)"
APP_ROLE }o--o{ INTERNAL_AUTH_PROVIDER : "default roles (internal_auth_provider_roles)"
LLM_PROVIDER ||--o{ LLM_MODEL : "models"
INTERNAL_AUTH_PROVIDER ||--o{ INTERNAL_AUTH_SUCCESS_RULE : "success rules"
MCP_SERVER ||--o{ MCP_CATALOG_ITEM : "catalog"
CHAT_SESSION ||--o{ CHAT_MESSAGE : "contains"
CHAT_SESSION }o--o| LLM_PROVIDER : "uses"
TOOL_APPROVAL }o--|| APP_USER : "requested_by"
TOOL_APPROVAL }o--|| CHAT_SESSION : "in"
AUDIT_LOG }o--o| APP_USER : "actor"
TOKEN_USAGE_RECORD }o--o| APP_USER : "accrued_by"
Loading

System Architecture

flowchart LR
subgraph Web["Web (Vue 3 + Cordis)"]
UI[Chat UI / 3D Theme] --> K[Kernel Plugins]
K -->|SSE / REST| HTTP[Vite proxy → /api]
end
subgraph Api["NextChats.Api (ASP.NET Core)"]
Ctl[Controllers / RBAC / Audit] --> Mod[Error Localization Middleware]
Ctl --> Orch{{ChatOrchestrator}}
Ctl --> Adm[Admin: Providers / MCP / Prompts / Skills / RBAC / Internal Auth / Approvals]
Ctl --> Auth[Auth: default password / internal auth]
Auth -->|HTTP| IA[(Internal Auth Center acs / ucs…)]
end
Orch --> Loop{{AgentLoopEngine}}
Loop -->|ReAct think→act→observe| Router[LLM Router]
Router --> OpenAI[OpenAI-Compatible Client]
Router --> Mock[Mock Client: localized output]
Orch --> Driver[MCP Driver]
Driver --> MCP1[(MCP Server: Demo 5300)]
Driver --> MCP2[(MCP Server: more…)]
Orch --> Skills[Skill Execution Engine]
Orch --> Policy[Policy Engine / Approval Coordinator]
Api --> DB[(SQLite: config / chat / audit)]
UI --> V3[Three.js scenes]
style Orch fill:#e3f2fd
style Loop fill:#fff3e0
Loading

Chat (ReAct) Flow — SSE streaming

sequenceDiagram
participant U as User (Browser)
participant K as Cordis Kernel
participant C as ChatController
participant O as ChatOrchestrator
participant L as AgentLoopEngine
participant LLM as LLM Client
participant M as MCP Driver / Skills
U->>K: type message (+ optional images)
K->>C: POST /api/chat/stream (SSE)
C->>O: ChatStreamRequest (lang, images)
O-->>C: SESSION_NOT_FOUND? / INPUT_FLAGGED? (error event)
O->>L: AgentRunRequest (tools, context window)
loop ReAct round
L->>LLM: stream prompt + tools
LLM-->>L: reasoning / text / tool calls chunks
L-->>U: thinking_start · thinking_delta · text_delta (SSE)
alt tool call
L->>Policy: evaluate (allow/deny/approval)
Policy-->>U: approval_updated (pending) → user approves
L->>M: execute tool (approval / args)
M-->>L: tool result
L-->>U: tool_start · tool_result (SSE)
end
end
L-->>U: done (usage: tokens / ttft / cost)
O->>DB: persist assistant message + usage + audit (trace_id)
O-->>C: SSE end event
C-->>K: event stream → UI renders
Loading

Image / Vision Flow (image_source standard base64)

sequenceDiagram
participant U as User (Browser)
participant B as ChatInputBar
participant C as ChatController
participant O as ChatOrchestrator
participant V as Vision MCP Tool (image_source)
participant LLM as LLM (Mock / real)
participant P as Provider · MCP Vision Flags
U->>C: GET /api/chat/vision-config
C->>P: provider.IsVision ∨ bound MCP.IsVision
P-->>U: supported: true → show upload button + Ctrl+V
U->>B: paste (Ctrl+V) or upload N images
B->>B: validate type/size → dataURL → base64
B->>C: POST /api/chat/stream { message, images[] }
C->>O: ChatStreamRequest.Images (standard base64)
loop each image (multi-image recognized one by one)
O->>V: call vision tool { image_source: base64 }
V-->>O: recognition text (per image)
end
O->>LLM: system + history + [vision texts] + user(message + <image_source> blocks)
LLM-->>U: streamed answer (SSE) using the recognized text
Loading

Tech Stack

LayerTechnology
Backend.NET 10 · ASP.NET Core · EF Core (SQLite now → MySQL 8 ready)
MCPModelContextProtocol SDK 2.2.0 (latest stable) · Streamable HTTP transport · STDIO extensible
FrontendVue 3 · TypeScript · Vite · Element Plus · Three.js · Cordis ^3.18.1 (plugin kernel)
SecurityJWT dual-token (30-min access + rotating refresh, revoked on disable/logout) · PBKDF2-SHA256(210k) password hashing · AES-256-GCM secret encryption · sanitized audit logs
i18nvue-i18n 10 (en / zh-CN, English default) · X-Lang error localization on the API

Repository Layout

next-chats/
├── src/
│ ├── NextChats.Core/ # Domain model, orchestration, engines, drivers (no ASP.NET dependency)
│ ├── NextChats.Infrastructure/ # EF Core data, caching, security services (swappable)
│ └── NextChats.Api/ # Web API (SSE streaming, admin, RBAC, audit, metrics, i18n)
├── samples/McpDemoServer/ # Demo MCP Server (Streamable HTTP)
├── e2e/ # Internal-auth E2E (mock auth center + PowerShell suite)
└── web/ # Vue 3 + Cordis frontend
├── src/i18n/ # en / zh dictionaries (per-domain locale files)
└── scripts/smoke*.mjs # End-to-end smoke scripts (Node fetch streaming)

Quick Start

# 1. Start the demo MCP Server (port 5300)
dotnet run --project samples/McpDemoServer -c Debug
# 2. Start the API (port 5210; DB is auto-created and seeded on first boot)
dotnet run --project src/NextChats.Api -c Debug
# 3. Start the frontend (Vite proxies /api → 5210)cd web && npm install && npm run dev
# Open http://localhost:5173 (seeded admin: admin / admin123)

Core Design

Authentication (default accounts + internal auth)

  • default(system accounts): local password accounts (PBKDF2-SHA256), created by an admin in User Management; the login page defaults to default.
  • Internal auth (acs / ucs…): admins maintain auth-center configs in Internal Auth Management — API URL, HTTP method, the request-body field names for the username/password (body(application/json)), success-response rules (one or more fields that must be not empty or equal a fixed value, dotted paths such as data.sessionID supported), and the category's default roles (multi-select).
  • The login page shows a sign-in-method radio group (default + enabled auth centers, from GET /api/auth/providers); choosing an internal method makes the backend validate the credentials against the configured auth center.
  • After a successful validation the user is auto-provisioned or reused: internal users are unique by (AuthType, Username) (a default and an acs account may share a username); when the user already exists their roles are read directly, otherwise a user is created (no password, displayName = username, status Active, bound to the provider's default roles).
  • Internal-auth users share the same JWT mechanism as default accounts: chat / catalog / permissions behave identically afterwards (role → model/MCP/Prompt/Skill bindings decide the visible scope).
  • Dual-token session (double-token): login issues a short-lived access token (30 min) plus a persisted refresh token (7 days, stored as SHA-256 hash). When the access token expires, the frontend silently exchanges the refresh token (POST /api/auth/refresh) and replays the request — active users are never logged out mid-work, idle sessions expire after 7 days. Every refresh rotates the token (the old one is revoked and recorded as replaced, so replays are rejected). Re-login, logout, and user disable revoke that user's refresh tokens immediately (a disabled account can no longer renew, and its access token dies within 30 min).
sequenceDiagram
participant U as User (Browser)
participant C as AuthController
participant P as InternalAuthProvider config
participant A as Auth Center (external)
participant DB as Users table
U->>C: POST /api/auth/login { authType: "acs", username, password }
C->>P: load provider (api / fields / success rules / default roles)
C->>A: HTTP POST api { username_field: …, password_field: … }
A-->>C: response JSON
C->>C: evaluate success rules (field not empty / equals value)
alt rule satisfied
C->>DB: find user by (AuthType=acs, Username)
alt not exists
C->>DB: auto-create (no password, default roles)
end
C-->>U: JWT (same mechanism as default accounts) + user info
else rule failed / auth center error
C-->>U: 401 (AUTH_INVALID_CREDENTIALS / AUTH_PROVIDER_ERROR)
end
Loading

Orchestration (Core)

  • Effective tool intersection: role binding ∩ user selection ∩ MCP global enabled per user, with data isolation.
  • Active Skill matching: Skills are exposed to the model as meta tools (skill_<slug>); instructions are lazy-loaded to avoid token explosion.
  • Prompt building: a template engine ({{var}} / #if / #each / #section) renders system prompts.
  • ReAct loop (AgentLoopEngine): producer–consumer Channel architecture decouples LLM streaming from the main loop; safe interruption.

Server-side Unified Configuration (Admin)

  • LLM providers (enable flag / model / endpoint / encrypted api-key / temperature / context-window)
  • MCP servers (Name / Endpoint / Headers JSON encrypted; "Fetch" auto-discovers tools/prompts/resources with schemas; per-item disable)
  • Multiple Prompts & Skills (pluggable, lazy-load)
  • RBAC: users / roles; role ↔ MCP / Prompt / Skill / LLM model bindings —— bound models are the only models the role's users can see/select, and direct API calls with an unauthorized model id are rejected server-side (MODEL_NOT_AUTHORIZED)
  • Internal Auth: auth-center configs (acs / ucs…) with success-response rules and default roles (see Authentication above)

Engines (Core)

  • LLM Router: multi-vendor priority + round-robin + failover (mark-unhealthy circuit breaking); OpenAI-compatible & Mock clients.
  • MCP driver: strictly follows the latest MCP spec (see MCP 2026-07 revision); connection pooling; call retry with backoff. MCP errors go into the loop — tool errors are fed back to the model as tool results for retry/explanation; sessions never break because of an MCP error.
  • Policy engine: Allow → Deny → RequireApproval with dangerous-operation heuristics (delete_all etc.); approvals support pending/approved/rejected/expired.
  • Context manager: token estimate → compress (LLM summary) → truncate; never exceeds the model length limit and never interrupts the session.

Observability & Cost

  • trace_id (trc_...) flows through Orchestration / LLM / MCP / Audit; token accounting in/out; metrics: TTFT, tokens, cost, tool latency, approval count (/api/admin/metrics/usage).
  • Idempotent writes: unique (UserId, ClientMessageId) key; key chat logs persist across restarts.

Logging, three channels

  • To the user: friendly message + error code (no stack / endpoint / header)
  • To the model: tool errors are re-fed as tool results (retryable, explainable)
  • To the logs: full context (sanitized)

Internationalization

  • Frontend: vue-i18n with per-domain locale files under web/src/i18n/locales/{en,zh}/; English is the default locale.
  • Backend: every user-visible message lives in one dictionary (src/NextChats.Core/Localization/Texts.cs, en/zh) — no hard-coded UI text in code. The API localizes errors via the X-Lang request header (or Accept-Language), so non-browser clients get the same language, e.g. curl -H "X-Lang: en"; seeded content (roles, prompts, skills, demo provider) is English.
  • README: this English file and README.zh.md are linked at the top.

Smoke Verification (web/scripts/ + e2e/)

node scripts/smoke.mjs # login → create session → SSE streaming chat → persistence → metrics
node scripts/smoke-tools.mjs # ReAct tool call (tool:echo) → dangerous approval flow (danger:delete_all)# Internal-auth E2E (mock auth center + PowerShell suite, see e2e/README.md)
node e2e/mock-auth.js # start mock auth center (127.0.0.1:53131)
powershell -File e2e/run-internal-auth-e2e.ps1 # assert CRUD / providers / auto-provision / uniqueness / role-based catalog

MCP References

About

What's not here is all in next ...

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Next Chats

中文版 README(简体中文)

Chat window

Chat window (conversation / thinking / tools / topic rail)


Admin console

Admin console (LLM providers / MCP / RBAC / internal auth / approvals / audit / usage)

A B/S multimodal AI chat platform based on the DeepSeek Harness principles: LLM + MCP (Model Context Protocol) + Skill plugin orchestration, with a Cordis-driven, Three.js-powered 3D frontend.

Reference paper: A Programming Paradigm for Spatiotemporal Composability — everything is a plugin, driven by Cordis.

Multilingual UI: English (default) and 简体中文 — switch from the top bar (🌐 EN / 中文); your choice is remembered in localStorage.


Architecture & Flows

Data Model (ER)

erDiagram
APP_USER {
Guid id PK
string auth_type "default=password account; acs/ucs…=internal auth"
string username "unique together with auth_type"
string display_name
string email
string password_hash
string password_salt
string status
datetime created_at
datetime last_login_at
}
APP_ROLE {
Guid id PK
string name
string code UK
string description
bool is_system
datetime created_at
}
CHAT_SESSION {
Guid id PK
Guid user_id FK
string title
string status
Guid llm_provider_id
string context_json
datetime created_at
datetime updated_at
datetime last_message_at
}
CHAT_MESSAGE {
Guid id PK
Guid session_id FK
Guid user_id FK
string role
text content
text reasoning
string tool_calls_json
string status
string model
int prompt_tokens
int completion_tokens
string trace_id
string client_message_id UK
datetime created_at
}
LLM_PROVIDER {
Guid id PK
string name
string kind
string base_url
string api_key_encrypted
int priority
bool enabled
bool is_healthy
datetime created_at
datetime updated_at
}
LLM_MODEL {
Guid id PK
Guid provider_id FK
string name
bool enabled
bool is_vision
int context_window
decimal price_in_per_1k
decimal price_out_per_1k
int priority
datetime created_at
}
INTERNAL_AUTH_PROVIDER {
Guid id PK
string name "acs / ucs…, unique"
string api "auth center URL"
string http_method
string request_format "body(application/json)"
string username_field
string password_field
bool enabled
int timeout_seconds
datetime created_at
}
INTERNAL_AUTH_SUCCESS_RULE {
Guid id PK
Guid provider_id FK
string field "JSON path, e.g. sessionID"
string operator "NotEmpty / Equals"
string expected_value
}
MCP_SERVER {
Guid id PK
string name
string transport
string endpoint
string headers_json
string stdio_command
bool enabled
bool is_vision
int timeout_seconds
string description
datetime created_at
}
MCP_CATALOG_ITEM {
Guid id PK
Guid mcp_server_id FK
string kind
string name
string description
string schema_json
bool enabled
}
PROMPT {
Guid id PK
string name
string description
text content
bool enabled
int version
datetime created_at
}
SKILL {
Guid id PK
string name
string meta_tool_name
text instruction
bool enabled
string model_override
int max_nested_steps
}
TOOL_APPROVAL {
Guid id PK
string trace_id
Guid user_id FK
Guid session_id FK
string mcp_server_name
string tool_name
string arguments_json
string status
datetime created_at
datetime decided_at
datetime expires_at
}
AUDIT_LOG {
Guid id PK
string trace_id
Guid user_id FK
string category
string action
string target
string detail_json
string ip
bool is_suspicious
datetime created_at
}
USER_SETTING {
Guid id PK
Guid user_id FK
string key
string value_json
}
TOKEN_USAGE_RECORD {
Guid id PK
string trace_id UK
Guid user_id FK
Guid session_id FK
string provider_name
string model
int total_tokens
decimal cost
int ttft_ms
datetime created_at
}
APP_USER ||--o{ CHAT_SESSION : "owns"
APP_USER ||--o{ CHAT_MESSAGE : "sends"
APP_USER }o--o{ APP_ROLE : "member_of (user_roles)"
APP_USER ||--o{ USER_SETTING : "has"
APP_ROLE }o--o{ MCP_SERVER : "can_use (role_mcp_servers)"
APP_ROLE }o--o{ PROMPT : "can_use (role_prompts)"
APP_ROLE }o--o{ SKILL : "can_use (role_skills)"
APP_ROLE }o--o{ LLM_MODEL : "can_use (role_model_bindings)"
APP_ROLE }o--o{ INTERNAL_AUTH_PROVIDER : "default roles (internal_auth_provider_roles)"
LLM_PROVIDER ||--o{ LLM_MODEL : "models"
INTERNAL_AUTH_PROVIDER ||--o{ INTERNAL_AUTH_SUCCESS_RULE : "success rules"
MCP_SERVER ||--o{ MCP_CATALOG_ITEM : "catalog"
CHAT_SESSION ||--o{ CHAT_MESSAGE : "contains"
CHAT_SESSION }o--o| LLM_PROVIDER : "uses"
TOOL_APPROVAL }o--|| APP_USER : "requested_by"
TOOL_APPROVAL }o--|| CHAT_SESSION : "in"
AUDIT_LOG }o--o| APP_USER : "actor"
TOKEN_USAGE_RECORD }o--o| APP_USER : "accrued_by"
Loading

System Architecture

flowchart LR
subgraph Web["Web (Vue 3 + Cordis)"]
UI[Chat UI / 3D Theme] --> K[Kernel Plugins]
K -->|SSE / REST| HTTP[Vite proxy → /api]
end
subgraph Api["NextChats.Api (ASP.NET Core)"]
Ctl[Controllers / RBAC / Audit] --> Mod[Error Localization Middleware]
Ctl --> Orch{{ChatOrchestrator}}
Ctl --> Adm[Admin: Providers / MCP / Prompts / Skills / RBAC / Internal Auth / Approvals]
Ctl --> Auth[Auth: default password / internal auth]
Auth -->|HTTP| IA[(Internal Auth Center acs / ucs…)]
end
Orch --> Loop{{AgentLoopEngine}}
Loop -->|ReAct think→act→observe| Router[LLM Router]
Router --> OpenAI[OpenAI-Compatible Client]
Router --> Mock[Mock Client: localized output]
Orch --> Driver[MCP Driver]
Driver --> MCP1[(MCP Server: Demo 5300)]
Driver --> MCP2[(MCP Server: more…)]
Orch --> Skills[Skill Execution Engine]
Orch --> Policy[Policy Engine / Approval Coordinator]
Api --> DB[(SQLite: config / chat / audit)]
UI --> V3[Three.js scenes]
style Orch fill:#e3f2fd
style Loop fill:#fff3e0
Loading

Chat (ReAct) Flow — SSE streaming

sequenceDiagram
participant U as User (Browser)
participant K as Cordis Kernel
participant C as ChatController
participant O as ChatOrchestrator
participant L as AgentLoopEngine
participant LLM as LLM Client
participant M as MCP Driver / Skills
U->>K: type message (+ optional images)
K->>C: POST /api/chat/stream (SSE)
C->>O: ChatStreamRequest (lang, images)
O-->>C: SESSION_NOT_FOUND? / INPUT_FLAGGED? (error event)
O->>L: AgentRunRequest (tools, context window)
loop ReAct round
L->>LLM: stream prompt + tools
LLM-->>L: reasoning / text / tool calls chunks
L-->>U: thinking_start · thinking_delta · text_delta (SSE)
alt tool call
L->>Policy: evaluate (allow/deny/approval)
Policy-->>U: approval_updated (pending) → user approves
L->>M: execute tool (approval / args)
M-->>L: tool result
L-->>U: tool_start · tool_result (SSE)
end
end
L-->>U: done (usage: tokens / ttft / cost)
O->>DB: persist assistant message + usage + audit (trace_id)
O-->>C: SSE end event
C-->>K: event stream → UI renders
Loading

Image / Vision Flow (image_source standard base64)

sequenceDiagram
participant U as User (Browser)
participant B as ChatInputBar
participant C as ChatController
participant O as ChatOrchestrator
participant V as Vision MCP Tool (image_source)
participant LLM as LLM (Mock / real)
participant P as Provider · MCP Vision Flags
U->>C: GET /api/chat/vision-config
C->>P: provider.IsVision ∨ bound MCP.IsVision
P-->>U: supported: true → show upload button + Ctrl+V
U->>B: paste (Ctrl+V) or upload N images
B->>B: validate type/size → dataURL → base64
B->>C: POST /api/chat/stream { message, images[] }
C->>O: ChatStreamRequest.Images (standard base64)
loop each image (multi-image recognized one by one)
O->>V: call vision tool { image_source: base64 }
V-->>O: recognition text (per image)
end
O->>LLM: system + history + [vision texts] + user(message + <image_source> blocks)
LLM-->>U: streamed answer (SSE) using the recognized text
Loading

Tech Stack

LayerTechnology
Backend.NET 10 · ASP.NET Core · EF Core (SQLite now → MySQL 8 ready)
MCPModelContextProtocol SDK 2.2.0 (latest stable) · Streamable HTTP transport · STDIO extensible
FrontendVue 3 · TypeScript · Vite · Element Plus · Three.js · Cordis ^3.18.1 (plugin kernel)
SecurityJWT dual-token (30-min access + rotating refresh, revoked on disable/logout) · PBKDF2-SHA256(210k) password hashing · AES-256-GCM secret encryption · sanitized audit logs
i18nvue-i18n 10 (en / zh-CN, English default) · X-Lang error localization on the API

Repository Layout

next-chats/
├── src/
│ ├── NextChats.Core/ # Domain model, orchestration, engines, drivers (no ASP.NET dependency)
│ ├── NextChats.Infrastructure/ # EF Core data, caching, security services (swappable)
│ └── NextChats.Api/ # Web API (SSE streaming, admin, RBAC, audit, metrics, i18n)
├── samples/McpDemoServer/ # Demo MCP Server (Streamable HTTP)
├── e2e/ # Internal-auth E2E (mock auth center + PowerShell suite)
└── web/ # Vue 3 + Cordis frontend
├── src/i18n/ # en / zh dictionaries (per-domain locale files)
└── scripts/smoke*.mjs # End-to-end smoke scripts (Node fetch streaming)

Quick Start

# 1. Start the demo MCP Server (port 5300)
dotnet run --project samples/McpDemoServer -c Debug
# 2. Start the API (port 5210; DB is auto-created and seeded on first boot)
dotnet run --project src/NextChats.Api -c Debug
# 3. Start the frontend (Vite proxies /api → 5210)cd web && npm install && npm run dev
# Open http://localhost:5173 (seeded admin: admin / admin123)

Core Design

Authentication (default accounts + internal auth)

  • default(system accounts): local password accounts (PBKDF2-SHA256), created by an admin in User Management; the login page defaults to default.
  • Internal auth (acs / ucs…): admins maintain auth-center configs in Internal Auth Management — API URL, HTTP method, the request-body field names for the username/password (body(application/json)), success-response rules (one or more fields that must be not empty or equal a fixed value, dotted paths such as data.sessionID supported), and the category's default roles (multi-select).
  • The login page shows a sign-in-method radio group (default + enabled auth centers, from GET /api/auth/providers); choosing an internal method makes the backend validate the credentials against the configured auth center.
  • After a successful validation the user is auto-provisioned or reused: internal users are unique by (AuthType, Username) (a default and an acs account may share a username); when the user already exists their roles are read directly, otherwise a user is created (no password, displayName = username, status Active, bound to the provider's default roles).
  • Internal-auth users share the same JWT mechanism as default accounts: chat / catalog / permissions behave identically afterwards (role → model/MCP/Prompt/Skill bindings decide the visible scope).
  • Dual-token session (double-token): login issues a short-lived access token (30 min) plus a persisted refresh token (7 days, stored as SHA-256 hash). When the access token expires, the frontend silently exchanges the refresh token (POST /api/auth/refresh) and replays the request — active users are never logged out mid-work, idle sessions expire after 7 days. Every refresh rotates the token (the old one is revoked and recorded as replaced, so replays are rejected). Re-login, logout, and user disable revoke that user's refresh tokens immediately (a disabled account can no longer renew, and its access token dies within 30 min).
sequenceDiagram
participant U as User (Browser)
participant C as AuthController
participant P as InternalAuthProvider config
participant A as Auth Center (external)
participant DB as Users table
U->>C: POST /api/auth/login { authType: "acs", username, password }
C->>P: load provider (api / fields / success rules / default roles)
C->>A: HTTP POST api { username_field: …, password_field: … }
A-->>C: response JSON
C->>C: evaluate success rules (field not empty / equals value)
alt rule satisfied
C->>DB: find user by (AuthType=acs, Username)
alt not exists
C->>DB: auto-create (no password, default roles)
end
C-->>U: JWT (same mechanism as default accounts) + user info
else rule failed / auth center error
C-->>U: 401 (AUTH_INVALID_CREDENTIALS / AUTH_PROVIDER_ERROR)
end
Loading

Orchestration (Core)

  • Effective tool intersection: role binding ∩ user selection ∩ MCP global enabled per user, with data isolation.
  • Active Skill matching: Skills are exposed to the model as meta tools (skill_<slug>); instructions are lazy-loaded to avoid token explosion.
  • Prompt building: a template engine ({{var}} / #if / #each / #section) renders system prompts.
  • ReAct loop (AgentLoopEngine): producer–consumer Channel architecture decouples LLM streaming from the main loop; safe interruption.

Server-side Unified Configuration (Admin)

  • LLM providers (enable flag / model / endpoint / encrypted api-key / temperature / context-window)
  • MCP servers (Name / Endpoint / Headers JSON encrypted; "Fetch" auto-discovers tools/prompts/resources with schemas; per-item disable)
  • Multiple Prompts & Skills (pluggable, lazy-load)
  • RBAC: users / roles; role ↔ MCP / Prompt / Skill / LLM model bindings —— bound models are the only models the role's users can see/select, and direct API calls with an unauthorized model id are rejected server-side (MODEL_NOT_AUTHORIZED)
  • Internal Auth: auth-center configs (acs / ucs…) with success-response rules and default roles (see Authentication above)

Engines (Core)

  • LLM Router: multi-vendor priority + round-robin + failover (mark-unhealthy circuit breaking); OpenAI-compatible & Mock clients.
  • MCP driver: strictly follows the latest MCP spec (see MCP 2026-07 revision); connection pooling; call retry with backoff. MCP errors go into the loop — tool errors are fed back to the model as tool results for retry/explanation; sessions never break because of an MCP error.
  • Policy engine: Allow → Deny → RequireApproval with dangerous-operation heuristics (delete_all etc.); approvals support pending/approved/rejected/expired.
  • Context manager: token estimate → compress (LLM summary) → truncate; never exceeds the model length limit and never interrupts the session.

Observability & Cost

  • trace_id (trc_...) flows through Orchestration / LLM / MCP / Audit; token accounting in/out; metrics: TTFT, tokens, cost, tool latency, approval count (/api/admin/metrics/usage).
  • Idempotent writes: unique (UserId, ClientMessageId) key; key chat logs persist across restarts.

Logging, three channels

  • To the user: friendly message + error code (no stack / endpoint / header)
  • To the model: tool errors are re-fed as tool results (retryable, explainable)
  • To the logs: full context (sanitized)

Internationalization

  • Frontend: vue-i18n with per-domain locale files under web/src/i18n/locales/{en,zh}/; English is the default locale.
  • Backend: every user-visible message lives in one dictionary (src/NextChats.Core/Localization/Texts.cs, en/zh) — no hard-coded UI text in code. The API localizes errors via the X-Lang request header (or Accept-Language), so non-browser clients get the same language, e.g. curl -H "X-Lang: en"; seeded content (roles, prompts, skills, demo provider) is English.
  • README: this English file and README.zh.md are linked at the top.

Smoke Verification (web/scripts/ + e2e/)

node scripts/smoke.mjs # login → create session → SSE streaming chat → persistence → metrics
node scripts/smoke-tools.mjs # ReAct tool call (tool:echo) → dangerous approval flow (danger:delete_all)# Internal-auth E2E (mock auth center + PowerShell suite, see e2e/README.md)
node e2e/mock-auth.js # start mock auth center (127.0.0.1:53131)
powershell -File e2e/run-internal-auth-e2e.ps1 # assert CRUD / providers / auto-provision / uniqueness / role-based catalog

MCP References

About

What's not here is all in next ...

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Next Chats

中文版 README(简体中文)

Chat window

Chat window (conversation / thinking / tools / topic rail)


Admin console

Admin console (LLM providers / MCP / RBAC / internal auth / approvals / audit / usage)

A B/S multimodal AI chat platform based on the DeepSeek Harness principles: LLM + MCP (Model Context Protocol) + Skill plugin orchestration, with a Cordis-driven, Three.js-powered 3D frontend.

Reference paper: A Programming Paradigm for Spatiotemporal Composability — everything is a plugin, driven by Cordis.

Multilingual UI: English (default) and 简体中文 — switch from the top bar (🌐 EN / 中文); your choice is remembered in localStorage.


Architecture & Flows

Data Model (ER)

erDiagram
APP_USER {
Guid id PK
string auth_type "default=password account; acs/ucs…=internal auth"
string username "unique together with auth_type"
string display_name
string email
string password_hash
string password_salt
string status
datetime created_at
datetime last_login_at
}
APP_ROLE {
Guid id PK
string name
string code UK
string description
bool is_system
datetime created_at
}
CHAT_SESSION {
Guid id PK
Guid user_id FK
string title
string status
Guid llm_provider_id
string context_json
datetime created_at
datetime updated_at
datetime last_message_at
}
CHAT_MESSAGE {
Guid id PK
Guid session_id FK
Guid user_id FK
string role
text content
text reasoning
string tool_calls_json
string status
string model
int prompt_tokens
int completion_tokens
string trace_id
string client_message_id UK
datetime created_at
}
LLM_PROVIDER {
Guid id PK
string name
string kind
string base_url
string api_key_encrypted
int priority
bool enabled
bool is_healthy
datetime created_at
datetime updated_at
}
LLM_MODEL {
Guid id PK
Guid provider_id FK
string name
bool enabled
bool is_vision
int context_window
decimal price_in_per_1k
decimal price_out_per_1k
int priority
datetime created_at
}
INTERNAL_AUTH_PROVIDER {
Guid id PK
string name "acs / ucs…, unique"
string api "auth center URL"
string http_method
string request_format "body(application/json)"
string username_field
string password_field
bool enabled
int timeout_seconds
datetime created_at
}
INTERNAL_AUTH_SUCCESS_RULE {
Guid id PK
Guid provider_id FK
string field "JSON path, e.g. sessionID"
string operator "NotEmpty / Equals"
string expected_value
}
MCP_SERVER {
Guid id PK
string name
string transport
string endpoint
string headers_json
string stdio_command
bool enabled
bool is_vision
int timeout_seconds
string description
datetime created_at
}
MCP_CATALOG_ITEM {
Guid id PK
Guid mcp_server_id FK
string kind
string name
string description
string schema_json
bool enabled
}
PROMPT {
Guid id PK
string name
string description
text content
bool enabled
int version
datetime created_at
}
SKILL {
Guid id PK
string name
string meta_tool_name
text instruction
bool enabled
string model_override
int max_nested_steps
}
TOOL_APPROVAL {
Guid id PK
string trace_id
Guid user_id FK
Guid session_id FK
string mcp_server_name
string tool_name
string arguments_json
string status
datetime created_at
datetime decided_at
datetime expires_at
}
AUDIT_LOG {
Guid id PK
string trace_id
Guid user_id FK
string category
string action
string target
string detail_json
string ip
bool is_suspicious
datetime created_at
}
USER_SETTING {
Guid id PK
Guid user_id FK
string key
string value_json
}
TOKEN_USAGE_RECORD {
Guid id PK
string trace_id UK
Guid user_id FK
Guid session_id FK
string provider_name
string model
int total_tokens
decimal cost
int ttft_ms
datetime created_at
}
APP_USER ||--o{ CHAT_SESSION : "owns"
APP_USER ||--o{ CHAT_MESSAGE : "sends"
APP_USER }o--o{ APP_ROLE : "member_of (user_roles)"
APP_USER ||--o{ USER_SETTING : "has"
APP_ROLE }o--o{ MCP_SERVER : "can_use (role_mcp_servers)"
APP_ROLE }o--o{ PROMPT : "can_use (role_prompts)"
APP_ROLE }o--o{ SKILL : "can_use (role_skills)"
APP_ROLE }o--o{ LLM_MODEL : "can_use (role_model_bindings)"
APP_ROLE }o--o{ INTERNAL_AUTH_PROVIDER : "default roles (internal_auth_provider_roles)"
LLM_PROVIDER ||--o{ LLM_MODEL : "models"
INTERNAL_AUTH_PROVIDER ||--o{ INTERNAL_AUTH_SUCCESS_RULE : "success rules"
MCP_SERVER ||--o{ MCP_CATALOG_ITEM : "catalog"
CHAT_SESSION ||--o{ CHAT_MESSAGE : "contains"
CHAT_SESSION }o--o| LLM_PROVIDER : "uses"
TOOL_APPROVAL }o--|| APP_USER : "requested_by"
TOOL_APPROVAL }o--|| CHAT_SESSION : "in"
AUDIT_LOG }o--o| APP_USER : "actor"
TOKEN_USAGE_RECORD }o--o| APP_USER : "accrued_by"
Loading

System Architecture

flowchart LR
subgraph Web["Web (Vue 3 + Cordis)"]
UI[Chat UI / 3D Theme] --> K[Kernel Plugins]
K -->|SSE / REST| HTTP[Vite proxy → /api]
end
subgraph Api["NextChats.Api (ASP.NET Core)"]
Ctl[Controllers / RBAC / Audit] --> Mod[Error Localization Middleware]
Ctl --> Orch{{ChatOrchestrator}}
Ctl --> Adm[Admin: Providers / MCP / Prompts / Skills / RBAC / Internal Auth / Approvals]
Ctl --> Auth[Auth: default password / internal auth]
Auth -->|HTTP| IA[(Internal Auth Center acs / ucs…)]
end
Orch --> Loop{{AgentLoopEngine}}
Loop -->|ReAct think→act→observe| Router[LLM Router]
Router --> OpenAI[OpenAI-Compatible Client]
Router --> Mock[Mock Client: localized output]
Orch --> Driver[MCP Driver]
Driver --> MCP1[(MCP Server: Demo 5300)]
Driver --> MCP2[(MCP Server: more…)]
Orch --> Skills[Skill Execution Engine]
Orch --> Policy[Policy Engine / Approval Coordinator]
Api --> DB[(SQLite: config / chat / audit)]
UI --> V3[Three.js scenes]
style Orch fill:#e3f2fd
style Loop fill:#fff3e0
Loading

Chat (ReAct) Flow — SSE streaming

sequenceDiagram
participant U as User (Browser)
participant K as Cordis Kernel
participant C as ChatController
participant O as ChatOrchestrator
participant L as AgentLoopEngine
participant LLM as LLM Client
participant M as MCP Driver / Skills
U->>K: type message (+ optional images)
K->>C: POST /api/chat/stream (SSE)
C->>O: ChatStreamRequest (lang, images)
O-->>C: SESSION_NOT_FOUND? / INPUT_FLAGGED? (error event)
O->>L: AgentRunRequest (tools, context window)
loop ReAct round
L->>LLM: stream prompt + tools
LLM-->>L: reasoning / text / tool calls chunks
L-->>U: thinking_start · thinking_delta · text_delta (SSE)
alt tool call
L->>Policy: evaluate (allow/deny/approval)
Policy-->>U: approval_updated (pending) → user approves
L->>M: execute tool (approval / args)
M-->>L: tool result
L-->>U: tool_start · tool_result (SSE)
end
end
L-->>U: done (usage: tokens / ttft / cost)
O->>DB: persist assistant message + usage + audit (trace_id)
O-->>C: SSE end event
C-->>K: event stream → UI renders
Loading

Image / Vision Flow (image_source standard base64)

sequenceDiagram
participant U as User (Browser)
participant B as ChatInputBar
participant C as ChatController
participant O as ChatOrchestrator
participant V as Vision MCP Tool (image_source)
participant LLM as LLM (Mock / real)
participant P as Provider · MCP Vision Flags
U->>C: GET /api/chat/vision-config
C->>P: provider.IsVision ∨ bound MCP.IsVision
P-->>U: supported: true → show upload button + Ctrl+V
U->>B: paste (Ctrl+V) or upload N images
B->>B: validate type/size → dataURL → base64
B->>C: POST /api/chat/stream { message, images[] }
C->>O: ChatStreamRequest.Images (standard base64)
loop each image (multi-image recognized one by one)
O->>V: call vision tool { image_source: base64 }
V-->>O: recognition text (per image)
end
O->>LLM: system + history + [vision texts] + user(message + <image_source> blocks)
LLM-->>U: streamed answer (SSE) using the recognized text
Loading

Tech Stack

LayerTechnology
Backend.NET 10 · ASP.NET Core · EF Core (SQLite now → MySQL 8 ready)
MCPModelContextProtocol SDK 2.2.0 (latest stable) · Streamable HTTP transport · STDIO extensible
FrontendVue 3 · TypeScript · Vite · Element Plus · Three.js · Cordis ^3.18.1 (plugin kernel)
SecurityJWT dual-token (30-min access + rotating refresh, revoked on disable/logout) · PBKDF2-SHA256(210k) password hashing · AES-256-GCM secret encryption · sanitized audit logs
i18nvue-i18n 10 (en / zh-CN, English default) · X-Lang error localization on the API

Repository Layout

next-chats/
├── src/
│ ├── NextChats.Core/ # Domain model, orchestration, engines, drivers (no ASP.NET dependency)
│ ├── NextChats.Infrastructure/ # EF Core data, caching, security services (swappable)
│ └── NextChats.Api/ # Web API (SSE streaming, admin, RBAC, audit, metrics, i18n)
├── samples/McpDemoServer/ # Demo MCP Server (Streamable HTTP)
├── e2e/ # Internal-auth E2E (mock auth center + PowerShell suite)
└── web/ # Vue 3 + Cordis frontend
├── src/i18n/ # en / zh dictionaries (per-domain locale files)
└── scripts/smoke*.mjs # End-to-end smoke scripts (Node fetch streaming)

Quick Start

# 1. Start the demo MCP Server (port 5300)
dotnet run --project samples/McpDemoServer -c Debug
# 2. Start the API (port 5210; DB is auto-created and seeded on first boot)
dotnet run --project src/NextChats.Api -c Debug
# 3. Start the frontend (Vite proxies /api → 5210)cd web && npm install && npm run dev
# Open http://localhost:5173 (seeded admin: admin / admin123)

Core Design

Authentication (default accounts + internal auth)

  • default(system accounts): local password accounts (PBKDF2-SHA256), created by an admin in User Management; the login page defaults to default.
  • Internal auth (acs / ucs…): admins maintain auth-center configs in Internal Auth Management — API URL, HTTP method, the request-body field names for the username/password (body(application/json)), success-response rules (one or more fields that must be not empty or equal a fixed value, dotted paths such as data.sessionID supported), and the category's default roles (multi-select).
  • The login page shows a sign-in-method radio group (default + enabled auth centers, from GET /api/auth/providers); choosing an internal method makes the backend validate the credentials against the configured auth center.
  • After a successful validation the user is auto-provisioned or reused: internal users are unique by (AuthType, Username) (a default and an acs account may share a username); when the user already exists their roles are read directly, otherwise a user is created (no password, displayName = username, status Active, bound to the provider's default roles).
  • Internal-auth users share the same JWT mechanism as default accounts: chat / catalog / permissions behave identically afterwards (role → model/MCP/Prompt/Skill bindings decide the visible scope).
  • Dual-token session (double-token): login issues a short-lived access token (30 min) plus a persisted refresh token (7 days, stored as SHA-256 hash). When the access token expires, the frontend silently exchanges the refresh token (POST /api/auth/refresh) and replays the request — active users are never logged out mid-work, idle sessions expire after 7 days. Every refresh rotates the token (the old one is revoked and recorded as replaced, so replays are rejected). Re-login, logout, and user disable revoke that user's refresh tokens immediately (a disabled account can no longer renew, and its access token dies within 30 min).
sequenceDiagram
participant U as User (Browser)
participant C as AuthController
participant P as InternalAuthProvider config
participant A as Auth Center (external)
participant DB as Users table
U->>C: POST /api/auth/login { authType: "acs", username, password }
C->>P: load provider (api / fields / success rules / default roles)
C->>A: HTTP POST api { username_field: …, password_field: … }
A-->>C: response JSON
C->>C: evaluate success rules (field not empty / equals value)
alt rule satisfied
C->>DB: find user by (AuthType=acs, Username)
alt not exists
C->>DB: auto-create (no password, default roles)
end
C-->>U: JWT (same mechanism as default accounts) + user info
else rule failed / auth center error
C-->>U: 401 (AUTH_INVALID_CREDENTIALS / AUTH_PROVIDER_ERROR)
end
Loading

Orchestration (Core)

  • Effective tool intersection: role binding ∩ user selection ∩ MCP global enabled per user, with data isolation.
  • Active Skill matching: Skills are exposed to the model as meta tools (skill_<slug>); instructions are lazy-loaded to avoid token explosion.
  • Prompt building: a template engine ({{var}} / #if / #each / #section) renders system prompts.
  • ReAct loop (AgentLoopEngine): producer–consumer Channel architecture decouples LLM streaming from the main loop; safe interruption.

Server-side Unified Configuration (Admin)

  • LLM providers (enable flag / model / endpoint / encrypted api-key / temperature / context-window)
  • MCP servers (Name / Endpoint / Headers JSON encrypted; "Fetch" auto-discovers tools/prompts/resources with schemas; per-item disable)
  • Multiple Prompts & Skills (pluggable, lazy-load)
  • RBAC: users / roles; role ↔ MCP / Prompt / Skill / LLM model bindings —— bound models are the only models the role's users can see/select, and direct API calls with an unauthorized model id are rejected server-side (MODEL_NOT_AUTHORIZED)
  • Internal Auth: auth-center configs (acs / ucs…) with success-response rules and default roles (see Authentication above)

Engines (Core)

  • LLM Router: multi-vendor priority + round-robin + failover (mark-unhealthy circuit breaking); OpenAI-compatible & Mock clients.
  • MCP driver: strictly follows the latest MCP spec (see MCP 2026-07 revision); connection pooling; call retry with backoff. MCP errors go into the loop — tool errors are fed back to the model as tool results for retry/explanation; sessions never break because of an MCP error.
  • Policy engine: Allow → Deny → RequireApproval with dangerous-operation heuristics (delete_all etc.); approvals support pending/approved/rejected/expired.
  • Context manager: token estimate → compress (LLM summary) → truncate; never exceeds the model length limit and never interrupts the session.

Observability & Cost

  • trace_id (trc_...) flows through Orchestration / LLM / MCP / Audit; token accounting in/out; metrics: TTFT, tokens, cost, tool latency, approval count (/api/admin/metrics/usage).
  • Idempotent writes: unique (UserId, ClientMessageId) key; key chat logs persist across restarts.

Logging, three channels

  • To the user: friendly message + error code (no stack / endpoint / header)
  • To the model: tool errors are re-fed as tool results (retryable, explainable)
  • To the logs: full context (sanitized)

Internationalization

  • Frontend: vue-i18n with per-domain locale files under web/src/i18n/locales/{en,zh}/; English is the default locale.
  • Backend: every user-visible message lives in one dictionary (src/NextChats.Core/Localization/Texts.cs, en/zh) — no hard-coded UI text in code. The API localizes errors via the X-Lang request header (or Accept-Language), so non-browser clients get the same language, e.g. curl -H "X-Lang: en"; seeded content (roles, prompts, skills, demo provider) is English.
  • README: this English file and README.zh.md are linked at the top.

Smoke Verification (web/scripts/ + e2e/)

node scripts/smoke.mjs # login → create session → SSE streaming chat → persistence → metrics
node scripts/smoke-tools.mjs # ReAct tool call (tool:echo) → dangerous approval flow (danger:delete_all)# Internal-auth E2E (mock auth center + PowerShell suite, see e2e/README.md)
node e2e/mock-auth.js # start mock auth center (127.0.0.1:53131)
powershell -File e2e/run-internal-auth-e2e.ps1 # assert CRUD / providers / auto-provision / uniqueness / role-based catalog

MCP References

About

What's not here is all in next ...

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages