@blockrun/franklin 3.3.3 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -4
- package/dist/agent/commands.d.ts +1 -1
- package/dist/agent/commands.js +128 -17
- package/dist/agent/compact.d.ts +2 -2
- package/dist/agent/compact.js +148 -22
- package/dist/agent/context.d.ts +8 -3
- package/dist/agent/context.js +301 -108
- package/dist/agent/error-classifier.d.ts +11 -2
- package/dist/agent/error-classifier.js +64 -10
- package/dist/agent/llm.d.ts +8 -1
- package/dist/agent/llm.js +114 -19
- package/dist/agent/loop.d.ts +1 -2
- package/dist/agent/loop.js +509 -61
- package/dist/agent/optimize.d.ts +2 -2
- package/dist/agent/optimize.js +9 -7
- package/dist/agent/permissions.d.ts +1 -1
- package/dist/agent/permissions.js +1 -1
- package/dist/agent/planner.d.ts +42 -0
- package/dist/agent/planner.js +110 -0
- package/dist/agent/reduce.d.ts +7 -1
- package/dist/agent/reduce.js +85 -3
- package/dist/agent/streaming-executor.d.ts +6 -1
- package/dist/agent/streaming-executor.js +83 -5
- package/dist/agent/tokens.d.ts +11 -2
- package/dist/agent/tokens.js +38 -5
- package/dist/agent/tool-guard.d.ts +27 -0
- package/dist/agent/tool-guard.js +324 -0
- package/dist/agent/types.d.ts +7 -1
- package/dist/agent/types.js +1 -1
- package/dist/brain/extract.d.ts +11 -0
- package/dist/brain/extract.js +154 -0
- package/dist/brain/index.d.ts +3 -0
- package/dist/brain/index.js +2 -0
- package/dist/brain/store.d.ts +42 -0
- package/dist/brain/store.js +225 -0
- package/dist/brain/types.d.ts +45 -0
- package/dist/brain/types.js +5 -0
- package/dist/commands/daemon.js +2 -1
- package/dist/commands/start.js +16 -3
- package/dist/config.js +1 -1
- package/dist/index.js +27 -2
- package/dist/learnings/extractor.d.ts +13 -0
- package/dist/learnings/extractor.js +69 -8
- package/dist/learnings/index.d.ts +1 -1
- package/dist/learnings/index.js +1 -1
- package/dist/learnings/store.js +42 -13
- package/dist/learnings/types.d.ts +1 -1
- package/dist/mcp/client.d.ts +1 -1
- package/dist/mcp/client.js +5 -5
- package/dist/mcp/config.d.ts +1 -1
- package/dist/mcp/config.js +1 -1
- package/dist/panel/html.d.ts +2 -0
- package/dist/panel/html.js +409 -146
- package/dist/panel/server.js +19 -0
- package/dist/pricing.js +3 -2
- package/dist/proxy/fallback.d.ts +3 -1
- package/dist/proxy/fallback.js +4 -4
- package/dist/proxy/server.js +29 -11
- package/dist/proxy/sse-translator.js +1 -1
- package/dist/router/categories.d.ts +21 -0
- package/dist/router/categories.js +96 -0
- package/dist/router/index.d.ts +9 -2
- package/dist/router/index.js +106 -27
- package/dist/router/local-elo.d.ts +32 -0
- package/dist/router/local-elo.js +107 -0
- package/dist/router/selector.d.ts +46 -0
- package/dist/router/selector.js +106 -0
- package/dist/session/storage.d.ts +5 -1
- package/dist/session/storage.js +24 -2
- package/dist/social/a11y.d.ts +1 -1
- package/dist/social/a11y.js +5 -1
- package/dist/social/browser.d.ts +5 -0
- package/dist/social/browser.js +22 -0
- package/dist/social/preflight.d.ts +4 -0
- package/dist/social/preflight.js +42 -3
- package/dist/stats/failures.d.ts +20 -0
- package/dist/stats/failures.js +63 -0
- package/dist/stats/format.d.ts +6 -0
- package/dist/stats/format.js +23 -0
- package/dist/stats/insights.js +1 -21
- package/dist/stats/session-tracker.d.ts +21 -0
- package/dist/stats/session-tracker.js +28 -0
- package/dist/stats/tracker.d.ts +1 -1
- package/dist/stats/tracker.js +1 -1
- package/dist/tools/bash.d.ts +14 -1
- package/dist/tools/bash.js +132 -7
- package/dist/tools/edit.js +77 -14
- package/dist/tools/glob.js +13 -3
- package/dist/tools/grep.js +30 -12
- package/dist/tools/imagegen.js +3 -3
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/read.d.ts +16 -2
- package/dist/tools/read.js +36 -8
- package/dist/tools/searchx.d.ts +6 -2
- package/dist/tools/searchx.js +221 -44
- package/dist/tools/subagent.js +37 -3
- package/dist/tools/task.js +43 -7
- package/dist/tools/validate.d.ts +11 -0
- package/dist/tools/validate.js +42 -0
- package/dist/tools/webfetch.js +18 -7
- package/dist/tools/websearch.js +41 -7
- package/dist/tools/write.js +26 -6
- package/dist/ui/app.js +31 -6
- package/dist/ui/model-picker.d.ts +1 -1
- package/dist/ui/model-picker.js +1 -1
- package/dist/ui/terminal.d.ts +1 -1
- package/dist/ui/terminal.js +1 -1
- package/package.json +2 -2
package/dist/panel/html.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Franklin Panel — embedded HTML dashboard.
|
|
3
3
|
* Single page, dark theme, zero dependencies.
|
|
4
|
+
* Design language adapted from Multica (oklch palette, sidebar nav).
|
|
5
|
+
* Currency-grade watermark + Inter font.
|
|
4
6
|
*/
|
|
5
7
|
export function getHTML() {
|
|
6
8
|
return `<!DOCTYPE html>
|
|
@@ -9,147 +11,402 @@ export function getHTML() {
|
|
|
9
11
|
<meta charset="utf-8">
|
|
10
12
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
11
13
|
<title>Franklin Panel</title>
|
|
14
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='30' y='20' width='55' height='60' rx='14' stroke='white' stroke-width='8' fill='none'/%3E%3Cpath d='M15 35 L25 35' stroke='white' stroke-width='6' stroke-linecap='round'/%3E%3Cpath d='M10 50 L25 50' stroke='white' stroke-width='6' stroke-linecap='round'/%3E%3Cpath d='M15 65 L25 65' stroke='white' stroke-width='6' stroke-linecap='round'/%3E%3C/svg%3E">
|
|
15
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
16
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
17
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
12
18
|
<style>
|
|
13
19
|
:root {
|
|
14
|
-
--bg:
|
|
15
|
-
--bg-card:
|
|
16
|
-
--bg-hover:
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
-
--
|
|
25
|
-
--
|
|
20
|
+
--bg: oklch(0.13 0.006 286);
|
|
21
|
+
--bg-card: oklch(0.19 0.006 286);
|
|
22
|
+
--bg-card-hover: oklch(0.23 0.006 286);
|
|
23
|
+
--bg-sidebar: oklch(0.16 0.005 286);
|
|
24
|
+
--border: oklch(1 0 0 / 8%);
|
|
25
|
+
--border-strong: oklch(1 0 0 / 14%);
|
|
26
|
+
--text: oklch(0.96 0 0);
|
|
27
|
+
--text-dim: oklch(0.50 0.012 286);
|
|
28
|
+
--text-muted: oklch(0.68 0.012 286);
|
|
29
|
+
--brand: oklch(0.68 0.16 260);
|
|
30
|
+
--success: oklch(0.72 0.17 150);
|
|
31
|
+
--warning: oklch(0.78 0.14 85);
|
|
32
|
+
--danger: oklch(0.65 0.20 25);
|
|
33
|
+
--gold: oklch(0.85 0.13 85);
|
|
34
|
+
--gold-dim: oklch(0.45 0.08 85);
|
|
35
|
+
--mono: 'JetBrains Mono','SF Mono','Fira Code','Menlo',monospace;
|
|
36
|
+
--sans: 'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
37
|
+
--radius: 10px;
|
|
26
38
|
}
|
|
27
39
|
* { margin:0; padding:0; box-sizing:border-box; }
|
|
28
|
-
body { background:var(--bg); color:var(--text); font-family:var(--sans); font-size:14px; }
|
|
29
|
-
a { color:var(--
|
|
40
|
+
body { background:var(--bg); color:var(--text); font-family:var(--sans); font-size:14px; display:flex; height:100vh; overflow:hidden; -webkit-font-smoothing:antialiased; }
|
|
41
|
+
a { color:var(--brand); text-decoration:none; }
|
|
30
42
|
a:hover { text-decoration:underline; }
|
|
43
|
+
::-webkit-scrollbar { width:5px; }
|
|
44
|
+
::-webkit-scrollbar-track { background:transparent; }
|
|
45
|
+
::-webkit-scrollbar-thumb { background:oklch(1 0 0 / 6%); border-radius:3px; }
|
|
46
|
+
::-webkit-scrollbar-thumb:hover { background:oklch(1 0 0 / 14%); }
|
|
31
47
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
48
|
+
/* ── Sidebar ── */
|
|
49
|
+
.sidebar {
|
|
50
|
+
width:230px; min-width:230px; background:var(--bg-sidebar);
|
|
51
|
+
border-right:1px solid var(--border); display:flex; flex-direction:column;
|
|
52
|
+
padding:20px 0; overflow-y:auto;
|
|
35
53
|
}
|
|
36
|
-
header
|
|
37
|
-
|
|
38
|
-
.
|
|
39
|
-
|
|
54
|
+
.sidebar-header { padding:0 20px 24px; }
|
|
55
|
+
.sidebar-brand { display:flex; align-items:center; gap:10px; margin-bottom:2px; }
|
|
56
|
+
.sidebar-brand .icon {
|
|
57
|
+
width:32px; height:32px; border-radius:50%; overflow:hidden;
|
|
58
|
+
border:1px solid oklch(0.85 0.13 85 / 30%); flex-shrink:0;
|
|
59
|
+
}
|
|
60
|
+
.sidebar-brand .icon img { width:100%; height:100%; object-fit:cover; object-position:top; }
|
|
61
|
+
.sidebar-brand h1 { font-size:16px; font-weight:700; letter-spacing:-0.02em; }
|
|
62
|
+
.sidebar-sub { font-size:10px; color:var(--text-dim); margin-left:38px; margin-top:-1px; letter-spacing:0.3px; }
|
|
63
|
+
.sidebar-status {
|
|
64
|
+
display:flex; align-items:center; gap:6px; margin-left:38px; margin-top:8px;
|
|
65
|
+
font-size:10px; color:var(--text-dim); font-family:var(--mono);
|
|
66
|
+
}
|
|
67
|
+
.dot { width:6px; height:6px; border-radius:50%; }
|
|
68
|
+
.dot.on { background:var(--success); box-shadow:0 0 8px oklch(0.72 0.17 150 / 60%); }
|
|
40
69
|
.dot.off { background:var(--danger); }
|
|
41
|
-
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.5} }
|
|
42
70
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
71
|
+
.sidebar-label {
|
|
72
|
+
font-size:10px; font-weight:600; color:var(--text-dim);
|
|
73
|
+
text-transform:uppercase; letter-spacing:0.8px;
|
|
74
|
+
padding:20px 20px 8px; user-select:none;
|
|
75
|
+
}
|
|
76
|
+
.sidebar-nav { display:flex; flex-direction:column; gap:1px; padding:0 10px; }
|
|
77
|
+
.nav-item {
|
|
78
|
+
display:flex; align-items:center; gap:10px;
|
|
79
|
+
padding:9px 14px; border-radius:8px;
|
|
80
|
+
cursor:pointer; color:var(--text-muted); font-size:13px; font-weight:500;
|
|
81
|
+
border:none; background:none; width:100%; text-align:left;
|
|
82
|
+
transition:all .15s ease;
|
|
83
|
+
}
|
|
84
|
+
.nav-item:hover { background:oklch(1 0 0 / 5%); color:var(--text); }
|
|
85
|
+
.nav-item.active { background:oklch(1 0 0 / 8%); color:var(--text); }
|
|
86
|
+
.nav-item svg { width:16px; height:16px; opacity:0.5; flex-shrink:0; }
|
|
87
|
+
.nav-item.active svg { opacity:0.9; }
|
|
88
|
+
|
|
89
|
+
.sidebar-footer {
|
|
90
|
+
margin-top:auto; padding:16px 20px; border-top:1px solid var(--border);
|
|
91
|
+
}
|
|
92
|
+
.wallet-mini { font-family:var(--mono); font-size:11px; color:var(--text-dim); }
|
|
93
|
+
.wallet-mini .bal { color:var(--gold); font-weight:700; font-size:14px; display:block; margin-bottom:3px; }
|
|
94
|
+
|
|
95
|
+
/* ── Content ── */
|
|
96
|
+
.content { flex:1; overflow-y:auto; padding:32px 36px; position:relative; }
|
|
97
|
+
.content > * { position:relative; z-index:1; }
|
|
98
|
+
|
|
99
|
+
/* ── FRANKLIN watermark ── */
|
|
100
|
+
.watermark {
|
|
101
|
+
position:fixed; top:0; right:0; bottom:0; width:calc(100% - 230px);
|
|
102
|
+
pointer-events:none; z-index:0; overflow:hidden;
|
|
103
|
+
}
|
|
104
|
+
.watermark-text {
|
|
105
|
+
position:absolute; top:50%; left:50%; white-space:nowrap;
|
|
106
|
+
transform:translate(-50%, -50%) rotate(-25deg);
|
|
107
|
+
font-family:var(--sans); font-size:160px; font-weight:900;
|
|
108
|
+
letter-spacing:20px; text-transform:uppercase;
|
|
109
|
+
color:oklch(1 0 0 / 3%);
|
|
110
|
+
text-shadow:0 0 120px oklch(0.85 0.13 85 / 4%);
|
|
111
|
+
user-select:none;
|
|
112
|
+
}
|
|
113
|
+
.watermark-line2 {
|
|
114
|
+
position:absolute; top:calc(50% + 180px); left:50%; white-space:nowrap;
|
|
115
|
+
transform:translate(-50%, -50%) rotate(-25deg);
|
|
116
|
+
font-family:var(--mono); font-size:40px; font-weight:600;
|
|
117
|
+
letter-spacing:16px; text-transform:uppercase;
|
|
118
|
+
color:oklch(1 0 0 / 2%);
|
|
119
|
+
user-select:none;
|
|
120
|
+
}
|
|
121
|
+
.watermark-guilloche {
|
|
122
|
+
position:absolute; top:0; left:0; right:0; bottom:0;
|
|
123
|
+
background:
|
|
124
|
+
/* Top-right gold rosette */
|
|
125
|
+
radial-gradient(ellipse 650px 650px at 88% 6%, oklch(0.85 0.13 85 / 5%) 0%, transparent 40%),
|
|
126
|
+
radial-gradient(ellipse 550px 550px at 88% 6%, transparent 14%, oklch(0.85 0.13 85 / 4%) 14.8%, transparent 15.6%),
|
|
127
|
+
radial-gradient(ellipse 550px 550px at 88% 6%, transparent 22%, oklch(0.85 0.13 85 / 3.5%) 22.8%, transparent 23.6%),
|
|
128
|
+
radial-gradient(ellipse 550px 550px at 88% 6%, transparent 30%, oklch(0.85 0.13 85 / 3%) 30.8%, transparent 31.6%),
|
|
129
|
+
radial-gradient(ellipse 550px 550px at 88% 6%, transparent 38%, oklch(0.85 0.13 85 / 2.5%) 38.8%, transparent 39.6%),
|
|
130
|
+
/* Bottom-left green rosette */
|
|
131
|
+
radial-gradient(ellipse 500px 500px at 12% 92%, oklch(0.72 0.17 150 / 4%) 0%, transparent 35%),
|
|
132
|
+
radial-gradient(ellipse 400px 400px at 12% 92%, transparent 18%, oklch(0.72 0.17 150 / 3%) 18.8%, transparent 19.6%),
|
|
133
|
+
radial-gradient(ellipse 400px 400px at 12% 92%, transparent 30%, oklch(0.72 0.17 150 / 2.5%) 30.8%, transparent 31.6%),
|
|
134
|
+
/* Fine engraving lines */
|
|
135
|
+
repeating-linear-gradient(35deg, oklch(1 0 0 / 1.5%) 0px, oklch(1 0 0 / 1.5%) 1px, transparent 1px, transparent 5px),
|
|
136
|
+
repeating-linear-gradient(-55deg, oklch(1 0 0 / 1%) 0px, oklch(1 0 0 / 1%) 1px, transparent 1px, transparent 7px);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Franklin portrait — right side (same treatment as website hero) */
|
|
140
|
+
.watermark-portrait {
|
|
141
|
+
position:absolute; inset:0 0 0 auto; width:55%;
|
|
142
|
+
background:url(/assets/franklin-bill.jpg) top/cover no-repeat;
|
|
143
|
+
opacity:0.5; filter:brightness(1.4);
|
|
144
|
+
}
|
|
145
|
+
.watermark-portrait-fade {
|
|
146
|
+
position:absolute; inset:0 0 0 auto; width:55%;
|
|
147
|
+
background:linear-gradient(to right, var(--bg), transparent);
|
|
148
|
+
}
|
|
149
|
+
.watermark-portrait-bottom {
|
|
150
|
+
position:absolute; inset:auto 0 0 0; height:120px;
|
|
151
|
+
background:linear-gradient(to top, var(--bg), transparent);
|
|
48
152
|
}
|
|
49
|
-
nav button:hover { color:var(--text); }
|
|
50
|
-
nav button.active { color:var(--accent); border-bottom-color:var(--accent); }
|
|
51
153
|
|
|
52
|
-
|
|
53
|
-
.
|
|
154
|
+
.content-header { margin-bottom:24px; }
|
|
155
|
+
.content-header h2 { font-size:22px; font-weight:700; letter-spacing:-0.03em; }
|
|
156
|
+
.content-header p { font-size:13px; color:var(--text-dim); margin-top:4px; font-weight:400; }
|
|
157
|
+
|
|
158
|
+
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:12px; }
|
|
159
|
+
.grid-4 { grid-template-columns:repeat(4,1fr); }
|
|
54
160
|
.card {
|
|
55
|
-
background:
|
|
56
|
-
border-radius:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.
|
|
61
|
-
.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
.
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
|
|
70
|
-
.
|
|
71
|
-
|
|
161
|
+
background:oklch(0.19 0.006 286 / 80%); border:1px solid var(--border);
|
|
162
|
+
border-radius:var(--radius); padding:20px;
|
|
163
|
+
backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
|
|
164
|
+
transition:border-color .15s, background .15s;
|
|
165
|
+
}
|
|
166
|
+
.card:hover { border-color:var(--border-strong); }
|
|
167
|
+
.card h3 {
|
|
168
|
+
font-size:10px; color:var(--text-dim); text-transform:uppercase;
|
|
169
|
+
letter-spacing:0.8px; font-weight:600; margin-bottom:12px;
|
|
170
|
+
}
|
|
171
|
+
.metric { font-size:28px; font-weight:700; font-family:var(--mono); line-height:1.1; }
|
|
172
|
+
.metric.brand { color:var(--brand); }
|
|
173
|
+
.metric.success { color:var(--success); }
|
|
174
|
+
.metric.gold { color:var(--gold); }
|
|
175
|
+
.metric.warning { color:var(--warning); }
|
|
176
|
+
.sub { font-size:11px; color:var(--text-dim); margin-top:6px; font-weight:400; }
|
|
177
|
+
|
|
178
|
+
/* ── Savings Hero ── */
|
|
179
|
+
.savings-hero {
|
|
180
|
+
background:linear-gradient(135deg, oklch(0.22 0.04 150 / 85%), oklch(0.19 0.006 286 / 80%) 70%);
|
|
181
|
+
border:1px solid oklch(0.72 0.17 150 / 12%);
|
|
182
|
+
border-radius:var(--radius); padding:28px; margin-bottom:12px;
|
|
183
|
+
display:flex; align-items:center; gap:28px;
|
|
184
|
+
box-shadow:0 4px 24px oklch(0 0 0 / 20%), inset 0 1px 0 oklch(1 0 0 / 4%);
|
|
185
|
+
backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
|
|
186
|
+
}
|
|
187
|
+
.savings-amount { font-size:44px; font-weight:800; font-family:var(--mono); color:var(--success); line-height:1; }
|
|
188
|
+
.savings-detail { flex:1; }
|
|
189
|
+
.savings-detail .label { font-size:10px; text-transform:uppercase; letter-spacing:0.8px; color:var(--text-muted); font-weight:600; margin-bottom:6px; }
|
|
190
|
+
.savings-detail .breakdown { font-size:13px; color:var(--text-muted); margin-top:10px; line-height:1.7; }
|
|
191
|
+
.savings-detail .breakdown span { color:var(--text); font-family:var(--mono); font-weight:600; }
|
|
192
|
+
.savings-pct {
|
|
193
|
+
font-size:56px; font-weight:900; font-family:var(--mono);
|
|
194
|
+
color:oklch(0.72 0.17 150 / 20%); line-height:1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* ── Bar chart ── */
|
|
198
|
+
.bar-chart { display:flex; flex-direction:column; gap:8px; }
|
|
199
|
+
.bar-row { display:flex; align-items:center; gap:10px; font-size:12px; }
|
|
200
|
+
.bar-label {
|
|
201
|
+
width:150px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
|
|
202
|
+
color:var(--text-muted); font-family:var(--mono); font-size:11px; font-weight:500;
|
|
203
|
+
}
|
|
204
|
+
.bar-track { flex:1; height:6px; background:oklch(1 0 0 / 4%); border-radius:3px; overflow:hidden; }
|
|
205
|
+
.bar-fill {
|
|
206
|
+
height:100%; border-radius:3px; transition:width .5s ease;
|
|
207
|
+
background:linear-gradient(90deg, var(--brand), oklch(0.75 0.14 260));
|
|
208
|
+
}
|
|
209
|
+
.bar-val { font-family:var(--mono); color:var(--text-dim); font-size:10px; min-width:80px; text-align:right; }
|
|
210
|
+
|
|
211
|
+
/* ── Daily chart ── */
|
|
212
|
+
.daily-chart { display:flex; align-items:flex-end; gap:3px; height:100px; padding-top:8px; }
|
|
213
|
+
.daily-bar {
|
|
214
|
+
flex:1; border-radius:3px 3px 0 0; min-height:2px;
|
|
215
|
+
transition:height .4s ease, opacity .15s; opacity:.4; position:relative; cursor:crosshair;
|
|
216
|
+
background:linear-gradient(180deg, var(--brand), oklch(0.55 0.16 260));
|
|
217
|
+
}
|
|
72
218
|
.daily-bar:hover { opacity:1; }
|
|
219
|
+
.daily-bar:hover::after {
|
|
220
|
+
content:attr(data-tip); position:absolute; bottom:calc(100% + 8px); left:50%;
|
|
221
|
+
transform:translateX(-50%); background:oklch(0.22 0.006 286); color:var(--text);
|
|
222
|
+
font-size:10px; font-family:var(--mono); padding:4px 8px; border-radius:5px;
|
|
223
|
+
white-space:nowrap; pointer-events:none; border:1px solid var(--border-strong);
|
|
224
|
+
box-shadow:0 4px 12px oklch(0 0 0 / 30%);
|
|
225
|
+
}
|
|
73
226
|
|
|
74
|
-
|
|
227
|
+
/* ── Sessions ── */
|
|
228
|
+
.session-list { display:flex; flex-direction:column; gap:6px; }
|
|
75
229
|
.session-item {
|
|
76
|
-
background:
|
|
77
|
-
padding:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.session-item
|
|
81
|
-
.session-
|
|
82
|
-
.
|
|
83
|
-
.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.
|
|
230
|
+
background:oklch(0.19 0.006 286 / 75%); border:1px solid var(--border); border-radius:8px;
|
|
231
|
+
padding:14px 18px; cursor:pointer; transition:all .15s ease;
|
|
232
|
+
backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
|
|
233
|
+
}
|
|
234
|
+
.session-item:hover { background:var(--bg-card-hover); border-color:var(--border-strong); transform:translateY(-1px); }
|
|
235
|
+
.session-item .title { font-size:13px; font-weight:500; }
|
|
236
|
+
.session-item .meta { font-size:10px; color:var(--text-dim); font-family:var(--mono); margin-top:5px; font-weight:400; }
|
|
237
|
+
.session-detail {
|
|
238
|
+
background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius);
|
|
239
|
+
padding:20px; margin-top:14px; max-height:60vh; overflow-y:auto;
|
|
240
|
+
}
|
|
241
|
+
.msg { margin-bottom:14px; }
|
|
242
|
+
.msg.user .role { color:var(--brand); }
|
|
243
|
+
.msg.assistant .role { color:var(--success); }
|
|
244
|
+
.msg .role { font-size:9px; font-weight:700; text-transform:uppercase; letter-spacing:0.8px; margin-bottom:4px; }
|
|
245
|
+
.msg pre { font-family:var(--mono); font-size:12px; white-space:pre-wrap; line-height:1.6; color:var(--text-muted); }
|
|
246
|
+
|
|
247
|
+
/* ── Learnings ── */
|
|
248
|
+
.learning-item {
|
|
249
|
+
padding:12px 0; border-bottom:1px solid var(--border);
|
|
250
|
+
display:flex; gap:12px; align-items:center;
|
|
251
|
+
}
|
|
88
252
|
.learning-item:last-child { border:none; }
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
253
|
+
.badge {
|
|
254
|
+
font-size:9px; font-family:var(--mono); font-weight:700;
|
|
255
|
+
padding:3px 8px; border-radius:5px; white-space:nowrap;
|
|
256
|
+
}
|
|
257
|
+
.badge.high { background:oklch(0.72 0.17 150 / 12%); color:var(--success); }
|
|
258
|
+
.badge.mid { background:oklch(0.78 0.14 85 / 12%); color:var(--warning); }
|
|
259
|
+
.badge.low { background:oklch(1 0 0 / 5%); color:var(--text-dim); }
|
|
260
|
+
.learning-text { flex:1; font-size:13px; color:var(--text-muted); line-height:1.5; }
|
|
261
|
+
.learning-count { font-size:10px; font-family:var(--mono); color:var(--text-dim); font-weight:500; }
|
|
93
262
|
|
|
263
|
+
/* ── Search ── */
|
|
94
264
|
.search-box {
|
|
95
|
-
width:100%; padding:10px
|
|
96
|
-
border-radius:
|
|
265
|
+
width:100%; padding:10px 14px; background:oklch(1 0 0 / 3%); border:1px solid var(--border);
|
|
266
|
+
border-radius:8px; color:var(--text); font-size:13px; font-family:var(--sans);
|
|
267
|
+
margin-bottom:16px; outline:none; transition:border-color .2s, box-shadow .2s;
|
|
97
268
|
}
|
|
98
|
-
.search-box
|
|
269
|
+
.search-box::placeholder { color:var(--text-dim); }
|
|
270
|
+
.search-box:focus { border-color:var(--brand); box-shadow:0 0 0 3px oklch(0.68 0.16 260 / 12%); }
|
|
271
|
+
|
|
99
272
|
.tab { display:none; }
|
|
100
273
|
.tab.active { display:block; }
|
|
101
|
-
.empty { color:var(--text-dim); text-align:center; padding:
|
|
274
|
+
.empty { color:var(--text-dim); text-align:center; padding:56px 24px; font-size:13px; }
|
|
275
|
+
|
|
276
|
+
@media (max-width:768px) {
|
|
277
|
+
body { flex-direction:column; }
|
|
278
|
+
.sidebar { width:100%; min-width:100%; flex-direction:row; padding:8px; overflow-x:auto; border-right:none; border-bottom:1px solid var(--border); }
|
|
279
|
+
.sidebar-header, .sidebar-label, .sidebar-footer { display:none; }
|
|
280
|
+
.sidebar-nav { flex-direction:row; gap:4px; padding:0; }
|
|
281
|
+
.content { padding:16px; }
|
|
282
|
+
.grid-4 { grid-template-columns:repeat(2,1fr); }
|
|
283
|
+
.savings-hero { flex-direction:column; gap:12px; text-align:center; }
|
|
284
|
+
.savings-pct { display:none; }
|
|
285
|
+
.watermark { width:100%; }
|
|
286
|
+
}
|
|
102
287
|
</style>
|
|
103
288
|
</head>
|
|
104
289
|
<body>
|
|
105
290
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<div>
|
|
109
|
-
<
|
|
110
|
-
|
|
291
|
+
<!-- Sidebar -->
|
|
292
|
+
<aside class="sidebar">
|
|
293
|
+
<div class="sidebar-header">
|
|
294
|
+
<div class="sidebar-brand">
|
|
295
|
+
<div class="icon"><img src="/assets/franklin-portrait.jpg" alt="F"></div>
|
|
296
|
+
<h1>Franklin</h1>
|
|
297
|
+
</div>
|
|
298
|
+
<div class="sidebar-sub">by <span style="color:var(--success)">BlockRun.ai</span></div>
|
|
299
|
+
<div class="sidebar-status">
|
|
300
|
+
<span class="dot off" id="dot"></span>
|
|
301
|
+
<span id="status">connecting</span>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<div class="sidebar-label">Dashboard</div>
|
|
306
|
+
<div class="sidebar-nav">
|
|
307
|
+
<button class="nav-item active" data-tab="overview">
|
|
308
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg>
|
|
309
|
+
Overview
|
|
310
|
+
</button>
|
|
311
|
+
<button class="nav-item" data-tab="sessions">
|
|
312
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
313
|
+
Sessions
|
|
314
|
+
</button>
|
|
315
|
+
<button class="nav-item" data-tab="social">
|
|
316
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4l11.733 16h4.267l-11.733-16z"/><path d="M4 20l6.768-6.768M15.232 11.232L20 4"/></svg>
|
|
317
|
+
Social
|
|
318
|
+
</button>
|
|
319
|
+
<button class="nav-item" data-tab="learnings">
|
|
320
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
|
|
321
|
+
Learnings
|
|
322
|
+
</button>
|
|
111
323
|
</div>
|
|
112
|
-
</header>
|
|
113
324
|
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
325
|
+
<div class="sidebar-footer">
|
|
326
|
+
<a href="https://franklin.run" target="_blank" rel="noopener" style="display:flex;align-items:center;gap:8px;padding:8px 0 12px;color:var(--text-dim);font-size:12px;text-decoration:none;transition:color 0.15s;">
|
|
327
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
|
328
|
+
franklin.run
|
|
329
|
+
</a>
|
|
330
|
+
<div class="wallet-mini">
|
|
331
|
+
<span class="bal" id="sidebar-balance">—</span>
|
|
332
|
+
<span id="sidebar-addr">Loading wallet...</span>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
</aside>
|
|
336
|
+
|
|
337
|
+
<!-- Watermark layer -->
|
|
338
|
+
<div class="watermark" aria-hidden="true">
|
|
339
|
+
<div class="watermark-guilloche"></div>
|
|
340
|
+
<div class="watermark-text">FRANKLIN</div>
|
|
341
|
+
<div class="watermark-line2">THE AI AGENT WITH A WALLET</div>
|
|
342
|
+
<div class="watermark-portrait"></div>
|
|
343
|
+
<div class="watermark-portrait-fade"></div>
|
|
344
|
+
<div class="watermark-portrait-bottom"></div>
|
|
345
|
+
</div>
|
|
120
346
|
|
|
121
|
-
|
|
347
|
+
<!-- Content -->
|
|
348
|
+
<div class="content">
|
|
122
349
|
<!-- Overview -->
|
|
123
350
|
<div class="tab active" id="tab-overview">
|
|
124
|
-
<div class="
|
|
351
|
+
<div class="content-header">
|
|
352
|
+
<h2>Overview</h2>
|
|
353
|
+
<p>Usage stats and cost breakdown</p>
|
|
354
|
+
</div>
|
|
355
|
+
|
|
356
|
+
<div class="savings-hero" id="savings-hero" style="display:none">
|
|
357
|
+
<div>
|
|
358
|
+
<div class="savings-detail">
|
|
359
|
+
<div class="label">Saved vs Claude Opus</div>
|
|
360
|
+
</div>
|
|
361
|
+
<div class="savings-amount" id="savings-amount">—</div>
|
|
362
|
+
<div class="savings-detail">
|
|
363
|
+
<div class="breakdown">
|
|
364
|
+
You spent <span id="savings-actual">—</span> instead of <span id="savings-opus">—</span>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
<div class="savings-pct" id="savings-pct">—</div>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<div class="grid grid-4">
|
|
125
372
|
<div class="card">
|
|
126
|
-
<h3>
|
|
127
|
-
<div class="
|
|
128
|
-
<div class="sub" id="wallet-
|
|
373
|
+
<h3>Balance</h3>
|
|
374
|
+
<div class="metric gold" id="balance">—</div>
|
|
375
|
+
<div class="sub" id="wallet-chain">—</div>
|
|
129
376
|
</div>
|
|
130
377
|
<div class="card">
|
|
131
378
|
<h3>Total Spent</h3>
|
|
132
|
-
<div class="
|
|
133
|
-
<div class="sub" id="total-requests"
|
|
379
|
+
<div class="metric brand" id="total-cost">—</div>
|
|
380
|
+
<div class="sub" id="total-requests">—</div>
|
|
134
381
|
</div>
|
|
135
382
|
<div class="card">
|
|
136
|
-
<h3>
|
|
137
|
-
<div class="
|
|
138
|
-
<div class="sub"
|
|
383
|
+
<h3>Requests</h3>
|
|
384
|
+
<div class="metric" id="request-count">—</div>
|
|
385
|
+
<div class="sub" id="avg-cost">—</div>
|
|
386
|
+
</div>
|
|
387
|
+
<div class="card">
|
|
388
|
+
<h3>Models Used</h3>
|
|
389
|
+
<div class="metric" id="model-count">—</div>
|
|
390
|
+
<div class="sub" id="period-info">—</div>
|
|
139
391
|
</div>
|
|
140
392
|
</div>
|
|
141
|
-
|
|
142
|
-
|
|
393
|
+
|
|
394
|
+
<div class="card" style="margin-top:12px">
|
|
395
|
+
<h3>Daily Spend (30 days)</h3>
|
|
143
396
|
<div class="daily-chart" id="daily-chart"></div>
|
|
144
397
|
</div>
|
|
145
|
-
<div class="card" style="margin-top:
|
|
146
|
-
<h3>Model
|
|
398
|
+
<div class="card" style="margin-top:12px">
|
|
399
|
+
<h3>Cost by Model</h3>
|
|
147
400
|
<div class="bar-chart" id="model-chart"></div>
|
|
148
401
|
</div>
|
|
149
402
|
</div>
|
|
150
403
|
|
|
151
404
|
<!-- Sessions -->
|
|
152
405
|
<div class="tab" id="tab-sessions">
|
|
406
|
+
<div class="content-header">
|
|
407
|
+
<h2>Sessions</h2>
|
|
408
|
+
<p>Browse past conversations</p>
|
|
409
|
+
</div>
|
|
153
410
|
<input class="search-box" id="session-search" placeholder="Search sessions..." />
|
|
154
411
|
<div class="session-list" id="session-list"></div>
|
|
155
412
|
<div class="session-detail" id="session-detail" style="display:none"></div>
|
|
@@ -157,8 +414,12 @@ main { padding:24px; max-width:1200px; margin:0 auto; }
|
|
|
157
414
|
|
|
158
415
|
<!-- Social -->
|
|
159
416
|
<div class="tab" id="tab-social">
|
|
160
|
-
<div class="
|
|
161
|
-
|
|
417
|
+
<div class="content-header">
|
|
418
|
+
<h2>Social</h2>
|
|
419
|
+
<p>X/Twitter engagement stats</p>
|
|
420
|
+
</div>
|
|
421
|
+
<div class="grid grid-4" id="social-stats"></div>
|
|
422
|
+
<div class="card" style="margin-top:12px">
|
|
162
423
|
<h3>Recent Activity</h3>
|
|
163
424
|
<div id="social-feed" class="empty">No social activity yet</div>
|
|
164
425
|
</div>
|
|
@@ -166,29 +427,30 @@ main { padding:24px; max-width:1200px; margin:0 auto; }
|
|
|
166
427
|
|
|
167
428
|
<!-- Learnings -->
|
|
168
429
|
<div class="tab" id="tab-learnings">
|
|
430
|
+
<div class="content-header">
|
|
431
|
+
<h2>Learnings</h2>
|
|
432
|
+
<p>Preferences Franklin has learned over time</p>
|
|
433
|
+
</div>
|
|
169
434
|
<div id="learnings-list"></div>
|
|
170
435
|
</div>
|
|
171
|
-
</
|
|
436
|
+
</div>
|
|
172
437
|
|
|
173
438
|
<script>
|
|
174
439
|
// Tab switching
|
|
175
|
-
document.querySelectorAll('nav
|
|
440
|
+
document.querySelectorAll('.nav-item').forEach(btn => {
|
|
176
441
|
btn.addEventListener('click', () => {
|
|
177
|
-
document.querySelectorAll('nav
|
|
442
|
+
document.querySelectorAll('.nav-item').forEach(b => b.classList.remove('active'));
|
|
178
443
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
179
444
|
btn.classList.add('active');
|
|
180
445
|
document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
|
|
181
446
|
});
|
|
182
447
|
});
|
|
183
448
|
|
|
184
|
-
// API helpers
|
|
185
449
|
const api = (path) => fetch('/api/' + path).then(r => r.json()).catch(() => null);
|
|
186
|
-
|
|
187
|
-
// Format currency
|
|
188
450
|
const usd = (n) => '$' + (n || 0).toFixed(4);
|
|
189
451
|
const usdBig = (n) => '$' + (n || 0).toFixed(2);
|
|
452
|
+
const esc = (s) => s.replace(/</g, '<').replace(/>/g, '>');
|
|
190
453
|
|
|
191
|
-
// Load overview
|
|
192
454
|
async function loadOverview() {
|
|
193
455
|
const [wallet, stats, insights] = await Promise.all([
|
|
194
456
|
api('wallet'), api('stats'), api('insights?days=30')
|
|
@@ -196,28 +458,39 @@ async function loadOverview() {
|
|
|
196
458
|
|
|
197
459
|
if (wallet) {
|
|
198
460
|
document.getElementById('balance').textContent = usdBig(wallet.balance) + ' USDC';
|
|
199
|
-
document.getElementById('wallet-
|
|
461
|
+
document.getElementById('wallet-chain').textContent = wallet.chain;
|
|
462
|
+
document.getElementById('sidebar-balance').textContent = usdBig(wallet.balance) + ' USDC';
|
|
463
|
+
const addr = wallet.address || '';
|
|
464
|
+
document.getElementById('sidebar-addr').textContent = addr.slice(0, 6) + '...' + addr.slice(-4);
|
|
200
465
|
}
|
|
201
466
|
|
|
202
467
|
if (stats) {
|
|
203
468
|
document.getElementById('total-cost').textContent = usd(stats.totalCostUsd);
|
|
204
469
|
document.getElementById('total-requests').textContent = stats.totalRequests.toLocaleString() + ' requests';
|
|
470
|
+
document.getElementById('request-count').textContent = stats.totalRequests.toLocaleString();
|
|
471
|
+
document.getElementById('avg-cost').textContent = usd(stats.avgCostPerRequest) + ' avg/req';
|
|
472
|
+
document.getElementById('model-count').textContent = Object.keys(stats.byModel || {}).length;
|
|
473
|
+
document.getElementById('period-info').textContent = stats.period || '';
|
|
474
|
+
|
|
205
475
|
if (stats.opusCost > 0) {
|
|
206
|
-
const
|
|
207
|
-
|
|
476
|
+
const saved = stats.saved || (stats.opusCost - stats.totalCostUsd);
|
|
477
|
+
const pct = stats.savedPct || ((1 - stats.totalCostUsd / stats.opusCost) * 100);
|
|
478
|
+
document.getElementById('savings-hero').style.display = 'flex';
|
|
479
|
+
document.getElementById('savings-amount').textContent = usdBig(saved);
|
|
480
|
+
document.getElementById('savings-pct').textContent = pct.toFixed(0) + '%';
|
|
481
|
+
document.getElementById('savings-actual').textContent = usd(stats.totalCostUsd);
|
|
482
|
+
document.getElementById('savings-opus').textContent = usdBig(stats.opusCost);
|
|
208
483
|
}
|
|
209
484
|
|
|
210
|
-
// Model chart
|
|
211
485
|
const models = Object.entries(stats.byModel || {})
|
|
212
|
-
.map(([name, d]) => ({ name, cost: d.costUsd || 0 }))
|
|
213
|
-
.sort((a, b) => b.cost - a.cost)
|
|
214
|
-
.slice(0, 10);
|
|
486
|
+
.map(([name, d]) => ({ name, cost: d.costUsd || 0, reqs: d.requests || 0 }))
|
|
487
|
+
.sort((a, b) => b.cost - a.cost).slice(0, 10);
|
|
215
488
|
const maxCost = Math.max(...models.map(m => m.cost), 0.001);
|
|
216
489
|
document.getElementById('model-chart').innerHTML = models.map(m =>
|
|
217
490
|
'<div class="bar-row">' +
|
|
218
|
-
'<span class="bar-label">' + m.name.split('/').pop() + '</span>' +
|
|
219
|
-
'<div class="bar-fill" style="width:' + (m.cost/maxCost*100) + '%"></div>' +
|
|
220
|
-
'<span class="bar-val">' + usd(m.cost) + '</span>' +
|
|
491
|
+
'<span class="bar-label">' + esc(m.name.split('/').pop()) + '</span>' +
|
|
492
|
+
'<div class="bar-track"><div class="bar-fill" style="width:' + (m.cost/maxCost*100) + '%"></div></div>' +
|
|
493
|
+
'<span class="bar-val">' + usd(m.cost) + ' (' + m.reqs + ')</span>' +
|
|
221
494
|
'</div>'
|
|
222
495
|
).join('');
|
|
223
496
|
}
|
|
@@ -226,12 +499,11 @@ async function loadOverview() {
|
|
|
226
499
|
const days = insights.dailyCosts.slice(-30);
|
|
227
500
|
const maxDay = Math.max(...days.map(d => d.cost), 0.001);
|
|
228
501
|
document.getElementById('daily-chart').innerHTML = days.map(d =>
|
|
229
|
-
'<div class="daily-bar"
|
|
502
|
+
'<div class="daily-bar" data-tip="' + d.date + ': ' + usd(d.cost) + '" style="height:' + Math.max(d.cost/maxDay*100, 2) + '%"></div>'
|
|
230
503
|
).join('');
|
|
231
504
|
}
|
|
232
505
|
}
|
|
233
506
|
|
|
234
|
-
// Load sessions
|
|
235
507
|
async function loadSessions() {
|
|
236
508
|
const sessions = await api('sessions');
|
|
237
509
|
if (!sessions || sessions.length === 0) {
|
|
@@ -239,30 +511,26 @@ async function loadSessions() {
|
|
|
239
511
|
return;
|
|
240
512
|
}
|
|
241
513
|
document.getElementById('session-list').innerHTML = sessions.slice(0, 50).map(s =>
|
|
242
|
-
'<div class="session-item" data-id="' + s.id + '">' +
|
|
243
|
-
'<div>' + (s.model || 'unknown') + '
|
|
244
|
-
'<div class="meta">' + new Date(s.createdAt).toLocaleString() + '
|
|
514
|
+
'<div class="session-item" data-id="' + esc(s.id) + '">' +
|
|
515
|
+
'<div class="title">' + esc(s.model || 'unknown') + ' — ' + s.messageCount + ' messages</div>' +
|
|
516
|
+
'<div class="meta">' + new Date(s.createdAt).toLocaleString() + ' · ' + esc((s.workDir || '').split('/').pop()) + '</div>' +
|
|
245
517
|
'</div>'
|
|
246
518
|
).join('');
|
|
247
|
-
|
|
248
519
|
document.querySelectorAll('.session-item').forEach(el => {
|
|
249
520
|
el.addEventListener('click', async () => {
|
|
250
|
-
const
|
|
251
|
-
const history = await api('sessions/' + encodeURIComponent(id));
|
|
521
|
+
const history = await api('sessions/' + encodeURIComponent(el.dataset.id));
|
|
252
522
|
if (!history) return;
|
|
253
523
|
const detail = document.getElementById('session-detail');
|
|
254
524
|
detail.style.display = 'block';
|
|
255
525
|
detail.innerHTML = history.map(m => {
|
|
256
526
|
const role = m.role || 'system';
|
|
257
527
|
let text = typeof m.content === 'string' ? m.content : JSON.stringify(m.content).slice(0, 500);
|
|
258
|
-
|
|
259
|
-
return '<div class="msg ' + role + '"><pre>' + role.toUpperCase() + ': ' + text + '</pre></div>';
|
|
528
|
+
return '<div class="msg ' + role + '"><div class="role">' + role + '</div><pre>' + esc(text) + '</pre></div>';
|
|
260
529
|
}).join('');
|
|
261
530
|
});
|
|
262
531
|
});
|
|
263
532
|
}
|
|
264
533
|
|
|
265
|
-
// Session search
|
|
266
534
|
let searchTimeout;
|
|
267
535
|
document.getElementById('session-search').addEventListener('input', (e) => {
|
|
268
536
|
clearTimeout(searchTimeout);
|
|
@@ -276,25 +544,23 @@ document.getElementById('session-search').addEventListener('input', (e) => {
|
|
|
276
544
|
}
|
|
277
545
|
document.getElementById('session-list').innerHTML = results.map(r =>
|
|
278
546
|
'<div class="session-item">' +
|
|
279
|
-
'<div>' + r.snippet
|
|
280
|
-
'<div class="meta">' + r.sessionId + '
|
|
547
|
+
'<div class="title">' + esc(r.snippet) + '</div>' +
|
|
548
|
+
'<div class="meta">' + esc(r.sessionId) + ' · score: ' + r.score.toFixed(2) + '</div>' +
|
|
281
549
|
'</div>'
|
|
282
550
|
).join('');
|
|
283
551
|
}, 300);
|
|
284
552
|
});
|
|
285
553
|
|
|
286
|
-
// Load social
|
|
287
554
|
async function loadSocial() {
|
|
288
555
|
const social = await api('social');
|
|
289
|
-
if (!social)
|
|
556
|
+
if (!social) return;
|
|
290
557
|
document.getElementById('social-stats').innerHTML =
|
|
291
|
-
'<div class="card"><h3>Posted</h3><div class="
|
|
292
|
-
'<div class="card"><h3>Drafted</h3><div class="
|
|
293
|
-
'<div class="card"><h3>Skipped</h3><div class="
|
|
294
|
-
'<div class="card"><h3>
|
|
558
|
+
'<div class="card"><h3>Posted</h3><div class="metric success">' + (social.posted || 0) + '</div></div>' +
|
|
559
|
+
'<div class="card"><h3>Drafted</h3><div class="metric">' + (social.drafted || 0) + '</div></div>' +
|
|
560
|
+
'<div class="card"><h3>Skipped</h3><div class="metric">' + (social.skipped || 0) + '</div></div>' +
|
|
561
|
+
'<div class="card"><h3>Social Cost</h3><div class="metric gold">' + usd(social.totalCost || 0) + '</div></div>';
|
|
295
562
|
}
|
|
296
563
|
|
|
297
|
-
// Load learnings
|
|
298
564
|
async function loadLearnings() {
|
|
299
565
|
const learnings = await api('learnings');
|
|
300
566
|
if (!learnings || learnings.length === 0) {
|
|
@@ -306,34 +572,31 @@ async function loadLearnings() {
|
|
|
306
572
|
.map(l => {
|
|
307
573
|
const cls = l.confidence >= 0.8 ? 'high' : l.confidence >= 0.5 ? 'mid' : 'low';
|
|
308
574
|
return '<div class="learning-item">' +
|
|
309
|
-
'<span class="
|
|
310
|
-
'<span>' + l.learning + '</span>' +
|
|
311
|
-
'<span
|
|
575
|
+
'<span class="badge ' + cls + '">' + (l.confidence * 100).toFixed(0) + '%</span>' +
|
|
576
|
+
'<span class="learning-text">' + esc(l.learning) + '</span>' +
|
|
577
|
+
'<span class="learning-count">×' + l.times_confirmed + '</span>' +
|
|
312
578
|
'</div>';
|
|
313
579
|
}).join('');
|
|
314
580
|
}
|
|
315
581
|
|
|
316
|
-
// SSE
|
|
317
582
|
const es = new EventSource('/api/events');
|
|
318
583
|
const dot = document.getElementById('dot');
|
|
319
584
|
const statusEl = document.getElementById('status');
|
|
320
585
|
es.onopen = () => { dot.className = 'dot on'; statusEl.textContent = 'live'; };
|
|
321
|
-
es.onerror = () => { dot.className = 'dot off'; statusEl.textContent = '
|
|
586
|
+
es.onerror = () => { dot.className = 'dot off'; statusEl.textContent = 'offline'; };
|
|
322
587
|
es.onmessage = (e) => {
|
|
323
|
-
try {
|
|
324
|
-
const evt = JSON.parse(e.data);
|
|
325
|
-
if (evt.type === 'stats.updated') loadOverview();
|
|
326
|
-
} catch {}
|
|
588
|
+
try { if (JSON.parse(e.data).type === 'stats.updated') loadOverview(); } catch {}
|
|
327
589
|
};
|
|
328
590
|
|
|
329
|
-
// Init
|
|
330
591
|
loadOverview();
|
|
331
592
|
loadSessions();
|
|
332
593
|
loadSocial();
|
|
333
594
|
loadLearnings();
|
|
334
|
-
// Refresh wallet balance every 30s
|
|
335
595
|
setInterval(() => api('wallet').then(w => {
|
|
336
|
-
if (w)
|
|
596
|
+
if (w) {
|
|
597
|
+
document.getElementById('balance').textContent = usdBig(w.balance) + ' USDC';
|
|
598
|
+
document.getElementById('sidebar-balance').textContent = usdBig(w.balance) + ' USDC';
|
|
599
|
+
}
|
|
337
600
|
}), 30000);
|
|
338
601
|
</script>
|
|
339
602
|
</body>
|