Completed
on 7 Jan 2026, 4:42 pm

Output

📄 File Viewer

Showing first 100 lines of 2,824 total. Click lines to select, use Copy button to copy content.

📘 konui/src/router.ts 2,824 lines · TypeScript
1/**
2 * Request routing for Konui.
3 * Handles both API (JSON) and HTML routes.
4 */
5 
6import { loadConfig, type KonuiConfig } from "./config.ts";
7import { requireAuth, getAuthUsername, getAuthRole, type AuthResult } from "./auth.ts";
8import {
9 createSession,
10 deleteSession,
11 getTokenFromHeader,
12 validateCredentials,
13} from "./session.ts";
14import { getLogger } from "./logger.ts";
15import type { KontaskStatus, KontaskType, CreateKontaskInput } from "./kontask-types.ts";
16import * as kontaskStore from "./kontask-db.ts";
17import * as reports from "./reports.ts";
18import { dashboardPage } from "./views/layout.ts";
19import { reportsListPage, reportDetailPage } from "./views/reports.ts";
20import { kontasksListPage } from "./views/kontasks.ts";
21import { kontaskDetailPage, kontaskNotFoundPage, type StalenessInfo } from "./views/kontask-detail.ts";
22import { logsPage } from "./views/logs.ts";
23import { loginPage } from "./views/login.ts";
24import { newKontaskPage } from "./views/kontask-new.ts";
25import { claudeConsolePage } from "./views/claude-console.ts";
26import { sessionsPage } from "./views/sessions.ts";
27import {
28 createSSEStream,
29 getLogTailer,
30 getLogChannel,
31 getCombinedLogTailer,
32 emitTaskEvent,
33 eventBus,
34 dashboardStreams,
35 formatSSE,
36 type SSEEvent,
37} from "./streaming.ts";
38import { getPlatformMetrics } from "./metrics.ts";
39import {
40 isKonsoleHealthy,
41 getOrCreateSession,
42 sendPromptToKonsole,
43 parseKonsoleStream,
44 stopKonsoleSession,
45 deleteKonsoleSession,
46 clearSessionFile,
47} from "./konsole-client.ts";
48 
49let configCache: KonuiConfig | null = null;
50 
51async function getConfig(): Promise<KonuiConfig> {
52 if (!configCache) {
53 configCache = await loadConfig();
54 }
55 return configCache;
56}
57 
58/**
59 * Get per-user konsole session file path.
60 * Each user gets their own session to prevent context mixing.
61 */
62function getKonsoleSessionFile(username: string): string {
63 // Sanitize username for filesystem safety
64 const safeUsername = username.replace(/[^a-zA-Z0-9_-]/g, "_") || "anonymous";
65 return `/konnectvol/konui/data/konsole-sessions/${safeUsername}.txt`;
66}
67 
68/**
69 * Get per-user Claude console session file path (for /claude "Go" button).
70 * Each user gets their own session to prevent context mixing.
71 */
72function getClaudeSessionFile(username: string): string {
73 const safeUsername = username.replace(/[^a-zA-Z0-9_-]/g, "_") || "anonymous";
74 return `/konnectvol/konui/data/claude-sessions/${safeUsername}.txt`;
75}
76 
77/**
78 * Get per-user Claude console history directory (for /claude "Go" button).
79 */
80function getClaudeHistoryDir(username: string): string {
81 const safeUsername = username.replace(/[^a-zA-Z0-9_-]/g, "_") || "anonymous";
82 return `/konnectvol/konui/data/claude-history/${safeUsername}`;
83}
84 
85/**
86 * Get per-user dashboard console session file path (legacy direct Claude).
87 */
88function getDashboardSessionFile(username: string): string {
89 const safeUsername = username.replace(/[^a-zA-Z0-9_-]/g, "_") || "anonymous";
90 return `/konnectvol/konui/data/dashboard-sessions/${safeUsername}.txt`;
91}
92 
93/**
94 * Get per-user dashboard console history directory (legacy direct Claude).
95 */
96function getDashboardHistoryDir(username: string): string {
97 const safeUsername = username.replace(/[^a-zA-Z0-9_-]/g, "_") || "anonymous";
98 return `/konnectvol/konui/data/dashboard-history/${safeUsername}`;
99}
100 

📊 File Summary

