From 240a4776f605f5bcf7dfaa9640877fbc5e579b1f Mon Sep 17 00:00:00 2001 From: "Claude (Chronicler #82)" Date: Sun, 12 Apr 2026 02:14:12 -0500 Subject: [PATCH] The Forge module + collapsible sidebar nav New Module: The Forge (/admin/forge) - AI knowledge assistant powered by Gemma 4 via Dify RAG - Streaming SSE chat interface with markdown rendering - Think-tag filtering for Gemma 4's reasoning tokens - Conversation continuity via Dify conversation IDs - Source citations from knowledge base documents - Fire/Frost/Arcane gradient branding - Welcome screen with suggestion buttons - Env vars: DIFY_API_URL, DIFY_APP_KEY Sidebar Navigation Overhaul (layout.ejs) - The Forge featured prominently at top with gradient border - Collapsible category groups: Core, Revenue, Community, Operations - localStorage persistence for collapsed/expanded state - CSS transitions for smooth collapse animation Chronicler #82 | April 12, 2026 --- .../arbiter-3.0/src/routes/admin/forge.js | 82 +++++ .../arbiter-3.0/src/routes/admin/index.js | 2 + .../src/views/admin/forge/index.ejs | 285 ++++++++++++++++++ services/arbiter-3.0/src/views/layout.ejs | 159 ++++++---- 4 files changed, 474 insertions(+), 54 deletions(-) create mode 100644 services/arbiter-3.0/src/routes/admin/forge.js create mode 100644 services/arbiter-3.0/src/views/admin/forge/index.ejs diff --git a/services/arbiter-3.0/src/routes/admin/forge.js b/services/arbiter-3.0/src/routes/admin/forge.js new file mode 100644 index 0000000..0d53c99 --- /dev/null +++ b/services/arbiter-3.0/src/routes/admin/forge.js @@ -0,0 +1,82 @@ +const express = require('express'); +const router = express.Router(); + +/** + * The Forge — AI Knowledge Assistant + * Trinity Console module for querying Firefrost documentation + * via Dify RAG + Gemma 4 on TX1 + * + * Chronicler #82 | April 12, 2026 + */ + +const DIFY_API_URL = process.env.DIFY_API_URL || 'http://38.68.14.26:5001'; +const DIFY_APP_KEY = process.env.DIFY_APP_KEY || 'app-forge-trinity-console-key'; + +// GET /admin/forge — The Forge chat interface +router.get('/', (req, res) => { + res.render('admin/forge/index', { + title: 'The Forge', + currentPath: '/forge', + adminUser: req.user + }); +}); + +// POST /admin/forge/chat — Proxy to Dify streaming API +router.post('/chat', async (req, res) => { + const { query, conversation_id } = req.body; + + if (!query || !query.trim()) { + return res.status(400).json({ error: 'Query is required' }); + } + + const userId = `trinity-${req.user?.id || 'unknown'}`; + + try { + // Set up SSE headers + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + res.setHeader('X-Accel-Buffering', 'no'); + + const response = await fetch(`${DIFY_API_URL}/v1/chat-messages`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${DIFY_APP_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + inputs: {}, + query: query.trim(), + response_mode: 'streaming', + conversation_id: conversation_id || '', + user: userId + }) + }); + + if (!response.ok) { + const errText = await response.text(); + console.error('[Forge] Dify API error:', response.status, errText); + res.write(`data: ${JSON.stringify({ event: 'error', message: 'The Forge is temporarily unavailable.' })}\n\n`); + return res.end(); + } + + // Pipe the SSE stream from Dify to the client + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + const chunk = decoder.decode(value, { stream: true }); + res.write(chunk); + } + + res.end(); + } catch (err) { + console.error('[Forge] Chat error:', err); + res.write(`data: ${JSON.stringify({ event: 'error', message: 'Connection to The Forge failed.' })}\n\n`); + res.end(); + } +}); + +module.exports = router; diff --git a/services/arbiter-3.0/src/routes/admin/index.js b/services/arbiter-3.0/src/routes/admin/index.js index e3a4624..0c2f2d3 100644 --- a/services/arbiter-3.0/src/routes/admin/index.js +++ b/services/arbiter-3.0/src/routes/admin/index.js @@ -22,6 +22,7 @@ const infrastructureRouter = require('./infrastructure'); const aboutRouter = require('./about'); const mcpLogsRouter = require('./mcp-logs'); const tasksRouter = require('./tasks'); +const forgeRouter = require('./forge'); router.use(requireTrinityAccess); @@ -131,5 +132,6 @@ router.use('/infrastructure', infrastructureRouter); router.use('/about', aboutRouter); router.use('/mcp-logs', mcpLogsRouter); router.use('/tasks', tasksRouter); +router.use('/forge', forgeRouter); module.exports = router; diff --git a/services/arbiter-3.0/src/views/admin/forge/index.ejs b/services/arbiter-3.0/src/views/admin/forge/index.ejs new file mode 100644 index 0000000..4fca067 --- /dev/null +++ b/services/arbiter-3.0/src/views/admin/forge/index.ejs @@ -0,0 +1,285 @@ + + +
+
+
+

🔥 The Forge

+

Ask me anything about Firefrost Gaming — infrastructure, procedures, server configs, policies, or history.

+

Powered by Gemma 4 on TX1 • Responses may take 30-60 seconds

+
+ + + + +
+
+
+ +
+ + +
+
+
+ + diff --git a/services/arbiter-3.0/src/views/layout.ejs b/services/arbiter-3.0/src/views/layout.ejs index 72f829a..6e21da6 100644 --- a/services/arbiter-3.0/src/views/layout.ejs +++ b/services/arbiter-3.0/src/views/layout.ejs @@ -54,6 +54,8 @@ display: block; } } + .nav-group { transition: all 0.2s ease; overflow: hidden; } + .nav-collapsed { max-height: 0; opacity: 0; margin: 0; padding: 0; } @@ -71,65 +73,87 @@
@@ -168,5 +192,32 @@
+