Chris4K commited on
Commit
3856547
Β·
verified Β·
1 Parent(s): a225dc6

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1158 -19
index.html CHANGED
@@ -1,19 +1,1158 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>⚑ Chronos V3 β€” Kitchen Sink</title>
7
+
8
+ <!-- ═══ EXTERNAL LIBRARIES ═══ -->
9
+ <script src="https://cdn.jsdelivr.net/npm/@mlc-ai/web-llm@0.2.46/lib/index.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/pyodide@0.24.1/pyodide.js"></script>
13
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css">
14
+ <script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
15
+
16
+ <link rel="preconnect" href="https://fonts.googleapis.com">
17
+ <link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@400;700;900&family=VT323&display=swap" rel="stylesheet">
18
+
19
+ <style>
20
+ :root{--bg:#080b08;--bg2:#0c100c;--bg3:#111611;--bg4:#161e16;--fg:#3dff70;--fg-dim:#1c8038;--fg-faint:#0b2e16;--amber:#ffbb33;--cyan:#33f0ff;--red:#ff3355;--border:#1a2e1a;--glow:0 0 8px #3dff7066,0 0 24px #3dff7022;--scan:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,.15) 2px,rgba(0,0,0,.15) 4px)}
21
+ *{box-sizing:border-box;margin:0;padding:0}
22
+ html,body{height:100%;background:var(--bg);color:var(--fg);font-family:'Share Tech Mono',monospace;font-size:13px;overflow:hidden}
23
+ body::before{content:'';position:fixed;inset:0;background:var(--scan);pointer-events:none;z-index:9999;opacity:.3}
24
+
25
+ /* SETUP BANNER */
26
+ #setup-banner{position:fixed;inset:0;background:#000000ee;z-index:99999;display:flex;align-items:center;justify-content:center;padding:20px}
27
+ #setup-banner.hidden{display:none}
28
+ .sbox{background:var(--bg2);border:1px solid var(--amber);max-width:580px;width:100%;font-size:12px;line-height:1.7}
29
+ .sbox h1{font-family:'Orbitron';font-size:13px;color:var(--amber);padding:14px 18px;border-bottom:1px solid var(--border);letter-spacing:2px}
30
+ .sbox .bd{padding:16px 18px;color:var(--fg-dim)}
31
+ .sbox code{color:var(--cyan);background:var(--bg3);padding:1px 5px}
32
+ .cmd-row{background:var(--bg3);border:1px solid var(--border);padding:10px 14px;margin:8px 0;color:var(--fg);cursor:pointer;display:flex;justify-content:space-between}
33
+ .sbtn-main{width:100%;background:var(--fg-faint);border:none;border-top:1px solid var(--border);color:var(--fg);font-family:'Orbitron';font-size:10px;letter-spacing:2px;padding:12px;cursor:pointer}
34
+
35
+ /* LAYOUT */
36
+ #app{display:grid;grid-template-rows:44px 1fr;grid-template-columns:300px 1fr 240px;height:100vh;gap:1px;background:var(--border)}
37
+ @media(max-width:1100px){#app{grid-template-columns:240px 1fr}#rpanel{display:none}}
38
+ @media(max-width:768px){#app{grid-template-columns:1fr}#sidebar,#rpanel{display:none}}
39
+ #hdr{grid-column:1/-1;background:var(--bg2);display:flex;align-items:center;justify-content:space-between;padding:0 14px;border-bottom:1px solid var(--border)}
40
+ .logo{font-family:'Orbitron';font-weight:900;font-size:16px;color:var(--fg);text-shadow:var(--glow);letter-spacing:2px}
41
+ .hpills{display:flex;gap:8px;align-items:center}
42
+ .hpill{font-size:9px;letter-spacing:1px;padding:3px 8px;border:1px solid var(--border);color:var(--fg-dim);font-family:'Orbitron'}
43
+ .dot{width:7px;height:7px;border-radius:50%;background:var(--red);box-shadow:0 0 5px var(--red);display:inline-block;margin-right:5px;transition:all .3s}
44
+ .dot.ready{background:var(--fg);box-shadow:0 0 6px var(--fg);animation:pulse 2s infinite}
45
+ @keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
46
+
47
+ /* SIDEBAR */
48
+ #sidebar{background:var(--bg2);display:flex;flex-direction:column;overflow-y:auto}
49
+ .stitle{font-family:'Orbitron';font-size:8px;letter-spacing:3px;color:var(--fg-dim);padding:7px 10px 5px;border-bottom:1px solid var(--border);text-transform:uppercase;background:var(--bg3)}
50
+ .sbody{padding:8px 10px;border-bottom:1px solid var(--border)}
51
+ select.ctrl,input.ctrl{width:100%;background:var(--bg3);border:1px solid var(--border);color:var(--fg);font-family:'Share Tech Mono';font-size:10px;padding:5px 7px;outline:none;margin-bottom:4px}
52
+ .btn{width:100%;background:transparent;border:1px solid var(--fg-dim);color:var(--fg);font-family:'Share Tech Mono';font-size:11px;padding:6px;cursor:pointer;letter-spacing:1px;margin-top:3px}
53
+ .btn:hover:not(:disabled){background:var(--fg-faint);border-color:var(--fg);box-shadow:var(--glow)}
54
+ .btn:disabled{opacity:.35;cursor:not-allowed}
55
+ .bxs{background:transparent;border:1px solid var(--border);color:var(--fg-dim);font-family:'Share Tech Mono';font-size:9px;padding:2px 7px;cursor:pointer}
56
+ .bxs:hover{border-color:var(--fg-dim);color:var(--fg)}
57
+ .meta{font-size:9px;color:var(--fg-dim);margin:3px 0}
58
+ .frow{display:flex;align-items:center;gap:5px;padding:3px 0;font-size:11px}
59
+ .fname{color:var(--cyan);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
60
+ .fbadge{font-size:9px;color:var(--fg-dim);min-width:44px;text-align:right}
61
+ .chips{display:flex;flex-wrap:wrap;gap:3px;margin-top:4px;min-height:18px}
62
+ .chip{font-size:9px;color:var(--cyan);border:1px solid var(--fg-faint);padding:2px 6px;display:flex;align-items:center;gap:4px}
63
+ .chip .rm{cursor:pointer;color:var(--red);font-size:12px;line-height:1}
64
+ input[type=file]{display:none}
65
+
66
+ /* CHAT */
67
+ #chat{background:var(--bg);display:flex;flex-direction:column;overflow:hidden;position:relative}
68
+ #chat-hdr{padding:6px 14px;border-bottom:1px solid var(--border);background:var(--bg2);display:flex;align-items:center;justify-content:space-between;font-size:10px;color:var(--fg-dim)}
69
+ #msgs{flex:1;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:10px}
70
+ #msgs::-webkit-scrollbar{width:3px}
71
+ #msgs::-webkit-scrollbar-thumb{background:var(--fg-dim)}
72
+ #msgs.dragover{outline:2px dashed var(--cyan);outline-offset:-8px}
73
+
74
+ #irow{background:var(--bg2);border-top:1px solid var(--border);display:flex;align-items:stretch}
75
+ .ipfx{padding:0 10px;color:var(--fg);font-size:15px;font-family:'VT323';opacity:.6;display:flex;align-items:center}
76
+ #uin{flex:1;background:transparent;border:none;color:var(--fg);font-family:'Share Tech Mono';font-size:14px;padding:12px 10px;resize:vertical;outline:none;line-height:1.5;min-height:40px;max-height:300px}
77
+ #sbtn{width:72px;background:transparent;border:none;border-left:1px solid var(--border);color:var(--fg-dim);font-family:'Orbitron';font-size:8px;letter-spacing:2px;cursor:pointer}
78
+ #sbtn:hover:not(:disabled){background:var(--fg-faint);color:var(--fg)}
79
+ #sbtn:disabled{opacity:.3;cursor:not-allowed}
80
+ #voice-btn{width:36px;background:transparent;border:none;border-left:1px solid var(--border);color:var(--cyan);font-size:16px;cursor:pointer}
81
+ #voice-btn.listening{color:var(--red);animation:pulse 1s infinite}
82
+
83
+ .mwrap{display:flex;gap:10px;animation:fi .2s ease}
84
+ @keyframes fi{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
85
+ .mrole{font-family:'Orbitron';font-size:8px;letter-spacing:2px;width:50px;flex-shrink:0;padding-top:2px;text-align:right}
86
+ .mwrap.user .mrole{color:var(--amber)}
87
+ .mwrap.agent .mrole{color:var(--fg)}
88
+ .mbody{flex:1;line-height:1.65;font-size:12px}
89
+ .mwrap.user .mbody{color:var(--amber)}
90
+
91
+ .answer{color:var(--fg);white-space:pre-wrap;line-height:1.7;font-size:12px}
92
+ .answer pre{background:var(--bg3);border:1px solid var(--border);padding:8px;margin:6px 0;overflow-x:auto;color:var(--cyan);font-size:10px}
93
+ .answer code{background:var(--bg3);color:var(--cyan);padding:1px 4px}
94
+ .answer strong{color:var(--amber)}
95
+ .answer em{color:var(--fg-dim);font-style:italic}
96
+
97
+ /* TOOL CARDS */
98
+ .tcard{border:1px solid var(--border);margin-bottom:6px}
99
+ .tcard-hdr{background:var(--bg3);padding:5px 8px;display:flex;align-items:center;gap:6px;font-size:10px;cursor:pointer}
100
+ .tname{color:var(--amber);font-family:'Orbitron';font-size:8px;letter-spacing:2px}
101
+ .tparams{color:var(--fg-dim);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:9px}
102
+ .tres{padding:6px 8px;font-size:10px;color:var(--fg-dim);max-height:130px;overflow-y:auto;white-space:pre-wrap;line-height:1.4;border-top:1px solid var(--border);background:var(--bg);display:none}
103
+ .tres.vis{display:block}
104
+
105
+ /* MERMAID */
106
+ .mermaid-container{background:var(--bg3);border:1px solid var(--cyan);padding:12px;margin:8px 0;overflow-x:auto}
107
+ .mermaid-container svg{max-width:100%;height:auto}
108
+
109
+ /* KNOWLEDGE GRAPH */
110
+ #graph-container{width:100%;height:180px;background:var(--bg);border:1px solid var(--border);position:relative;overflow:hidden}
111
+ #graph-container svg{width:100%;height:100%}
112
+
113
+ /* RAG */
114
+ #rag-panel .rag-stats{font-size:9px;color:var(--fg-dim);margin-bottom:4px;line-height:1.6}
115
+ #rag-text{width:100%;background:var(--bg3);border:1px solid var(--border);color:var(--fg);font-family:'Share Tech Mono';font-size:10px;padding:5px 7px;resize:vertical;outline:none;min-height:50px;max-height:100px;margin-bottom:4px}
116
+ #rag-query{width:100%;background:var(--bg3);border:1px solid var(--border);color:var(--fg);font-family:'Share Tech Mono';font-size:10px;padding:5px 7px;outline:none;margin-bottom:4px}
117
+ #rag-results{max-height:120px;overflow-y:auto;font-size:9px;color:var(--fg-dim);line-height:1.5;margin-top:4px}
118
+ .rag-passage{border-left:2px solid var(--cyan);padding:3px 6px;margin-bottom:4px;background:var(--bg3)}
119
+ .rag-btn-row{display:flex;gap:4px;margin-bottom:4px;flex-wrap:wrap}
120
+
121
+ /* RIGHT PANEL */
122
+ #rpanel{background:var(--bg2);display:flex;flex-direction:column;overflow:hidden}
123
+ .sblk{padding:8px 10px;border-bottom:1px solid var(--border)}
124
+ .slbl{font-size:8px;letter-spacing:2px;color:var(--fg-dim);font-family:'Orbitron';margin-bottom:3px}
125
+ .sval{font-family:'VT323';font-size:26px;color:var(--fg);text-shadow:var(--glow);line-height:1}
126
+ canvas#spark{width:100%;height:28px;display:block}
127
+ #alog{flex:1;overflow-y:auto;padding:8px 10px;font-size:9px;color:var(--fg-dim);line-height:1.8}
128
+ .ll{border-left:2px solid var(--fg-faint);padding-left:5px;margin-bottom:3px}
129
+ .ll.g{border-color:var(--fg);color:var(--fg)}
130
+ .ll.w{border-color:var(--amber);color:var(--amber)}
131
+ .ll.e{border-color:var(--red);color:var(--red)}
132
+ .ll.t{border-color:var(--cyan);color:var(--cyan)}
133
+
134
+ /* COMMAND PALETTE */
135
+ #cmd-palette{display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:480px;max-width:90vw;max-height:60vh;background:var(--bg2);border:1px solid var(--fg);z-index:10002;flex-direction:column}
136
+ #cmd-palette.vis{display:flex}
137
+ #cmd-palette input{background:var(--bg3);border:none;border-bottom:1px solid var(--border);color:var(--fg);font-family:'Share Tech Mono';font-size:13px;padding:10px 14px;outline:none}
138
+ .cp-list{flex:1;overflow-y:auto;max-height:300px}
139
+ .cp-item{padding:8px 14px;cursor:pointer;font-size:11px;color:var(--fg-dim);border-bottom:1px solid var(--border);display:flex;gap:8px;align-items:center}
140
+ .cp-item:hover,.cp-item.sel{background:var(--fg-faint);color:var(--fg)}
141
+ .cp-badge{font-size:8px;color:var(--amber);font-family:'Orbitron';letter-spacing:1px;min-width:50px}
142
+
143
+ /* CONSOLE POPUP */
144
+ #console-popup{display:none;position:fixed;bottom:0;right:0;width:55vw;max-width:800px;height:50vh;background:rgba(8,11,8,.92);border:1px solid var(--cyan);border-bottom:none;z-index:10001;flex-direction:column;font-family:'Share Tech Mono';font-size:10px;backdrop-filter:blur(6px)}
145
+ #console-popup.vis{display:flex}
146
+ .cp-hdr{padding:6px 10px;background:var(--bg3);display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border)}
147
+ .cp-hdr span{font-family:'Orbitron';font-size:8px;letter-spacing:2px;color:var(--cyan)}
148
+ .cp-body{flex:1;overflow-y:auto;padding:6px 10px}
149
+ .clog-line{padding:1px 0;border-bottom:1px solid #0a150a;white-space:pre-wrap;word-break:break-all}
150
+ .clog-line.log{color:var(--fg-dim)}
151
+ .clog-line.warn{color:var(--amber)}
152
+ .clog-line.error{color:var(--red)}
153
+ .clog-line.info{color:var(--cyan)}
154
+
155
+ /* MODAL */
156
+ #modal{display:none;position:fixed;inset:0;background:#000c;z-index:10000;align-items:center;justify-content:center}
157
+ #modal.vis{display:flex}
158
+ .modal-box{background:var(--bg2);border:1px solid var(--border);width:800px;max-width:92vw;max-height:88vh;display:flex;flex-direction:column}
159
+ .modal-hdr{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}
160
+ .modal-hdr span{font-family:'Orbitron';font-size:10px;letter-spacing:2px;color:var(--fg)}
161
+ .modal-hdr button{background:none;border:none;color:var(--fg-dim);cursor:pointer;font-size:18px}
162
+ #modal-area{flex:1;background:var(--bg3);border:none;color:var(--fg);font-family:'Share Tech Mono';font-size:11px;padding:12px;resize:none;outline:none;min-height:400px;line-height:1.6}
163
+ .modal-ftr{padding:8px 14px;border-top:1px solid var(--border);display:flex;gap:8px}
164
+
165
+ /* EASYMDE */
166
+ .EasyMDEContainer{width:100%;height:100%;display:flex;flex-direction:column}
167
+ .EasyMDEContainer .CodeMirror{background:var(--bg3)!important;color:var(--fg)!important;border:1px solid var(--border)!important;font-family:'Share Tech Mono'!important;font-size:12px!important;flex:1;height:auto!important}
168
+ .EasyMDEContainer .CodeMirror-cursor{border-left-color:var(--fg)!important}
169
+ .EasyMDEContainer .editor-toolbar{background:var(--bg2)!important;border:1px solid var(--border)!important;border-bottom:none!important}
170
+ .EasyMDEContainer .editor-toolbar button{color:var(--fg-dim)!important;background:transparent!important;border:none!important}
171
+ .EasyMDEContainer .editor-toolbar button:hover{color:var(--fg)!important;background:var(--fg-faint)!important}
172
+ .EasyMDEContainer .editor-preview{background:var(--bg)!important;color:var(--fg)!important;font-family:'Share Tech Mono'!important}
173
+ .EasyMDEContainer .editor-statusbar{display:none}
174
+
175
+ /* TOAST */
176
+ #toast-area{position:fixed;top:50px;left:50%;transform:translateX(-50%);z-index:10003;display:flex;flex-direction:column;gap:8px;pointer-events:none;align-items:center}
177
+ .toast{background:var(--bg2);border:2px solid var(--amber);padding:14px 24px;font-size:14px;color:var(--amber);animation:toastin .4s ease;max-width:500px;text-align:center;font-family:'Orbitron';letter-spacing:1px}
178
+ @keyframes toastin{from{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:none}}
179
+ </style>
180
+ </head>
181
+ <body>
182
+
183
+ <div id="setup-banner">
184
+ <div class="sbox">
185
+ <h1>⚠ SERVE VIA HTTP REQUIRED</h1>
186
+ <div class="bd">
187
+ WebLLM needs <code>SharedArrayBuffer</code>. Serve via:<br>
188
+ <div class="cmd-row" onclick="navigator.clipboard?.writeText('python proxy.py 8080')"><span>python proxy.py 8080</span><span style="color:var(--fg-dim)">click to copy</span></div>
189
+ Then open: <code>http://127.0.0.1:8080/htmlClaw_V3.html</code>
190
+ </div>
191
+ <button class="sbtn-main" onclick="document.getElementById('setup-banner').classList.add('hidden')">DISMISS β†’</button>
192
+ </div>
193
+ </div>
194
+
195
+ <div id="app">
196
+ <header id="hdr">
197
+ <div class="logo">⚑ CHRONOS <span style="color:var(--fg-dim);font-size:9px">V3 · KITCHEN SINK</span></div>
198
+ <div class="hpills">
199
+ <span class="hpill"><span class="dot" id="mdot"></span><span id="mstatus">NO MODEL</span></span>
200
+ <span class="hpill" id="tools-pill">TOOLS: 0</span>
201
+ <span class="hpill" id="ctx-pill" style="color:var(--cyan)">CTX: 0%</span>
202
+ </div>
203
+ </header>
204
+
205
+ <aside id="sidebar">
206
+ <div class="stitle">βš™ Model</div>
207
+ <div class="sbody">
208
+ <select class="ctrl" id="msel">
209
+ <option value="Llama-3.2-1B-Instruct-q4f16_1-MLC">Llama 3.2 Β· 1B (fast)</option>
210
+ <option value="Llama-3.2-3B-Instruct-q4f16_1-MLC">Llama 3.2 Β· 3B</option>
211
+ <option value="Phi-3.5-mini-instruct-q4f16_1-MLC">Phi-3.5 Β· 3.8B</option>
212
+ <option value="Llama-3.1-8B-Instruct-q4f16_1-MLC">Llama 3.1 Β· 8B</option>
213
+ <option value="Mistral-7B-Instruct-v0.3-q4f16_1-MLC">Mistral Β· 7B</option>
214
+ </select>
215
+ <button class="btn" id="lbtn">β–Ά LOAD MODEL</button>
216
+ <div class="meta" style="margin-top:6px">BRAVE API KEY:</div>
217
+ <input class="ctrl" type="password" id="brave-in" placeholder="BSA...">
218
+ <button class="bxs" onclick="localStorage.setItem('chronos_brave_key',document.getElementById('brave-in').value);log('key saved','g')">SAVE</button>
219
+ </div>
220
+
221
+ <div class="stitle">πŸ“ Memory Files</div>
222
+ <div class="sbody">
223
+ <div class="frow"><span class="fname" id="soul-n">soul.md</span><span class="fbadge" id="soul-b">default</span><button class="bxs" onclick="MemoryModule.trigger('soul')">LOAD</button><button class="bxs" onclick="openEdit('soul')">EDIT</button><input type="file" id="soul-f" accept=".md,.txt"></div>
224
+ <div class="frow"><span class="fname" id="user-n">user.md</span><span class="fbadge" id="user-b">default</span><button class="bxs" onclick="MemoryModule.trigger('user')">LOAD</button><button class="bxs" onclick="openEdit('user')">EDIT</button><input type="file" id="user-f" accept=".md,.txt"></div>
225
+ </div>
226
+
227
+ <div class="stitle">⚑ Skills</div>
228
+ <div class="sbody">
229
+ <div class="frow" style="justify-content:space-between"><span style="font-size:10px;color:var(--fg-dim)">Dynamic injection</span><button class="bxs" onclick="MemoryModule.trigger('skill')">+ SKILL</button><input type="file" id="skill-f" accept=".md,.txt" multiple></div>
230
+ <div class="chips" id="skill-chips"></div>
231
+ </div>
232
+
233
+ <div class="stitle">πŸ”Ž RAG</div>
234
+ <div class="sbody" id="rag-panel">
235
+ <div class="rag-stats">Sentences: <span style="color:var(--cyan)" id="rag-count">0</span></div>
236
+ <textarea id="rag-text" placeholder="Paste text to index..."></textarea>
237
+ <div class="rag-btn-row">
238
+ <button class="bxs" onclick="RAGModule.index()">INDEX</button>
239
+ <button class="bxs" onclick="RAGModule.clear()">CLEAR</button>
240
+ </div>
241
+ <input id="rag-query" placeholder="Search query...">
242
+ <div class="rag-btn-row">
243
+ <button class="bxs" onclick="RAGModule.search()">SEARCH</button>
244
+ <button class="bxs" onclick="RAGModule.inject()">β†’ PROMPT</button>
245
+ </div>
246
+ <div id="rag-results"></div>
247
+ </div>
248
+
249
+ <div class="stitle">πŸ•Έ Knowledge Graph</div>
250
+ <div class="sbody">
251
+ <div id="graph-container"></div>
252
+ <div class="meta" id="graph-stats">0 nodes Β· 0 edges</div>
253
+ </div>
254
+
255
+ <div class="stitle">πŸ’Ύ State</div>
256
+ <div class="sbody">
257
+ <button class="btn" onclick="ExportModule.exportAll()">EXPORT ALL</button>
258
+ <button class="btn" onclick="FileSystemModule.pickFolder()">SET WORKSPACE</button>
259
+ <button class="btn" onclick="FileSystemModule.saveToWorkspace('report.md','Agent Report')">SAVE REPORT</button>
260
+ </div>
261
+ </aside>
262
+
263
+ <main id="chat">
264
+ <div id="chat-hdr">
265
+ <span id="ctitle" style="color:var(--fg)">⚑ Chronos V3</span>
266
+ <div style="display:flex;gap:8px"><button class="bxs" onclick="document.getElementById('msgs').innerHTML='';store.set({messages:[]})">CLR</button></div>
267
+ </div>
268
+ <div id="msgs"></div>
269
+ <div id="irow">
270
+ <span class="ipfx">&gt;_</span>
271
+ <textarea id="uin" placeholder="Message Chronos... (↑ palette, drop images)" rows="1" disabled></textarea>
272
+ <button id="voice-btn" title="Voice input">πŸŽ™οΈ</button>
273
+ <button id="sbtn" disabled>SEND</button>
274
+ </div>
275
+ </main>
276
+
277
+ <aside id="rpanel">
278
+ <div class="stitle">πŸ“Š Stats</div>
279
+ <div class="sblk"><div class="slbl">TOOLS USED</div><div class="sval" id="s-tools">0</div></div>
280
+ <div class="sblk"><div class="slbl">TPS</div><div class="sval" id="s-tps">β€”</div></div>
281
+ <div class="sblk"><div class="slbl">CONTEXT</div><div class="sval" id="s-ctx" style="font-size:14px">0%</div></div>
282
+ <div class="sblk"><div class="slbl">TPS HISTORY</div><canvas id="spark" width="180" height="28"></canvas></div>
283
+ <div class="stitle">πŸ“‹ Agent Log</div>
284
+ <div id="alog"></div>
285
+ </aside>
286
+ </div>
287
+
288
+ <!-- COMMAND PALETTE -->
289
+ <div id="cmd-palette">
290
+ <input id="cp-search" placeholder="Type to filter... (Esc to close)">
291
+ <div class="cp-list" id="cp-list"></div>
292
+ </div>
293
+
294
+ <!-- CONSOLE POPUP -->
295
+ <div id="console-popup">
296
+ <div class="cp-hdr"><span>CONSOLE (Ctrl+`)</span><button class="bxs" onclick="ConsoleModule.toggle()">βœ•</button></div>
297
+ <div class="cp-body" id="cp-body"></div>
298
+ </div>
299
+
300
+ <!-- MODAL -->
301
+ <div id="modal">
302
+ <div class="modal-box">
303
+ <div class="modal-hdr"><span id="modal-title">EDIT</span><button onclick="closeModal()">βœ•</button></div>
304
+ <textarea id="modal-area"></textarea>
305
+ <div class="modal-ftr">
306
+ <button class="btn" style="width:auto;padding:6px 20px" onclick="saveModal()">SAVE</button>
307
+ <button class="bxs" onclick="closeModal()">CANCEL</button>
308
+ </div>
309
+ </div>
310
+ </div>
311
+
312
+ <div id="toast-area"></div>
313
+
314
+ <script>
315
+ // ═══════════════════════════════════════════════════════════
316
+ // CORE: STORE, EVENT BUS, TOOL REGISTRY, UI
317
+ // ═══════════════════════════════════════════════════════════
318
+ class Store {
319
+ constructor(state) { this.state = state; this.listeners = new Set(); }
320
+ subscribe(fn) { this.listeners.add(fn); fn(this.state); return () => this.listeners.delete(fn); }
321
+ set(updates) { this.state = { ...this.state, ...updates }; this.listeners.forEach(fn => fn(this.state)); }
322
+ }
323
+
324
+ const store = new Store({
325
+ engineReady: false,
326
+ isGenerating: false,
327
+ toolsUsed: 0,
328
+ messages: [],
329
+ mem: { soul: 'You are Chronos, a cyberpunk AI agent.', user: '', skills: {} },
330
+ graph: { nodes: [], links: [] },
331
+ tpsHist: []
332
+ });
333
+
334
+ store.subscribe(s => {
335
+ document.getElementById('sbtn').disabled = s.isGenerating || !s.engineReady;
336
+ document.getElementById('sbtn').textContent = s.isGenerating ? 'β–  STOP' : 'SEND';
337
+ document.getElementById('s-tools').textContent = s.toolsUsed;
338
+ });
339
+
340
+ class EventBus {
341
+ constructor() { this.events = {}; }
342
+ on(e, cb) { (this.events[e] ||= []).push(cb); }
343
+ emit(e, data) { (this.events[e] || []).forEach(cb => cb(data)); }
344
+ }
345
+ const bus = new EventBus();
346
+
347
+ class ToolRegistry {
348
+ constructor() { this.tools = new Map(); }
349
+ register(name, desc, schema, fn) {
350
+ this.tools.set(name, { name, description: desc, schema, execute: fn });
351
+ bus.emit('tool:registered', name);
352
+ }
353
+ async run(name, args) {
354
+ const t = this.tools.get(name);
355
+ if (!t) throw new Error(`Tool '${name}' not found`);
356
+ return await t.execute(args);
357
+ }
358
+ getPromptList() {
359
+ return Array.from(this.tools.values()).map(t =>
360
+ `- ${t.name}: ${t.description} (Args: ${JSON.stringify(t.schema)})`
361
+ ).join('\n');
362
+ }
363
+ getNames() { return Array.from(this.tools.keys()); }
364
+ }
365
+ const tools = new ToolRegistry();
366
+
367
+ bus.on('tool:registered', () => {
368
+ document.getElementById('tools-pill').textContent = `TOOLS: ${tools.getNames().length}`;
369
+ });
370
+
371
+ const UI = {
372
+ appendMessage(role, text) {
373
+ const msgs = document.getElementById('msgs');
374
+ const wrap = document.createElement('div');
375
+ wrap.className = `mwrap ${role}`;
376
+ const body = document.createElement('div');
377
+ body.className = 'mbody answer';
378
+ body.innerHTML = this.renderMarkdown(text);
379
+ wrap.innerHTML = `<div class="mrole">${role.toUpperCase()}</div>`;
380
+ wrap.appendChild(body);
381
+ msgs.appendChild(wrap);
382
+ msgs.scrollTop = msgs.scrollHeight;
383
+ bus.emit('message:rendered', body);
384
+ return body;
385
+ },
386
+ renderMarkdown(text) {
387
+ let h = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
388
+ h = h.replace(/```mermaid\n?([\s\S]*?)```/g, (_, c) => `<div class="mermaid-pending">${c.trim()}</div>`);
389
+ h = h.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, l, c) => `<pre><code>${c}</code></pre>`);
390
+ h = h.replace(/`([^`\n]+)`/g, '<code>$1</code>');
391
+ h = h.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
392
+ h = h.replace(/\*([^*\n]+)\*/g, '<em>$1</em>');
393
+ h = h.replace(/\n\n/g, '<br><br>').replace(/\n(?!<)/g, '<br>');
394
+ return h;
395
+ },
396
+ appendToolCard(name, args) {
397
+ const msgs = document.getElementById('msgs');
398
+ const card = document.createElement('div');
399
+ card.className = 'tcard';
400
+ const id = 'tc-' + Date.now();
401
+ card.id = id;
402
+ card.innerHTML = `
403
+ <div class="tcard-hdr">
404
+ <span class="tname">⚑ ${name}</span>
405
+ <span class="tparams">${JSON.stringify(args).slice(0, 80)}</span>
406
+ <span style="color:var(--amber)">⟳</span>
407
+ </div>
408
+ <div class="tres vis">Running...</div>
409
+ `;
410
+ msgs.appendChild(card);
411
+ msgs.scrollTop = msgs.scrollHeight;
412
+ return id;
413
+ },
414
+ finalizeToolCard(cardId, result, isError) {
415
+ const card = document.getElementById(cardId);
416
+ if (!card) return;
417
+ card.querySelector('.tcard-hdr').children[2].textContent = isError ? '⚠' : 'βœ“';
418
+ card.querySelector('.tcard-hdr').children[2].style.color = isError ? 'var(--red)' : 'var(--fg)';
419
+ card.querySelector('.tres').textContent = result;
420
+ },
421
+ showToast(msg) {
422
+ const area = document.getElementById('toast-area');
423
+ const t = document.createElement('div');
424
+ t.className = 'toast';
425
+ t.textContent = msg;
426
+ area.appendChild(t);
427
+ setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 500); }, 4000);
428
+ }
429
+ };
430
+
431
+ function log(text, type = '') {
432
+ const el = document.getElementById('alog');
433
+ const t = new Date().toLocaleTimeString('en', { hour12: false });
434
+ const d = document.createElement('div');
435
+ d.className = `ll ${type}`;
436
+ d.textContent = `[${t}] ${text}`;
437
+ el.appendChild(d); el.scrollTop = el.scrollHeight;
438
+ }
439
+
440
+
441
+ // ═══════════════════════════════════════════════════════════
442
+ // MODULE: MEMORY (soul.md, user.md, skills)
443
+ // ═══════════════════════════════════════════════════════════
444
+ const MemoryModule = {
445
+ init() {
446
+ document.getElementById('soul-f').addEventListener('change', e => this.load(e, 'soul'));
447
+ document.getElementById('user-f').addEventListener('change', e => this.load(e, 'user'));
448
+ document.getElementById('skill-f').addEventListener('change', e => this.loadSkills(e));
449
+ this.restore();
450
+ this.refreshUI();
451
+ },
452
+ trigger(type) { document.getElementById(type + '-f').click(); },
453
+ load(e, type) {
454
+ const file = e.target.files[0];
455
+ if (!file) return;
456
+ const reader = new FileReader();
457
+ reader.onload = ev => {
458
+ store.state.mem[type] = ev.target.result;
459
+ localStorage.setItem('chronos_' + type, ev.target.result);
460
+ this.refreshUI();
461
+ log(`βœ“ ${type}.md loaded`, 'g');
462
+ };
463
+ reader.readAsText(file);
464
+ },
465
+ loadSkills(e) {
466
+ Array.from(e.target.files).forEach(file => {
467
+ const reader = new FileReader();
468
+ reader.onload = ev => {
469
+ const name = file.name.replace(/\.(md|txt)$/i, '');
470
+ store.state.mem.skills[name] = ev.target.result;
471
+ localStorage.setItem('chronos_skills', JSON.stringify(store.state.mem.skills));
472
+ this.refreshUI();
473
+ log(`βœ“ skill: ${name}`, 'g');
474
+ };
475
+ reader.readAsText(file);
476
+ });
477
+ },
478
+ refreshUI() {
479
+ const m = store.state.mem;
480
+ document.getElementById('soul-b').textContent = m.soul.length + 'ch';
481
+ document.getElementById('user-b').textContent = m.user.length + 'ch';
482
+ const chips = document.getElementById('skill-chips');
483
+ chips.innerHTML = '';
484
+ Object.keys(m.skills).forEach(name => {
485
+ const c = document.createElement('div');
486
+ c.className = 'chip';
487
+ c.innerHTML = `${name} <span class="rm" onclick="MemoryModule.removeSkill('${name}')">βœ•</span>`;
488
+ chips.appendChild(c);
489
+ });
490
+ },
491
+ removeSkill(name) {
492
+ delete store.state.mem.skills[name];
493
+ localStorage.setItem('chronos_skills', JSON.stringify(store.state.mem.skills));
494
+ this.refreshUI();
495
+ },
496
+ restore() {
497
+ store.state.mem.soul = localStorage.getItem('chronos_soul') || store.state.mem.soul;
498
+ store.state.mem.user = localStorage.getItem('chronos_user') || '';
499
+ store.state.mem.skills = JSON.parse(localStorage.getItem('chronos_skills') || '{}');
500
+ }
501
+ };
502
+
503
+
504
+ // ═══════════════════════════════════════════════════════════
505
+ // MODULE: RAG (Retrieval-Augmented Generation)
506
+ // ═══════════════════════════════════════════════════════════
507
+ const RAGModule = {
508
+ index: [],
509
+ init() {
510
+ this.index = JSON.parse(localStorage.getItem('chronos_rag') || '[]');
511
+ this.updateStats();
512
+ },
513
+ addText(text) {
514
+ const sentences = text.split(/[.!?]\s+/).filter(s => s.trim().length > 20);
515
+ sentences.forEach(s => this.index.push(s.trim()));
516
+ localStorage.setItem('chronos_rag', JSON.stringify(this.index));
517
+ this.updateStats();
518
+ return sentences.length;
519
+ },
520
+ search(query, topK = 5) {
521
+ const terms = query.toLowerCase().split(/\s+/);
522
+ const scored = this.index.map((sent, i) => {
523
+ const score = terms.reduce((acc, t) => acc + (sent.toLowerCase().includes(t) ? 1 : 0), 0);
524
+ return { text: sent, score };
525
+ }).filter(x => x.score > 0).sort((a, b) => b.score - a.score).slice(0, topK);
526
+ return scored;
527
+ },
528
+ updateStats() {
529
+ document.getElementById('rag-count').textContent = this.index.length;
530
+ },
531
+ clear() {
532
+ this.index = [];
533
+ localStorage.setItem('chronos_rag', '[]');
534
+ this.updateStats();
535
+ document.getElementById('rag-results').innerHTML = '';
536
+ },
537
+ searchUI() {
538
+ const q = document.getElementById('rag-query').value.trim();
539
+ if (!q) return;
540
+ const results = this.search(q);
541
+ document.getElementById('rag-results').innerHTML = results.map((r, i) =>
542
+ `<div class="rag-passage"><b>#${i + 1}</b> (${r.score})<br>${r.text}</div>`
543
+ ).join('') || '<div style="color:var(--fg-dim)">No results.</div>';
544
+ },
545
+ inject() {
546
+ const q = document.getElementById('rag-query').value.trim();
547
+ const results = this.search(q, 3);
548
+ const prompt = `Context:\n${results.map(r => r.text).join('\n')}\n\nBased on this context, answer: ${q}`;
549
+ document.getElementById('uin').value = prompt;
550
+ }
551
+ };
552
+ window.RAGModule = RAGModule;
553
+ document.getElementById('rag-query')?.addEventListener('keydown', e => {
554
+ if (e.key === 'Enter') RAGModule.searchUI();
555
+ });
556
+
557
+
558
+ // ═══════════════════════════════════════════════════════════
559
+ // MODULE: COMMAND PALETTE
560
+ // ═══════════════════════════════════════════════════════════
561
+ const CommandPaletteModule = {
562
+ open: false,
563
+ init() {
564
+ document.addEventListener('keydown', e => {
565
+ if (e.key === 'ArrowUp' && !e.shiftKey && document.getElementById('uin').value.trim() === '') {
566
+ e.preventDefault();
567
+ this.toggle();
568
+ }
569
+ if (e.key === 'Escape') this.close();
570
+ });
571
+ },
572
+ toggle() {
573
+ this.open ? this.close() : this.show();
574
+ },
575
+ show() {
576
+ this.open = true;
577
+ document.getElementById('cmd-palette').classList.add('vis');
578
+ document.getElementById('cp-search').focus();
579
+ this.render('');
580
+ },
581
+ close() {
582
+ this.open = false;
583
+ document.getElementById('cmd-palette').classList.remove('vis');
584
+ },
585
+ render(filter) {
586
+ const items = [
587
+ { badge: 'TOOL', label: 'Clear chat', action: () => { document.getElementById('msgs').innerHTML = ''; store.set({ messages: [] }); } },
588
+ { badge: 'TOOL', label: 'Export all', action: () => ExportModule.exportAll() },
589
+ { badge: 'TOOL', label: 'Toggle console', action: () => ConsoleModule.toggle() },
590
+ ...tools.getNames().map(t => ({ badge: 'TOOL', label: `Use tool: ${t}`, action: () => { document.getElementById('uin').value = `Use tool: ${t}`; } })),
591
+ ...Object.keys(store.state.mem.skills).map(s => ({ badge: 'SKILL', label: s, action: () => { document.getElementById('uin').value = `Use skill: ${s}`; } }))
592
+ ];
593
+ const filtered = items.filter(i => i.label.toLowerCase().includes(filter.toLowerCase()));
594
+ document.getElementById('cp-list').innerHTML = filtered.map((i, idx) =>
595
+ `<div class="cp-item ${idx === 0 ? 'sel' : ''}" onclick="CommandPaletteModule.exec(${items.indexOf(i)})">
596
+ <span class="cp-badge">${i.badge}</span><span>${i.label}</span>
597
+ </div>`
598
+ ).join('');
599
+ this.items = items;
600
+ },
601
+ exec(idx) {
602
+ this.items[idx].action();
603
+ this.close();
604
+ }
605
+ };
606
+ document.getElementById('cp-search')?.addEventListener('input', e => CommandPaletteModule.render(e.target.value));
607
+
608
+
609
+ // ═══════════════════════════════════════════════════════════
610
+ // MODULE: CONSOLE LOG POPUP
611
+ // ═══════════════════════════════════════════════════════════
612
+ const ConsoleModule = {
613
+ logs: [],
614
+ init() {
615
+ const orig = { log: console.log, warn: console.warn, error: console.error };
616
+ ['log', 'warn', 'error'].forEach(level => {
617
+ console[level] = (...args) => {
618
+ orig[level](...args);
619
+ const msg = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
620
+ this.logs.push({ level, msg, ts: new Date().toLocaleTimeString() });
621
+ if (this.logs.length > 300) this.logs.shift();
622
+ this.render();
623
+ };
624
+ });
625
+ document.addEventListener('keydown', e => {
626
+ if ((e.ctrlKey || e.metaKey) && e.key === '`') {
627
+ e.preventDefault();
628
+ this.toggle();
629
+ }
630
+ });
631
+ },
632
+ toggle() {
633
+ document.getElementById('console-popup').classList.toggle('vis');
634
+ this.render();
635
+ },
636
+ render() {
637
+ if (!document.getElementById('console-popup').classList.contains('vis')) return;
638
+ document.getElementById('cp-body').innerHTML = this.logs.map(l =>
639
+ `<div class="clog-line ${l.level}">[${l.ts}] ${l.msg}</div>`
640
+ ).join('');
641
+ }
642
+ };
643
+
644
+
645
+ // ═══════════════════════════════════════════════════════════
646
+ // MODULE: SPEECH I/O
647
+ // ═══════════════════════════════════════════════════════════
648
+ const SpeechModule = {
649
+ recognition: null,
650
+ listening: false,
651
+ init() {
652
+ const btn = document.getElementById('voice-btn');
653
+ btn.addEventListener('click', () => this.toggle());
654
+ if ('speechSynthesis' in window) {
655
+ bus.on('message:rendered', (container) => {
656
+ if (container.classList.contains('agent-answer')) {
657
+ const speakBtn = document.createElement('button');
658
+ speakBtn.className = 'bxs';
659
+ speakBtn.textContent = 'πŸ”Š';
660
+ speakBtn.onclick = () => speechSynthesis.speak(new SpeechSynthesisUtterance(container.textContent));
661
+ container.appendChild(speakBtn);
662
+ }
663
+ });
664
+ }
665
+ },
666
+ toggle() {
667
+ if (!('webkitSpeechRecognition' in window)) {
668
+ UI.showToast('Speech recognition not supported');
669
+ return;
670
+ }
671
+ if (this.listening) {
672
+ this.recognition.stop();
673
+ return;
674
+ }
675
+ this.recognition = new webkitSpeechRecognition();
676
+ this.recognition.continuous = false;
677
+ this.recognition.interimResults = false;
678
+ this.recognition.onstart = () => {
679
+ this.listening = true;
680
+ document.getElementById('voice-btn').classList.add('listening');
681
+ log('πŸŽ™οΈ Listening...', 't');
682
+ };
683
+ this.recognition.onresult = (e) => {
684
+ const text = e.results[0][0].transcript;
685
+ document.getElementById('uin').value = text;
686
+ log(`πŸŽ™οΈ Captured: "${text}"`, 'g');
687
+ };
688
+ this.recognition.onend = () => {
689
+ this.listening = false;
690
+ document.getElementById('voice-btn').classList.remove('listening');
691
+ };
692
+ this.recognition.start();
693
+ }
694
+ };
695
+
696
+
697
+ // ═══════════════════════════════════════════════════════════
698
+ // MODULE: FILE SYSTEM ACCESS
699
+ // ═══════════════════════════════════════════════════════════
700
+ const FileSystemModule = {
701
+ dirHandle: null,
702
+ async pickFolder() {
703
+ try {
704
+ this.dirHandle = await window.showDirectoryPicker();
705
+ UI.showToast(`Workspace: ${this.dirHandle.name}`);
706
+ log(`πŸ“ Workspace set: ${this.dirHandle.name}`, 'g');
707
+ } catch (e) {
708
+ log(`File picker cancelled`, 'w');
709
+ }
710
+ },
711
+ async saveToWorkspace(filename, content) {
712
+ if (!this.dirHandle) {
713
+ UI.showToast('Set workspace first');
714
+ return;
715
+ }
716
+ try {
717
+ const fileHandle = await this.dirHandle.getFileHandle(filename, { create: true });
718
+ const writable = await fileHandle.createWritable();
719
+ await writable.write(content);
720
+ await writable.close();
721
+ UI.showToast(`Saved: ${filename}`);
722
+ log(`πŸ’Ύ Saved ${filename} to workspace`, 'g');
723
+ } catch (e) {
724
+ log(`Save failed: ${e.message}`, 'e');
725
+ }
726
+ }
727
+ };
728
+
729
+
730
+ // ═══════════════════════════════════════════════════════════
731
+ // MODULE: EXPORT
732
+ // ═══════════════════════════════════════════════════════════
733
+ const ExportModule = {
734
+ exportAll() {
735
+ const state = {
736
+ timestamp: new Date().toISOString(),
737
+ memory: store.state.mem,
738
+ graph: store.state.graph,
739
+ rag: RAGModule.index,
740
+ messages: store.state.messages,
741
+ settings: {
742
+ model: document.getElementById('msel').value,
743
+ braveKey: localStorage.getItem('chronos_brave_key') || ''
744
+ }
745
+ };
746
+ const blob = new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' });
747
+ const a = document.createElement('a');
748
+ a.href = URL.createObjectURL(blob);
749
+ a.download = `chronos-export-${Date.now()}.json`;
750
+ a.click();
751
+ UI.showToast('Exported full state');
752
+ log('πŸ“¦ Exported full state', 'g');
753
+ }
754
+ };
755
+
756
+
757
+ // ═══════════════════════════════════════════════════════════
758
+ // MODULE: CONTEXT MANAGER (auto-summarize)
759
+ // ═══════════════════════════════════════════════════════════
760
+ const ContextManager = {
761
+ maxTokens: 4096,
762
+ estimateTokens(text) { return Math.ceil(text.length / 4); },
763
+ updateUI() {
764
+ const total = store.state.messages.reduce((acc, m) => acc + this.estimateTokens(m.content), 0);
765
+ const pct = Math.min(100, Math.round((total / this.maxTokens) * 100));
766
+ document.getElementById('ctx-pill').textContent = `CTX: ${pct}%`;
767
+ document.getElementById('ctx-pill').style.color = pct > 80 ? 'var(--red)' : (pct > 50 ? 'var(--amber)' : 'var(--cyan)');
768
+ document.getElementById('s-ctx').textContent = pct + '%';
769
+ },
770
+ async compact() {
771
+ if (store.state.messages.length < 10) return;
772
+ const old = store.state.messages.slice(0, -5);
773
+ const summary = await engine.chat.completions.create({
774
+ messages: [
775
+ { role: 'system', content: 'Summarize this conversation in 2-3 sentences.' },
776
+ { role: 'user', content: old.map(m => `${m.role}: ${m.content}`).join('\n') }
777
+ ],
778
+ max_tokens: 150
779
+ });
780
+ const summaryText = summary.choices[0].message.content;
781
+ store.set({
782
+ messages: [
783
+ { role: 'system', content: `Previous conversation summary: ${summaryText}` },
784
+ ...store.state.messages.slice(-5)
785
+ ]
786
+ });
787
+ log('πŸ“ Context auto-summarized', 'w');
788
+ UI.showToast('Context compacted');
789
+ }
790
+ };
791
+
792
+
793
+ // ═══════════════════════════════════════════════════════════
794
+ // TOOLS IMPLEMENTATION
795
+ // ═══════════════════════════════════════════════════════════
796
+ tools.register('web_search', 'Search web via Brave API', { query: 'string' }, async (args) => {
797
+ const apiKey = localStorage.getItem('chronos_brave_key') || '';
798
+ if (!apiKey) return 'ERROR: No Brave API key.';
799
+ try {
800
+ const url = `/cors-proxy?url=${encodeURIComponent(`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(args.query)}&count=5`)}`;
801
+ const res = await fetch(url, { headers: { 'X-Subscription-Token': apiKey, 'Accept': 'application/json' } });
802
+ if (!res.ok) return `Search failed: ${res.status}`;
803
+ const data = await res.json();
804
+ return (data.web?.results || []).map((r, i) => `${i + 1}. ${r.title}\n ${r.url}\n ${r.description}`).join('\n\n') || 'No results.';
805
+ } catch (e) { return `Error: ${e.message}`; }
806
+ });
807
+
808
+ tools.register('run_code', 'Execute JavaScript', { code: 'string' }, async (args) => {
809
+ try {
810
+ const logs = [];
811
+ const fakeConsole = { log: (...a) => logs.push(a.join(' ')) };
812
+ const fn = new Function('console', `return (async () => { ${args.code} })()`);
813
+ const result = await fn(fakeConsole);
814
+ return `Result: ${JSON.stringify(result)}\nLogs:\n${logs.join('\n') || '(none)'}`;
815
+ } catch (e) { return `Error: ${e.message}`; }
816
+ });
817
+
818
+ tools.register('run_python', 'Execute Python via Pyodide', { code: 'string' }, async (args) => {
819
+ if (!window.pyodide) {
820
+ window.pyodide = await loadPyodide();
821
+ }
822
+ try {
823
+ const result = window.pyodide.runPython(args.code);
824
+ return `Result: ${result}`;
825
+ } catch (e) { return `Python error: ${e.message}`; }
826
+ });
827
+
828
+ tools.register('remember', 'Save fact to memory', { fact: 'string' }, async (args) => {
829
+ store.state.mem.user += `\n- ${args.fact}`;
830
+ localStorage.setItem('chronos_user', store.state.mem.user);
831
+ MemoryModule.refreshUI();
832
+ return `Saved: "${args.fact}"`;
833
+ });
834
+
835
+ tools.register('read_memory', 'Read user memory', {}, async () => store.state.mem.user || '(empty)');
836
+
837
+ tools.register('extract_facts', 'Extract entity relationships', { text: 'string' }, async (args) => {
838
+ if (!engine) return 'Model not loaded.';
839
+ try {
840
+ const res = await engine.chat.completions.create({
841
+ messages: [
842
+ { role: 'system', content: 'Extract facts as JSON: [{"s":"subject","p":"predicate","o":"object"}]. Output ONLY valid JSON.' },
843
+ { role: 'user', content: args.text }
844
+ ],
845
+ temperature: 0.1, max_tokens: 400
846
+ });
847
+ const raw = res.choices[0].message.content;
848
+ const match = raw.match(/\[[\s\S]*\]/);
849
+ if (!match) return `Could not parse: ${raw.slice(0, 100)}`;
850
+ const triples = JSON.parse(match[0]);
851
+ const g = store.state.graph;
852
+ triples.forEach(t => {
853
+ if (!g.nodes.find(n => n.id === t.s)) g.nodes.push({ id: t.s });
854
+ if (!g.nodes.find(n => n.id === t.o)) g.nodes.push({ id: t.o });
855
+ g.links.push({ source: t.s, target: t.o, label: t.p });
856
+ });
857
+ store.set({ graph: { ...g } });
858
+ bus.emit('graph:updated');
859
+ return `Extracted ${triples.length} facts.`;
860
+ } catch (e) { return `Failed: ${e.message}`; }
861
+ });
862
+
863
+ tools.register('analyze_image', 'Describe an image', { image_url: 'string', prompt: 'string' }, async (args) => {
864
+ if (!engine) return 'Model not loaded.';
865
+ try {
866
+ const res = await engine.chat.completions.create({
867
+ messages: [{
868
+ role: 'user',
869
+ content: [
870
+ { type: 'text', text: args.prompt || 'Describe this image.' },
871
+ { type: 'image_url', image_url: { url: args.image_url } }
872
+ ]
873
+ }]
874
+ });
875
+ return res.choices[0].message.content;
876
+ } catch (e) { return `Vision failed: ${e.message}`; }
877
+ });
878
+
879
+ tools.register('save_file', 'Save content to workspace', { filename: 'string', content: 'string' }, async (args) => {
880
+ await FileSystemModule.saveToWorkspace(args.filename, args.content);
881
+ return `Saved ${args.filename}`;
882
+ });
883
+
884
+ tools.register('rag_search', 'Search local RAG index', { query: 'string' }, async (args) => {
885
+ const results = RAGModule.search(args.query, 3);
886
+ return results.map((r, i) => `${i + 1}. ${r.text}`).join('\n\n') || 'No RAG results.';
887
+ });
888
+
889
+
890
+ // ═══════════════════════════════════════════════════════════
891
+ // MERMAID RENDERER
892
+ // ═══════════════════════════════════════════════════════════
893
+ mermaid.initialize({
894
+ startOnLoad: false,
895
+ theme: 'dark',
896
+ themeVariables: {
897
+ primaryColor: '#111611', primaryTextColor: '#3dff70',
898
+ primaryBorderColor: '#1c8038', lineColor: '#33f0ff',
899
+ fontFamily: 'Share Tech Mono'
900
+ }
901
+ });
902
+
903
+ bus.on('message:rendered', async (container) => {
904
+ const blocks = container.querySelectorAll('.mermaid-pending');
905
+ for (const block of blocks) {
906
+ const code = block.textContent;
907
+ const id = 'm-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6);
908
+ try {
909
+ const { svg } = await mermaid.render(id, code);
910
+ const wrap = document.createElement('div');
911
+ wrap.className = 'mermaid-container';
912
+ wrap.innerHTML = svg;
913
+ block.replaceWith(wrap);
914
+ } catch (err) {
915
+ block.style.color = 'var(--red)';
916
+ block.textContent = `Mermaid error: ${err.message}`;
917
+ }
918
+ }
919
+ });
920
+
921
+
922
+ // ═══════════════════════════════════════════════════════════
923
+ // D3 KNOWLEDGE GRAPH
924
+ // ═══════════════════════════════════════════════════════════
925
+ bus.on('graph:updated', () => {
926
+ const g = store.state.graph;
927
+ const container = document.getElementById('graph-container');
928
+ container.innerHTML = '';
929
+ document.getElementById('graph-stats').textContent = `${g.nodes.length} nodes Β· ${g.links.length} edges`;
930
+ if (!g.nodes.length) return;
931
+ const w = container.offsetWidth, h = 180;
932
+ const svg = d3.select(container).append('svg').attr('width', w).attr('height', h);
933
+ const sim = d3.forceSimulation(g.nodes)
934
+ .force('link', d3.forceLink(g.links).id(d => d.id).distance(50))
935
+ .force('charge', d3.forceManyBody().strength(-100))
936
+ .force('center', d3.forceCenter(w / 2, h / 2));
937
+ const link = svg.append('g').selectAll('line').data(g.links).join('line').attr('stroke', '#1c8038').attr('stroke-width', 1);
938
+ const node = svg.append('g').selectAll('circle').data(g.nodes).join('circle').attr('r', 5).attr('fill', '#3dff70').attr('stroke', '#ffbb33').attr('stroke-width', 1)
939
+ .call(d3.drag()
940
+ .on('start', (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
941
+ .on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
942
+ .on('end', (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })
943
+ );
944
+ const label = svg.append('g').selectAll('text').data(g.nodes).join('text').text(d => d.id).attr('font-size', 8).attr('fill', '#3dff70').attr('font-family', 'Share Tech Mono').attr('dx', 7).attr('dy', 3);
945
+ sim.on('tick', () => {
946
+ link.attr('x1', d => d.source.x).attr('y1', d => d.source.y).attr('x2', d => d.target.x).attr('y2', d => d.target.y);
947
+ node.attr('cx', d => d.x).attr('cy', d => d.y);
948
+ label.attr('x', d => d.x).attr('y', d => d.y);
949
+ });
950
+ });
951
+
952
+
953
+ // ═══════════════════════════════════════════════════════════
954
+ // EASYMDE MODAL
955
+ // ═══════════════════════════════════════════════════════════
956
+ let easyMDE = null, editTarget = null;
957
+ window.openEdit = function(target) {
958
+ editTarget = target;
959
+ document.getElementById('modal-title').textContent = `EDIT ${target.toUpperCase()}.MD`;
960
+ document.getElementById('modal-area').value = store.state.mem[target] || '';
961
+ document.getElementById('modal').classList.add('vis');
962
+ if (easyMDE) { easyMDE.toTextArea(); easyMDE = null; }
963
+ easyMDE = new EasyMDE({
964
+ element: document.getElementById('modal-area'),
965
+ spellChecker: false, status: false,
966
+ toolbar: ['bold', 'italic', 'heading', '|', 'unordered-list', 'ordered-list', '|', 'preview', 'side-by-side']
967
+ });
968
+ };
969
+ window.closeModal = function() {
970
+ document.getElementById('modal').classList.remove('vis');
971
+ if (easyMDE) { easyMDE.toTextArea(); easyMDE = null; }
972
+ };
973
+ window.saveModal = function() {
974
+ const val = easyMDE ? easyMDE.value() : document.getElementById('modal-area').value;
975
+ store.state.mem[editTarget] = val;
976
+ localStorage.setItem('chronos_' + editTarget, val);
977
+ MemoryModule.refreshUI();
978
+ log(`saved ${editTarget}.md`, 'g');
979
+ closeModal();
980
+ };
981
+
982
+
983
+ // ═══════════════════════════════════════════════════════════
984
+ // CORE AGENT LOOP (with self-correction)
985
+ // ═══════════════════════════════════════════════════════════
986
+ let engine;
987
+
988
+ async function loadModel() {
989
+ const id = document.getElementById('msel').value;
990
+ document.getElementById('lbtn').disabled = true;
991
+ document.getElementById('mstatus').textContent = 'LOADING...';
992
+ log(`loading ${id}`, 'w');
993
+ try {
994
+ engine = new webllm.MLCEngine();
995
+ await engine.reload(id);
996
+ store.set({ engineReady: true });
997
+ document.getElementById('mdot').classList.add('ready');
998
+ document.getElementById('mstatus').textContent = 'READY';
999
+ document.getElementById('uin').disabled = false;
1000
+ document.getElementById('uin').focus();
1001
+ log(`βœ“ ${id} ready`, 'g');
1002
+ UI.appendMessage('agent', `**${id}** loaded.\n\nTools: ${tools.getNames().join(', ')}\n\nTry: "Search for AI news", "Extract facts: Chris likes cyberpunk", or drop an image.`);
1003
+ } catch (e) {
1004
+ log(`❌ load failed: ${e.message}`, 'e');
1005
+ document.getElementById('mstatus').textContent = 'ERROR';
1006
+ document.getElementById('lbtn').disabled = false;
1007
+ }
1008
+ }
1009
+
1010
+ async function runAgent(userText) {
1011
+ store.set({ isGenerating: true });
1012
+ UI.appendMessage('user', userText);
1013
+
1014
+ const mem = store.state.mem;
1015
+ const systemPrompt = `${mem.soul}\n\nUser memory:\n${mem.user}\n\nSkills:\n${Object.entries(mem.skills).map(([k, v]) => `[${k}]: ${v.slice(0, 200)}`).join('\n')}\n\nAvailable tools:\n${tools.getPromptList()}\n\nTo use a tool, output EXACTLY:\n<action>{"tool":"name","args":{"key":"value"}}</action>\n\nOtherwise respond normally.`;
1016
+
1017
+ const messages = [
1018
+ { role: 'system', content: systemPrompt },
1019
+ ...store.state.messages.slice(-10),
1020
+ { role: 'user', content: userText }
1021
+ ];
1022
+
1023
+ const maxRetries = 2;
1024
+ let attempt = 0;
1025
+ let success = false;
1026
+
1027
+ while (!success && attempt <= maxRetries) {
1028
+ try {
1029
+ const stream = await engine.chat.completions.create({ messages, stream: true, temperature: 0.7, max_tokens: 1200 });
1030
+ let full = '', bubble = UI.appendMessage('agent', '');
1031
+ const t0 = Date.now();
1032
+ let tokens = 0;
1033
+
1034
+ for await (const chunk of stream) {
1035
+ const delta = chunk.choices[0]?.delta?.content || '';
1036
+ if (delta) { full += delta; tokens++; bubble.innerHTML = UI.renderMarkdown(full); }
1037
+ }
1038
+
1039
+ const elapsed = (Date.now() - t0) / 1000;
1040
+ const tps = Math.round(tokens / elapsed);
1041
+ document.getElementById('s-tps').textContent = tps;
1042
+ store.state.tpsHist.push(tps);
1043
+ if (store.state.tpsHist.length > 20) store.state.tpsHist.shift();
1044
+ drawSpark();
1045
+
1046
+ const actionMatch = full.match(/<action>([\s\S]*?)<\/action>/);
1047
+ if (actionMatch) {
1048
+ try {
1049
+ const action = JSON.parse(actionMatch[1]);
1050
+ const cardId = UI.appendToolCard(action.tool, action.args);
1051
+ const result = await tools.run(action.tool, action.args);
1052
+ UI.finalizeToolCard(cardId, result);
1053
+ store.set({ toolsUsed: store.state.toolsUsed + 1 });
1054
+ log(`tool: ${action.tool} βœ“`, 't');
1055
+
1056
+ messages.push({ role: 'assistant', content: full });
1057
+ messages.push({ role: 'user', content: `Tool result:\n${result}\n\nProvide your final answer.` });
1058
+ const finalStream = await engine.chat.completions.create({ messages, stream: true });
1059
+ let final = '';
1060
+ bubble.innerHTML = '';
1061
+ for await (const chunk of finalStream) {
1062
+ const delta = chunk.choices[0]?.delta?.content || '';
1063
+ if (delta) { final += delta; bubble.innerHTML = UI.renderMarkdown(final); }
1064
+ }
1065
+ full = final;
1066
+ } catch (e) {
1067
+ UI.appendMessage('agent', `Tool error: ${e.message}`);
1068
+ }
1069
+ }
1070
+
1071
+ store.set({ messages: [...store.state.messages, { role: 'user', content: userText }, { role: 'assistant', content: full }] });
1072
+ ContextManager.updateUI();
1073
+ if (store.state.messages.length > 15) await ContextManager.compact();
1074
+ success = true;
1075
+
1076
+ } catch (e) {
1077
+ attempt++;
1078
+ log(`Attempt ${attempt} failed: ${e.message}`, 'e');
1079
+ if (attempt > maxRetries) {
1080
+ UI.appendMessage('agent', `Failed after ${maxRetries} retries: ${e.message}`);
1081
+ success = true;
1082
+ }
1083
+ }
1084
+ }
1085
+
1086
+ store.set({ isGenerating: false });
1087
+ }
1088
+
1089
+ function drawSpark() {
1090
+ const c = document.getElementById('spark');
1091
+ const ctx = c.getContext('2d');
1092
+ const w = c.width, h = c.height;
1093
+ const hist = store.state.tpsHist;
1094
+ ctx.clearRect(0, 0, w, h);
1095
+ if (hist.length < 2) return;
1096
+ const max = Math.max(...hist, 1);
1097
+ ctx.strokeStyle = '#3dff70';
1098
+ ctx.lineWidth = 1.5;
1099
+ ctx.beginPath();
1100
+ hist.forEach((v, i) => {
1101
+ const x = (i / (hist.length - 1)) * w;
1102
+ const y = h - (v / max) * (h - 4) - 2;
1103
+ i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
1104
+ });
1105
+ ctx.stroke();
1106
+ }
1107
+
1108
+
1109
+ // ═══════════════════════════════════════════════════════════
1110
+ // WIRING
1111
+ // ═══════════════════════════════════════════════════════════
1112
+ document.getElementById('lbtn').addEventListener('click', loadModel);
1113
+ document.getElementById('sbtn').addEventListener('click', () => {
1114
+ const inp = document.getElementById('uin');
1115
+ const t = inp.value.trim();
1116
+ if (t && !store.state.isGenerating) { inp.value = ''; runAgent(t); }
1117
+ });
1118
+ document.getElementById('uin').addEventListener('keydown', e => {
1119
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); document.getElementById('sbtn').click(); }
1120
+ });
1121
+
1122
+ // Drag-drop images
1123
+ const msgsEl = document.getElementById('msgs');
1124
+ msgsEl.addEventListener('dragover', e => { e.preventDefault(); msgsEl.classList.add('dragover'); });
1125
+ msgsEl.addEventListener('dragleave', () => msgsEl.classList.remove('dragover'));
1126
+ msgsEl.addEventListener('drop', async e => {
1127
+ e.preventDefault(); msgsEl.classList.remove('dragover');
1128
+ const file = e.dataTransfer.files[0];
1129
+ if (!file || !file.type.startsWith('image/')) return;
1130
+ const reader = new FileReader();
1131
+ reader.onload = async ev => {
1132
+ UI.appendMessage('user', `[image: ${file.name}]`);
1133
+ const cardId = UI.appendToolCard('analyze_image', { file: file.name });
1134
+ const result = await tools.run('analyze_image', { image_url: ev.target.result, prompt: 'Describe this image.' });
1135
+ UI.finalizeToolCard(cardId, result);
1136
+ UI.appendMessage('agent', result);
1137
+ };
1138
+ reader.readAsDataURL(file);
1139
+ });
1140
+
1141
+ // Init all modules
1142
+ MemoryModule.init();
1143
+ RAGModule.init();
1144
+ CommandPaletteModule.init();
1145
+ ConsoleModule.init();
1146
+ SpeechModule.init();
1147
+
1148
+ if (window.crossOriginIsolated) {
1149
+ document.getElementById('setup-banner').classList.add('hidden');
1150
+ } else {
1151
+ setTimeout(() => { if (!window.crossOriginIsolated) document.getElementById('setup-banner').classList.remove('hidden'); }, 500);
1152
+ }
1153
+ document.getElementById('brave-in').value = localStorage.getItem('chronos_brave_key') || '';
1154
+ log('Chronos V3 initialized', 'g');
1155
+ log(`Tools: ${tools.getNames().join(', ')}`, '');
1156
+ </script>
1157
+ </body>
1158
+ </html>