Total Lines2,824
File Size102 KB
LanguageTypeScript
PurposeMain request router

Highlighted lines 6-13 show the main imports.

Quick Actions

Original Request

No layout configured

Details

Type General
Status Completed
Scope vibetools
Tags kontasksuicomponentsfile-viewer
Created 7 Jan 2026, 4:42 pm
Updated 7 Jan 2026, 4:42 pm
Timing
⏱️ 110.8s
0 tools
TTFE 0% Thinking 100% Tools 0%
Created By claude

Raw Data

{
  "id": "02601ef2-335c-4083-a1af-f9060ac42ffa",
  "type": "general",
  "status": "completed",
  "title": "File: konui/src/router.ts",
  "description": "FileViewer showing router.ts with syntax highlighting",
  "context": {
    "output": "<div style=\"background:#1e1e1e;color:#e0e0e0;padding:20px;border-radius:8px;font-family:sans-serif;\">\n<h2 style=\"margin:0 0 8px 0;color:#fff;\">📄 File Viewer</h2>\n<p style=\"color:#9ca3af;margin-bottom:16px;\">Showing first 100 lines of 2,824 total. Click lines to select, use Copy button to copy content.</p>\n\n<div class=\"file-viewer\" id=\"router-viewer\">\n  <div class=\"fv-header\">\n    <div class=\"fv-file-info\">\n      <span class=\"fv-icon\">📘</span>\n      <span class=\"fv-path\">konui/src/router.ts</span>\n      <span class=\"fv-meta\">2,824 lines · TypeScript</span>\n    </div>\n    <div class=\"fv-actions\">\n      <button class=\"fv-btn fv-copy-btn\" onclick=\"copyFileContent_router_viewer()\">📋 Copy</button>\n    </div>\n  </div>\n  <div class=\"fv-content\" style=\"max-height: 500px;\">\n    <div class=\"fv-lines\">\n      <div class=\"fv-line\" data-line=\"1\"><span class=\"fv-line-num\">1</span><span class=\"fv-line-content\"><span class=\"fv-comment\">/**</span></span></div>\n      <div class=\"fv-line\" data-line=\"2\"><span class=\"fv-line-num\">2</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> * Request routing for Konui.</span></span></div>\n      <div class=\"fv-line\" data-line=\"3\"><span class=\"fv-line-num\">3</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> * Handles both API (JSON) and HTML routes.</span></span></div>\n      <div class=\"fv-line\" data-line=\"4\"><span class=\"fv-line-num\">4</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> */</span></span></div>\n      <div class=\"fv-line\" data-line=\"5\"><span class=\"fv-line-num\">5</span><span class=\"fv-line-content\">&nbsp;</span></div>\n      <div class=\"fv-line highlighted\" data-line=\"6\"><span class=\"fv-line-num\">6</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { loadConfig, <span class=\"fv-keyword\">type</span> KonuiConfig } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./config.ts\"</span>;</span></div>\n      <div class=\"fv-line highlighted\" data-line=\"7\"><span class=\"fv-line-num\">7</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { requireAuth, getAuthUsername, getAuthRole, <span class=\"fv-keyword\">type</span> AuthResult } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./auth.ts\"</span>;</span></div>\n      <div class=\"fv-line highlighted\" data-line=\"8\"><span class=\"fv-line-num\">8</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> {</span></div>\n      <div class=\"fv-line highlighted\" data-line=\"9\"><span class=\"fv-line-num\">9</span><span class=\"fv-line-content\">  createSession,</span></div>\n      <div class=\"fv-line highlighted\" data-line=\"10\"><span class=\"fv-line-num\">10</span><span class=\"fv-line-content\">  deleteSession,</span></div>\n      <div class=\"fv-line highlighted\" data-line=\"11\"><span class=\"fv-line-num\">11</span><span class=\"fv-line-content\">  getTokenFromHeader,</span></div>\n      <div class=\"fv-line highlighted\" data-line=\"12\"><span class=\"fv-line-num\">12</span><span class=\"fv-line-content\">  validateCredentials,</span></div>\n      <div class=\"fv-line highlighted\" data-line=\"13\"><span class=\"fv-line-num\">13</span><span class=\"fv-line-content\">} <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./session.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"14\"><span class=\"fv-line-num\">14</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { getLogger } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./logger.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"15\"><span class=\"fv-line-num\">15</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> <span class=\"fv-keyword\">type</span> { KontaskStatus, KontaskType, CreateKontaskInput } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./kontask-types.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"16\"><span class=\"fv-line-num\">16</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> * <span class=\"fv-keyword\">as</span> kontaskStore <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./kontask-db.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"17\"><span class=\"fv-line-num\">17</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> * <span class=\"fv-keyword\">as</span> reports <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./reports.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"18\"><span class=\"fv-line-num\">18</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { dashboardPage } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./views/layout.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"19\"><span class=\"fv-line-num\">19</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { reportsListPage, reportDetailPage } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./views/reports.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"20\"><span class=\"fv-line-num\">20</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { kontasksListPage } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./views/kontasks.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"21\"><span class=\"fv-line-num\">21</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { kontaskDetailPage, kontaskNotFoundPage, <span class=\"fv-keyword\">type</span> StalenessInfo } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./views/kontask-detail.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"22\"><span class=\"fv-line-num\">22</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { logsPage } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./views/logs.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"23\"><span class=\"fv-line-num\">23</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { loginPage } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./views/login.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"24\"><span class=\"fv-line-num\">24</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { newKontaskPage } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./views/kontask-new.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"25\"><span class=\"fv-line-num\">25</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { claudeConsolePage } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./views/claude-console.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"26\"><span class=\"fv-line-num\">26</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { sessionsPage } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./views/sessions.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"27\"><span class=\"fv-line-num\">27</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> {</span></div>\n      <div class=\"fv-line\" data-line=\"28\"><span class=\"fv-line-num\">28</span><span class=\"fv-line-content\">  createSSEStream,</span></div>\n      <div class=\"fv-line\" data-line=\"29\"><span class=\"fv-line-num\">29</span><span class=\"fv-line-content\">  getLogTailer,</span></div>\n      <div class=\"fv-line\" data-line=\"30\"><span class=\"fv-line-num\">30</span><span class=\"fv-line-content\">  getLogChannel,</span></div>\n      <div class=\"fv-line\" data-line=\"31\"><span class=\"fv-line-num\">31</span><span class=\"fv-line-content\">  getCombinedLogTailer,</span></div>\n      <div class=\"fv-line\" data-line=\"32\"><span class=\"fv-line-num\">32</span><span class=\"fv-line-content\">  emitTaskEvent,</span></div>\n      <div class=\"fv-line\" data-line=\"33\"><span class=\"fv-line-num\">33</span><span class=\"fv-line-content\">  eventBus,</span></div>\n      <div class=\"fv-line\" data-line=\"34\"><span class=\"fv-line-num\">34</span><span class=\"fv-line-content\">  dashboardStreams,</span></div>\n      <div class=\"fv-line\" data-line=\"35\"><span class=\"fv-line-num\">35</span><span class=\"fv-line-content\">  formatSSE,</span></div>\n      <div class=\"fv-line\" data-line=\"36\"><span class=\"fv-line-num\">36</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">type</span> SSEEvent,</span></div>\n      <div class=\"fv-line\" data-line=\"37\"><span class=\"fv-line-num\">37</span><span class=\"fv-line-content\">} <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./streaming.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"38\"><span class=\"fv-line-num\">38</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> { getPlatformMetrics } <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./metrics.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"39\"><span class=\"fv-line-num\">39</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">import</span> {</span></div>\n      <div class=\"fv-line\" data-line=\"40\"><span class=\"fv-line-num\">40</span><span class=\"fv-line-content\">  isKonsoleHealthy,</span></div>\n      <div class=\"fv-line\" data-line=\"41\"><span class=\"fv-line-num\">41</span><span class=\"fv-line-content\">  getOrCreateSession,</span></div>\n      <div class=\"fv-line\" data-line=\"42\"><span class=\"fv-line-num\">42</span><span class=\"fv-line-content\">  sendPromptToKonsole,</span></div>\n      <div class=\"fv-line\" data-line=\"43\"><span class=\"fv-line-num\">43</span><span class=\"fv-line-content\">  parseKonsoleStream,</span></div>\n      <div class=\"fv-line\" data-line=\"44\"><span class=\"fv-line-num\">44</span><span class=\"fv-line-content\">  stopKonsoleSession,</span></div>\n      <div class=\"fv-line\" data-line=\"45\"><span class=\"fv-line-num\">45</span><span class=\"fv-line-content\">  deleteKonsoleSession,</span></div>\n      <div class=\"fv-line\" data-line=\"46\"><span class=\"fv-line-num\">46</span><span class=\"fv-line-content\">  clearSessionFile,</span></div>\n      <div class=\"fv-line\" data-line=\"47\"><span class=\"fv-line-num\">47</span><span class=\"fv-line-content\">} <span class=\"fv-keyword\">from</span> <span class=\"fv-string\">\"./konsole-client.ts\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"48\"><span class=\"fv-line-num\">48</span><span class=\"fv-line-content\">&nbsp;</span></div>\n      <div class=\"fv-line\" data-line=\"49\"><span class=\"fv-line-num\">49</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">let</span> configCache: KonuiConfig | <span class=\"fv-keyword\">null</span> = <span class=\"fv-keyword\">null</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"50\"><span class=\"fv-line-num\">50</span><span class=\"fv-line-content\">&nbsp;</span></div>\n      <div class=\"fv-line\" data-line=\"51\"><span class=\"fv-line-num\">51</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">async</span> <span class=\"fv-keyword\">function</span> <span class=\"fv-function\">getConfig</span>(): Promise&lt;KonuiConfig&gt; {</span></div>\n      <div class=\"fv-line\" data-line=\"52\"><span class=\"fv-line-num\">52</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">if</span> (!configCache) {</span></div>\n      <div class=\"fv-line\" data-line=\"53\"><span class=\"fv-line-num\">53</span><span class=\"fv-line-content\">    configCache = <span class=\"fv-keyword\">await</span> loadConfig();</span></div>\n      <div class=\"fv-line\" data-line=\"54\"><span class=\"fv-line-num\">54</span><span class=\"fv-line-content\">  }</span></div>\n      <div class=\"fv-line\" data-line=\"55\"><span class=\"fv-line-num\">55</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">return</span> configCache;</span></div>\n      <div class=\"fv-line\" data-line=\"56\"><span class=\"fv-line-num\">56</span><span class=\"fv-line-content\">}</span></div>\n      <div class=\"fv-line\" data-line=\"57\"><span class=\"fv-line-num\">57</span><span class=\"fv-line-content\">&nbsp;</span></div>\n      <div class=\"fv-line\" data-line=\"58\"><span class=\"fv-line-num\">58</span><span class=\"fv-line-content\"><span class=\"fv-comment\">/**</span></span></div>\n      <div class=\"fv-line\" data-line=\"59\"><span class=\"fv-line-num\">59</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> * Get per-user konsole session file path.</span></span></div>\n      <div class=\"fv-line\" data-line=\"60\"><span class=\"fv-line-num\">60</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> * Each user gets their own session to prevent context mixing.</span></span></div>\n      <div class=\"fv-line\" data-line=\"61\"><span class=\"fv-line-num\">61</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> */</span></span></div>\n      <div class=\"fv-line\" data-line=\"62\"><span class=\"fv-line-num\">62</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">function</span> <span class=\"fv-function\">getKonsoleSessionFile</span>(username: <span class=\"fv-type\">string</span>): <span class=\"fv-type\">string</span> {</span></div>\n      <div class=\"fv-line\" data-line=\"63\"><span class=\"fv-line-num\">63</span><span class=\"fv-line-content\">  <span class=\"fv-comment\">// Sanitize username for filesystem safety</span></span></div>\n      <div class=\"fv-line\" data-line=\"64\"><span class=\"fv-line-num\">64</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">const</span> safeUsername = username.replace(/[^a-zA-Z<span class=\"fv-number\">0</span>-<span class=\"fv-number\">9</span>_-]/g, <span class=\"fv-string\">\"_\"</span>) || <span class=\"fv-string\">\"anonymous\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"65\"><span class=\"fv-line-num\">65</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">return</span> <span class=\"fv-string\">`/konnectvol/konui/data/konsole-sessions/${safeUsername}.txt`</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"66\"><span class=\"fv-line-num\">66</span><span class=\"fv-line-content\">}</span></div>\n      <div class=\"fv-line\" data-line=\"67\"><span class=\"fv-line-num\">67</span><span class=\"fv-line-content\">&nbsp;</span></div>\n      <div class=\"fv-line\" data-line=\"68\"><span class=\"fv-line-num\">68</span><span class=\"fv-line-content\"><span class=\"fv-comment\">/**</span></span></div>\n      <div class=\"fv-line\" data-line=\"69\"><span class=\"fv-line-num\">69</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> * Get per-user Claude console session file path (for /claude \"Go\" button).</span></span></div>\n      <div class=\"fv-line\" data-line=\"70\"><span class=\"fv-line-num\">70</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> * Each user gets their own session to prevent context mixing.</span></span></div>\n      <div class=\"fv-line\" data-line=\"71\"><span class=\"fv-line-num\">71</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> */</span></span></div>\n      <div class=\"fv-line\" data-line=\"72\"><span class=\"fv-line-num\">72</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">function</span> <span class=\"fv-function\">getClaudeSessionFile</span>(username: <span class=\"fv-type\">string</span>): <span class=\"fv-type\">string</span> {</span></div>\n      <div class=\"fv-line\" data-line=\"73\"><span class=\"fv-line-num\">73</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">const</span> safeUsername = username.replace(/[^a-zA-Z<span class=\"fv-number\">0</span>-<span class=\"fv-number\">9</span>_-]/g, <span class=\"fv-string\">\"_\"</span>) || <span class=\"fv-string\">\"anonymous\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"74\"><span class=\"fv-line-num\">74</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">return</span> <span class=\"fv-string\">`/konnectvol/konui/data/claude-sessions/${safeUsername}.txt`</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"75\"><span class=\"fv-line-num\">75</span><span class=\"fv-line-content\">}</span></div>\n      <div class=\"fv-line\" data-line=\"76\"><span class=\"fv-line-num\">76</span><span class=\"fv-line-content\">&nbsp;</span></div>\n      <div class=\"fv-line\" data-line=\"77\"><span class=\"fv-line-num\">77</span><span class=\"fv-line-content\"><span class=\"fv-comment\">/**</span></span></div>\n      <div class=\"fv-line\" data-line=\"78\"><span class=\"fv-line-num\">78</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> * Get per-user Claude console history directory (for /claude \"Go\" button).</span></span></div>\n      <div class=\"fv-line\" data-line=\"79\"><span class=\"fv-line-num\">79</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> */</span></span></div>\n      <div class=\"fv-line\" data-line=\"80\"><span class=\"fv-line-num\">80</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">function</span> <span class=\"fv-function\">getClaudeHistoryDir</span>(username: <span class=\"fv-type\">string</span>): <span class=\"fv-type\">string</span> {</span></div>\n      <div class=\"fv-line\" data-line=\"81\"><span class=\"fv-line-num\">81</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">const</span> safeUsername = username.replace(/[^a-zA-Z<span class=\"fv-number\">0</span>-<span class=\"fv-number\">9</span>_-]/g, <span class=\"fv-string\">\"_\"</span>) || <span class=\"fv-string\">\"anonymous\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"82\"><span class=\"fv-line-num\">82</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">return</span> <span class=\"fv-string\">`/konnectvol/konui/data/claude-history/${safeUsername}`</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"83\"><span class=\"fv-line-num\">83</span><span class=\"fv-line-content\">}</span></div>\n      <div class=\"fv-line\" data-line=\"84\"><span class=\"fv-line-num\">84</span><span class=\"fv-line-content\">&nbsp;</span></div>\n      <div class=\"fv-line\" data-line=\"85\"><span class=\"fv-line-num\">85</span><span class=\"fv-line-content\"><span class=\"fv-comment\">/**</span></span></div>\n      <div class=\"fv-line\" data-line=\"86\"><span class=\"fv-line-num\">86</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> * Get per-user dashboard console session file path (legacy direct Claude).</span></span></div>\n      <div class=\"fv-line\" data-line=\"87\"><span class=\"fv-line-num\">87</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> */</span></span></div>\n      <div class=\"fv-line\" data-line=\"88\"><span class=\"fv-line-num\">88</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">function</span> <span class=\"fv-function\">getDashboardSessionFile</span>(username: <span class=\"fv-type\">string</span>): <span class=\"fv-type\">string</span> {</span></div>\n      <div class=\"fv-line\" data-line=\"89\"><span class=\"fv-line-num\">89</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">const</span> safeUsername = username.replace(/[^a-zA-Z<span class=\"fv-number\">0</span>-<span class=\"fv-number\">9</span>_-]/g, <span class=\"fv-string\">\"_\"</span>) || <span class=\"fv-string\">\"anonymous\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"90\"><span class=\"fv-line-num\">90</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">return</span> <span class=\"fv-string\">`/konnectvol/konui/data/dashboard-sessions/${safeUsername}.txt`</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"91\"><span class=\"fv-line-num\">91</span><span class=\"fv-line-content\">}</span></div>\n      <div class=\"fv-line\" data-line=\"92\"><span class=\"fv-line-num\">92</span><span class=\"fv-line-content\">&nbsp;</span></div>\n      <div class=\"fv-line\" data-line=\"93\"><span class=\"fv-line-num\">93</span><span class=\"fv-line-content\"><span class=\"fv-comment\">/**</span></span></div>\n      <div class=\"fv-line\" data-line=\"94\"><span class=\"fv-line-num\">94</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> * Get per-user dashboard console history directory (legacy direct Claude).</span></span></div>\n      <div class=\"fv-line\" data-line=\"95\"><span class=\"fv-line-num\">95</span><span class=\"fv-line-content\"><span class=\"fv-comment\"> */</span></span></div>\n      <div class=\"fv-line\" data-line=\"96\"><span class=\"fv-line-num\">96</span><span class=\"fv-line-content\"><span class=\"fv-keyword\">function</span> <span class=\"fv-function\">getDashboardHistoryDir</span>(username: <span class=\"fv-type\">string</span>): <span class=\"fv-type\">string</span> {</span></div>\n      <div class=\"fv-line\" data-line=\"97\"><span class=\"fv-line-num\">97</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">const</span> safeUsername = username.replace(/[^a-zA-Z<span class=\"fv-number\">0</span>-<span class=\"fv-number\">9</span>_-]/g, <span class=\"fv-string\">\"_\"</span>) || <span class=\"fv-string\">\"anonymous\"</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"98\"><span class=\"fv-line-num\">98</span><span class=\"fv-line-content\">  <span class=\"fv-keyword\">return</span> <span class=\"fv-string\">`/konnectvol/konui/data/dashboard-history/${safeUsername}`</span>;</span></div>\n      <div class=\"fv-line\" data-line=\"99\"><span class=\"fv-line-num\">99</span><span class=\"fv-line-content\">}</span></div>\n      <div class=\"fv-line\" data-line=\"100\"><span class=\"fv-line-num\">100</span><span class=\"fv-line-content\">&nbsp;</span></div>\n    </div>\n  </div>\n  <div class=\"fv-footer\">\n    <span class=\"fv-scroll-hint\">Showing lines 1-100 of 2,824 ↕</span>\n  </div>\n</div>\n\n<script>\n(function() {\n  const viewer = document.getElementById('router-viewer');\n  if (!viewer) return;\n  \n  const content = viewer.querySelector('.fv-content');\n  const footer = viewer.querySelector('.fv-footer');\n  \n  function updateScrollHint() {\n    if (content && footer) {\n      const atBottom = content.scrollTop + content.clientHeight >= content.scrollHeight - 10;\n      footer.style.opacity = atBottom ? '0.5' : '1';\n    }\n  }\n  if (content) content.addEventListener('scroll', updateScrollHint);\n  \n  window.copyFileContent_router_viewer = function() {\n    const lines = viewer.querySelectorAll('.fv-line-content');\n    const text = Array.from(lines).map(l => l.textContent || '').join('\\n');\n    navigator.clipboard.writeText(text).then(() => {\n      const btn = viewer.querySelector('.fv-copy-btn');\n      if (btn) {\n        const orig = btn.textContent;\n        btn.textContent = '✓ Copied!';\n        setTimeout(() => btn.textContent = orig, 2000);\n      }\n    });\n  };\n  \n  viewer.querySelectorAll('.fv-line').forEach(line => {\n    line.addEventListener('click', () => line.classList.toggle('selected'));\n  });\n  \n  console.log('[FileViewer] router.ts initialized');\n})();\n</script>\n\n<style>\n.file-viewer { background: #1e1e1e; border-radius: 8px; overflow: hidden; margin: 12px 0; font-family: 'Consolas', 'Monaco', 'Fira Code', monospace; font-size: 0.85rem; }\n.fv-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; background: #2d2d2d; border-bottom: 1px solid #3d3d3d; }\n.fv-file-info { display: flex; align-items: center; gap: 8px; overflow: hidden; }\n.fv-icon { font-size: 1rem; flex-shrink: 0; }\n.fv-path { color: #e0e0e0; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.fv-meta { color: #7f848e; font-size: 0.8rem; white-space: nowrap; }\n.fv-actions { display: flex; gap: 8px; flex-shrink: 0; }\n.fv-btn { background: #3d3d3d; border: none; color: #e0e0e0; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 0.8rem; transition: background 0.15s; }\n.fv-btn:hover { background: #4d4d4d; }\n.fv-content { overflow: auto; background: #1e1e1e; max-height: 500px; }\n.fv-lines { display: table; width: 100%; padding: 8px 0; }\n.fv-line { display: table-row; line-height: 1.5; }\n.fv-line:hover { background: rgba(255, 255, 255, 0.05); }\n.fv-line.highlighted { background: rgba(201, 149, 66, 0.2); }\n.fv-line.highlighted:hover { background: rgba(201, 149, 66, 0.3); }\n.fv-line.selected { background: rgba(97, 175, 239, 0.2); }\n.fv-line-num { display: table-cell; width: 50px; padding: 0 16px; text-align: right; color: #5c6370; user-select: none; border-right: 1px solid #3d3d3d; }\n.fv-line-content { display: table-cell; padding: 0 16px; white-space: pre; color: #abb2bf; }\n.fv-footer { padding: 8px 16px; background: #2d2d2d; border-top: 1px solid #3d3d3d; text-align: center; }\n.fv-scroll-hint { color: #7f848e; font-size: 0.75rem; }\n.fv-keyword { color: #c678dd; }\n.fv-string { color: #98c379; }\n.fv-comment { color: #5c6370; font-style: italic; }\n.fv-number { color: #d19a66; }\n.fv-function { color: #61afef; }\n.fv-type { color: #e5c07b; }\n</style>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-top:16px;\">\n<h4 style=\"margin:0 0 12px 0;color:#fff;\">📊 File Summary</h4>\n<table style=\"width:100%;color:#9ca3af;font-size:0.9rem;\">\n<tr><td style=\"padding:4px 0;\">Total Lines</td><td style=\"text-align:right;\"><strong style=\"color:#fff;\">2,824</strong></td></tr>\n<tr><td style=\"padding:4px 0;\">File Size</td><td style=\"text-align:right;\"><strong style=\"color:#fff;\">102 KB</strong></td></tr>\n<tr><td style=\"padding:4px 0;\">Language</td><td style=\"text-align:right;\"><strong style=\"color:#fff;\">TypeScript</strong></td></tr>\n<tr><td style=\"padding:4px 0;\">Purpose</td><td style=\"text-align:right;\"><strong style=\"color:#fff;\">Main request router</strong></td></tr>\n</table>\n<p style=\"color:#7f848e;margin:12px 0 0 0;font-size:0.85rem;\">Highlighted lines 6-13 show the main imports.</p>\n</div>\n</div>",
    "requestedAt": "2026-01-07T22:18:00.000Z",
    "requestId": "46293268-6ede-4816-a5eb-789f4ba6bbc7",
    "choices": [
      {
        "label": "View more lines",
        "value": "Show lines 100-200 of konui/src/router.ts"
      },
      {
        "label": "Back to tree",
        "value": "Show the konui file tree again"
      },
      {
        "label": "View another file",
        "value": "Show file: konui/src/main.ts"
      }
    ],
    "turnTiming": {
      "totalMs": 110750,
      "ttfeMs": 417,
      "thinkingMs": 110333,
      "toolExecutionMs": 0,
      "toolCallCount": 0,
      "thinkingPct": 100,
      "toolsPct": 0,
      "ttfePct": 0
    }
  },
  "createdBy": "claude",
  "createdAt": "2026-01-07T06:42:07.506Z",
  "updatedAt": "2026-01-07T06:42:14.348Z",
  "requestId": "46293268-6ede-4816-a5eb-789f4ba6bbc7",
  "scope": "vibetools",
  "tags": [
    "kontasks",
    "ui",
    "components",
    "file-viewer"
  ],
  "targetUser": "claude"
}
DashboardReportsKontasksFlowsDecisionsSessionsTelemetryLogs + Go