# How the CyberStrikeAI Web Console Delivers an Interactive Experience

> Discover how the CyberStrikeAI web console provides an interactive experience using a vanilla JavaScript SPA architecture with WebSocket terminals and SSE for AI responses.

- Repository: [公明/CyberStrikeAI](https://github.com/Ed1s0nZ/CyberStrikeAI)
- Tags: architecture
- Published: 2026-03-09

---

**The CyberStrikeAI web console achieves its interactive, desktop-like experience through a vanilla JavaScript SPA architecture that combines hash-based routing, WebSocket terminal sessions, Server-Sent Event streaming for AI responses, and dynamic UI components like @-mention tool pickers and draft persistence.**

The CyberStrikeAI project (Ed1s0nZ/CyberStrikeAI) provides a browser-based security operations interface built as a single-page application (SPA). This architecture eliminates full page reloads while enabling real-time terminal access, progressive AI chat rendering, and dynamic tool selection directly within the browser.

## Hash-Based SPA Routing for Instant Navigation

The console’s navigation system resides in [`web/static/js/router.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/web/static/js/router.js) and orchestrates page transitions through URL hash manipulation. When the document fires `DOMContentLoaded`, the `initRouter()` function (lines 5-38) parses `window.location.hash` to extract page identifiers and query parameters.

The `switchPage(pageId)` method hides all `.page` sections, activates the target pane (`#page-${pageId}`), and delegates to page-specific initializers like `initChatPage()` or `initTerminal()`. Because navigation relies solely on hash changes, the browser never triggers HTTP requests for new pages, creating an instantaneous transition feel that mimics native desktop software.

## Real-Time Terminal via WebSocket

The terminal component creates an interactive shell inside the browser using a bidirectional WebSocket connection defined in [`web/static/js/terminal.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/web/static/js/terminal.js). The `buildTerminalWSURL()` function (lines 75-84) dynamically constructs the WebSocket URL based on the current protocol—using `wss://` for HTTPS and `ws://` for HTTP—while appending authentication tokens from `localStorage`:

```javascript
function buildTerminalWSURL() {
    const proto = (window.location.protocol === 'https:') ? 'wss://' : 'ws://';
    let url = proto + window.location.host + '/api/terminal/ws';
    const token = getStoredAuthToken();
    if (token) url += '?token=' + encodeURIComponent(token);
    return url;
}

```

On the server side, [`internal/handler/terminal_ws_unix.go`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/internal/handler/terminal_ws_unix.go) spawns the requested shell process, streams stdout/stderr through the WebSocket, and enforces timeouts via `terminalTimeout`. Client-side, incoming frames are written directly to an **xterm.js** instance using `term.write()`, providing a fully functional terminal emulator within the web interface.

## Streaming AI Output with Server-Sent Events

The chat interface in [`web/static/js/chat.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/web/static/js/chat.js) consumes AI-generated content through HTTP streaming rather than waiting for complete responses. The client initiates a `fetch` request to `/api/agent-loop/stream` and processes the response body as a `ReadableStream` using `response.body.getReader()`.

The backend returns Server-Sent Events (SSE) where each line begins with `data:` followed by JSON payloads. The client loop (around lines 191-225) concatenates partial chunks, splits on newlines, and parses each event through `handleStreamEvent()`. This architecture supports:

- **Real-time progress bars** via `addProgressMessage()`
- **MCP execution tracking** with clickable "调用 #n" buttons that trigger `showMCPDetail(execId)`
- **Graceful cancellation** when the server aborts long-running tool calls

## @-Mention Tool Picker for Dynamic Tool Selection

When users type "@" in the chat input, `handleChatInputInput` activates a mention state that triggers `updateMentionStateFromInput`. The system lazily loads tool metadata through `ensureMentionToolsLoaded()`, which calls `fetchMentionTools()` to paginate through `/api/config/tools` (page size 100).

The implementation merges internal and external MCP tools, deduplicates them using composite keys like `external_mcp::tool.name`, and filters by the `roleEnabled` flag to highlight tools available for the current user role. The `renderMentionSuggestions()` function (lines 551-581) generates a dropdown of `<button>` elements with keyboard navigation (arrow keys, Enter/Tab) and injects selected tool names directly into the textarea.

## File Upload and Draft Persistence

The console protects user work through intelligent draft management and supports rich file attachments. The `setupChatFileUpload()` function in [`chat.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/chat.js) handles both hidden file inputs and drag-and-drop interfaces, reading files via `FileReader` as either text or base64 data URLs depending on MIME type.

Draft persistence operates through debounced `localStorage` writes:

- `saveChatDraftDebounced()` stores textarea content under the key `cyberstrike-chat-draft`
- `restoreChatDraft()` retrieves content on page initialization only if the input is empty and no recent message was sent
- `clearChatDraft()` removes stored data after successful message transmission

This ensures users never lose unsent work while preventing the accidental restoration of placeholder text.

## Responsive Sidebar and Navigation State

The sidebar component provides collapsible navigation with state persistence across sessions. The `toggleSidebar()` function saves the collapsed state to `localStorage` under `sidebarCollapsed`, while `updateNavState()` automatically expands sub-menus (MCP, Knowledge, Skills, Roles) when their child pages become active.

When collapsed, clicking top-level navigation items triggers `showSubmenuPopup()` to display floating menu overlays, maintaining full navigation capabilities even in compact mode. All state transitions are handled within [`router.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/router.js) (lines 75-126 and 166-200) without server round-trips.

## Summary

- **Hash-based routing** in [`web/static/js/router.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/web/static/js/router.js) enables instant page transitions without HTTP reloads by manipulating `window.location.hash` and switching visibility of `.page` sections.
- **WebSocket terminal** integration provides a true interactive shell through [`web/static/js/terminal.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/web/static/js/terminal.js) and the Go handler [`internal/handler/terminal_ws_unix.go`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/internal/handler/terminal_ws_unix.go), supporting both `ws://` and `wss://` protocols with token authentication.
- **SSE streaming** delivers progressive AI output via `fetch()` and `ReadableStream` processing in [`web/static/js/chat.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/web/static/js/chat.js), enabling real-time progress bars and execution status updates.
- **@-mention system** offers dynamic tool selection with role-aware filtering, pagination, and keyboard navigation through functions like `ensureMentionToolsLoaded()` and `renderMentionSuggestions()`.
- **Draft persistence** safeguards user input using debounced `localStorage` operations (`saveChatDraftDebounced()`, `restoreChatDraft()`), while file upload handlers support drag-and-drop attachment with automatic MIME-type detection.
- **Responsive sidebar** maintains navigation state in `localStorage` and adapts its presentation between expanded and collapsed modes without page refreshes.

## Frequently Asked Questions

### How does the CyberStrikeAI console handle page navigation without reloading?

The console implements a hash-based router in [`web/static/js/router.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/web/static/js/router.js) that intercepts all navigation through `window.location.hash` changes. The `initRouter()` function parses hash values to identify pages, calls `switchPage()` to toggle visibility of HTML sections, and invokes page-specific initializers like `initChatPage()`. Because the browser remains on the same document, transitions occur instantly without network latency or rendering delays.

### What technology enables the interactive terminal inside the browser?

The terminal uses native WebSocket connections managed by [`web/static/js/terminal.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/web/static/js/terminal.js) and the Go backend [`internal/handler/terminal_ws_unix.go`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/internal/handler/terminal_ws_unix.go). The client constructs protocol-aware URLs (`wss://` or `ws://`) with authentication tokens, then streams user input through `ws.send()` while writing server responses into an **xterm.js** terminal instance. The server spawns actual shell processes and pipes their stdout/stderr through the WebSocket frame by frame.

### How does the chat interface stream AI responses in real-time?

The chat module consumes Server-Sent Events (SSE) from the `/api/agent-loop/stream` endpoint using the Streams API. The client reads the response body through a `ReadableStream` reader, decodes chunks with `TextDecoder`, and parses lines beginning with `data:` as JSON events. The `handleStreamEvent()` function processes these increments to update progress bars, append text to message bubbles, and display MCP execution IDs as they arrive from the backend.

### Can the console restore unsent messages if the browser is refreshed?

Yes, the implementation in [`web/static/js/chat.js`](https://github.com/Ed1s0nZ/CyberStrikeAI/blob/main/web/static/js/chat.js) automatically persists chat input using `saveChatDraftDebounced()`, which writes to `localStorage` under the key `cyberstrike-chat-draft` after a debounce delay. On initialization, `restoreChatDraft()` retrieves this content only if the textarea is empty and no message was recently sent, preventing the accidental display of stale data while protecting unsent work across browser sessions.