openclacky 1.1.1 → 1.1.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +28 -7
- data/lib/clacky/agent/llm_caller.rb +23 -1
- data/lib/clacky/agent/session_serializer.rb +6 -1
- data/lib/clacky/agent.rb +14 -5
- data/lib/clacky/anthropic_stream_aggregator.rb +135 -0
- data/lib/clacky/bedrock_stream_aggregator.rb +137 -0
- data/lib/clacky/cli.rb +9 -2
- data/lib/clacky/client.rb +146 -17
- data/lib/clacky/default_skills/onboard/SKILL.md +6 -2
- data/lib/clacky/default_skills/onboard/scripts/import_external_skills.rb +50 -6
- data/lib/clacky/openai_stream_aggregator.rb +130 -0
- data/lib/clacky/server/http_server.rb +2 -3
- data/lib/clacky/server/web_ui_controller.rb +8 -4
- data/lib/clacky/ui2/progress_handle.rb +77 -15
- data/lib/clacky/ui2/ui_controller.rb +4 -2
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +6 -4
- data/lib/clacky/web/i18n.js +6 -0
- data/lib/clacky/web/index.html +3 -1
- data/lib/clacky/web/sessions.js +152 -48
- data/lib/clacky/web/vendor/katex/auto-render.min.js +1 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/katex.min.css +1 -0
- data/lib/clacky/web/vendor/katex/katex.min.js +1 -0
- data/lib/clacky/web/ws-dispatcher.js +19 -4
- data/lib/clacky.rb +3 -0
- data/scripts/install.ps1 +14 -3
- metadata +28 -2
data/lib/clacky/version.rb
CHANGED
data/lib/clacky/web/app.css
CHANGED
|
@@ -943,9 +943,9 @@ body {
|
|
|
943
943
|
align-self: center;
|
|
944
944
|
}
|
|
945
945
|
.session-item:hover .session-actions-btn { display: flex; }
|
|
946
|
-
.session-actions-btn:hover {
|
|
947
|
-
background: var(--color-border-primary);
|
|
948
|
-
color: var(--color-text-primary);
|
|
946
|
+
.session-actions-btn:hover {
|
|
947
|
+
background: var(--color-border-primary);
|
|
948
|
+
color: var(--color-text-primary);
|
|
949
949
|
}
|
|
950
950
|
|
|
951
951
|
/* Pin icon in session name */
|
|
@@ -1808,6 +1808,8 @@ body {
|
|
|
1808
1808
|
.msg-assistant em { font-style: italic; color: var(--color-text-secondary); }
|
|
1809
1809
|
.msg-tool { background: var(--color-bg-primary); border: 1px solid var(--color-border-primary); font-family: monospace; font-size: 12px; color: var(--color-text-secondary); align-self: flex-start; }
|
|
1810
1810
|
.msg-info { color: var(--color-text-secondary); font-size: 12px; align-self: center; font-style: italic; }
|
|
1811
|
+
.msg-info.msg-info-main { font-style: normal; }
|
|
1812
|
+
.msg-info-sub { color: var(--color-text-secondary); font-size: 11px; align-self: center; opacity: 0.7; margin-top: -27px; }
|
|
1811
1813
|
|
|
1812
1814
|
/* ── Feedback request card ──────────────────────────────────────────────── */
|
|
1813
1815
|
.feedback-card {
|
|
@@ -1926,7 +1928,7 @@ body {
|
|
|
1926
1928
|
}
|
|
1927
1929
|
.msg-success { color: var(--color-success); align-self: flex-start; font-size: 13px; }
|
|
1928
1930
|
.tool-name { color: var(--color-warning); font-weight: 600; }
|
|
1929
|
-
.progress-msg { color: var(--color-accent-primary); font-size: 12px; align-self: center;
|
|
1931
|
+
.progress-msg { color: var(--color-accent-primary); font-size: 12px; align-self: center; }
|
|
1930
1932
|
|
|
1931
1933
|
/* ── Token usage line ────────────────────────────────────────────────────── */
|
|
1932
1934
|
.token-usage-line {
|
data/lib/clacky/web/i18n.js
CHANGED
|
@@ -37,6 +37,7 @@ const I18n = (() => {
|
|
|
37
37
|
"chat.status.running": "running",
|
|
38
38
|
"chat.status.error": "error",
|
|
39
39
|
"chat.input.placeholder": "Message… (Enter to send, Shift+Enter for newline)",
|
|
40
|
+
"chat.input.placeholderRunning": "AI is working — you can still send extra info anytime...",
|
|
40
41
|
"chat.btn.send": "Send",
|
|
41
42
|
"chat.thinking": "Thinking…",
|
|
42
43
|
"chat.retrying": "Retrying",
|
|
@@ -44,6 +45,8 @@ const I18n = (() => {
|
|
|
44
45
|
"chat.history_start": "No more history",
|
|
45
46
|
"chat.image_expired": "Expired",
|
|
46
47
|
"chat.done": "Done — {{n}} iteration(s), {{cost}}",
|
|
48
|
+
"chat.done.duration": " · {{duration}}s",
|
|
49
|
+
"chat.done.cache": "Cache hit {{rate}}% ({{hits}}/{{total}}) · {{tokens}} tokens reused",
|
|
47
50
|
"chat.interrupted": "Interrupted.",
|
|
48
51
|
"chat.feedback_hint": "Or type your own answer below ↓",
|
|
49
52
|
"chat.newMessageHint": "New messages ↓",
|
|
@@ -562,6 +565,7 @@ const I18n = (() => {
|
|
|
562
565
|
"chat.status.running": "运行中",
|
|
563
566
|
"chat.status.error": "出错",
|
|
564
567
|
"chat.input.placeholder": "输入消息…(Enter 发送,Shift+Enter 换行)",
|
|
568
|
+
"chat.input.placeholderRunning": "AI 正在工作,你仍然可以随时补充新信息给它...",
|
|
565
569
|
"chat.btn.send": "发送",
|
|
566
570
|
"chat.thinking": "思考中…",
|
|
567
571
|
"chat.retrying": "正在重试",
|
|
@@ -569,6 +573,8 @@ const I18n = (() => {
|
|
|
569
573
|
"chat.history_start": "没有更多历史了",
|
|
570
574
|
"chat.image_expired": "已过期",
|
|
571
575
|
"chat.done": "完成 — {{n}} 步,{{cost}}",
|
|
576
|
+
"chat.done.duration": " · {{duration}}s",
|
|
577
|
+
"chat.done.cache": "缓存命中 {{rate}}% ({{hits}}/{{total}}) · 复用 {{tokens}} tokens",
|
|
572
578
|
"chat.interrupted": "已中断。",
|
|
573
579
|
"chat.feedback_hint": "或在下方输入框自由作答 ↓",
|
|
574
580
|
"chat.newMessageHint": "有新消息 ↓",
|
data/lib/clacky/web/index.html
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title id="page-title">{{BRAND_NAME}}</title>
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/icon.svg">
|
|
8
|
+
<link rel="stylesheet" href="/vendor/katex/katex.min.css">
|
|
8
9
|
<link rel="stylesheet" href="/app.css">
|
|
9
10
|
<script>
|
|
10
11
|
// Inline theme init — must run before CSS renders to prevent flash of wrong theme.
|
|
@@ -905,7 +906,6 @@
|
|
|
905
906
|
<div class="modal-box new-session-modal">
|
|
906
907
|
<div class="modal-header">
|
|
907
908
|
<h3 class="modal-title" data-i18n="sessions.modal.title">Create New Session</h3>
|
|
908
|
-
<button id="new-session-modal-close" class="modal-close-btn" title="Close">×</button>
|
|
909
909
|
</div>
|
|
910
910
|
<div class="modal-body">
|
|
911
911
|
<div class="modal-field">
|
|
@@ -997,6 +997,8 @@
|
|
|
997
997
|
|
|
998
998
|
|
|
999
999
|
<script src="/marked.min.js"></script>
|
|
1000
|
+
<script src="/vendor/katex/katex.min.js"></script>
|
|
1001
|
+
<script src="/vendor/katex/auto-render.min.js"></script>
|
|
1000
1002
|
<script src="/i18n.js"></script>
|
|
1001
1003
|
<script src="/auth.js"></script>
|
|
1002
1004
|
<script src="/theme.js"></script>
|
data/lib/clacky/web/sessions.js
CHANGED
|
@@ -90,20 +90,67 @@ const Sessions = (() => {
|
|
|
90
90
|
// if the marked library is unavailable.
|
|
91
91
|
function _markedParse(text) {
|
|
92
92
|
if (!text) return "";
|
|
93
|
+
|
|
94
|
+
// Extract math BEFORE marked so backslashes / underscores survive intact.
|
|
95
|
+
const math = [];
|
|
96
|
+
const PLACEHOLDER = (i) => `\u0000KTX${i}\u0000`;
|
|
97
|
+
let prepared = _extractMath(text, math, PLACEHOLDER);
|
|
98
|
+
|
|
99
|
+
let html;
|
|
93
100
|
if (typeof marked !== "undefined") {
|
|
94
|
-
// Custom renderer: open all links in a new tab
|
|
95
101
|
const renderer = new marked.Renderer();
|
|
96
102
|
renderer.link = function({ href, title, text }) {
|
|
97
103
|
const titleAttr = title ? ` title="${title}"` : "";
|
|
98
104
|
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
|
|
99
105
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
html = marked.parse(prepared, { breaks: true, gfm: true, renderer });
|
|
107
|
+
} else {
|
|
108
|
+
html = escapeHtml(prepared).replace(/\n/g, "<br>");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (math.length) {
|
|
112
|
+
html = html.replace(/\u0000KTX(\d+)\u0000/g, (_, i) => _renderMath(math[+i]));
|
|
113
|
+
}
|
|
114
|
+
return html;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Pull $$...$$, \[...\], $...$, \(...\) out of `text` and replace each with a
|
|
118
|
+
// sentinel placeholder so marked won't mangle the LaTeX source. The matched
|
|
119
|
+
// segments are pushed (with display flag) onto `out` for later KaTeX rendering.
|
|
120
|
+
function _extractMath(text, out, placeholder) {
|
|
121
|
+
// Order matters: longest/most-specific delimiters first.
|
|
122
|
+
const patterns = [
|
|
123
|
+
{ re: /\$\$([\s\S]+?)\$\$/g, display: true },
|
|
124
|
+
{ re: /\\\[([\s\S]+?)\\\]/g, display: true },
|
|
125
|
+
{ re: /\\\(([\s\S]+?)\\\)/g, display: false },
|
|
126
|
+
// Inline $...$: avoid $$, escaped \$, and prevent crossing newlines/blanks.
|
|
127
|
+
{ re: /(^|[^\$])\$(?!\s)([^\$\n]+?)(?<!\s)\$(?!\d)/g, display: false, hasPrefix: true },
|
|
128
|
+
];
|
|
129
|
+
let result = text;
|
|
130
|
+
for (const { re, display, hasPrefix } of patterns) {
|
|
131
|
+
result = result.replace(re, (m, a, b) => {
|
|
132
|
+
const body = hasPrefix ? b : a;
|
|
133
|
+
const idx = out.length;
|
|
134
|
+
out.push({ body, display });
|
|
135
|
+
return (hasPrefix ? a : "") + placeholder(idx);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function _renderMath({ body, display }) {
|
|
142
|
+
if (typeof katex === "undefined") {
|
|
143
|
+
return `<code>${escapeHtml((display ? "$$" : "$") + body + (display ? "$$" : "$"))}</code>`;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return katex.renderToString(body, {
|
|
147
|
+
displayMode: display,
|
|
148
|
+
throwOnError: false,
|
|
149
|
+
output: "html",
|
|
150
|
+
});
|
|
151
|
+
} catch (e) {
|
|
152
|
+
return `<code class="katex-error">${escapeHtml(body)}</code>`;
|
|
104
153
|
}
|
|
105
|
-
// Fallback: plain escaped text with newlines preserved
|
|
106
|
-
return escapeHtml(text).replace(/\n/g, "<br>");
|
|
107
154
|
}
|
|
108
155
|
|
|
109
156
|
// Build the collapsible thinking block HTML for a given rendered-HTML content string.
|
|
@@ -112,7 +159,7 @@ const Sessions = (() => {
|
|
|
112
159
|
return `<details class="thinking-block">` +
|
|
113
160
|
`<summary class="thinking-summary">` +
|
|
114
161
|
`<span class="thinking-chevron">›</span>` +
|
|
115
|
-
`<span class="thinking-label">
|
|
162
|
+
`<span class="thinking-label">Thoughts</span>` +
|
|
116
163
|
`</summary>` +
|
|
117
164
|
`<div class="thinking-body">${renderedHtml}</div>` +
|
|
118
165
|
`</details>`;
|
|
@@ -349,9 +396,7 @@ const Sessions = (() => {
|
|
|
349
396
|
document.getElementById("btn-welcome-new")
|
|
350
397
|
.addEventListener("click", () => Sessions.create("general"));
|
|
351
398
|
|
|
352
|
-
// Modal:
|
|
353
|
-
document.getElementById("new-session-modal-close")
|
|
354
|
-
.addEventListener("click", () => Sessions.closeNewSessionModal());
|
|
399
|
+
// Modal: cancel / create / overlay click
|
|
355
400
|
document.getElementById("new-session-cancel")
|
|
356
401
|
.addEventListener("click", () => Sessions.closeNewSessionModal());
|
|
357
402
|
document.getElementById("new-session-create")
|
|
@@ -2051,6 +2096,15 @@ const Sessions = (() => {
|
|
|
2051
2096
|
// Here we only update the interrupt button visibility.
|
|
2052
2097
|
const interrupt = $("btn-interrupt");
|
|
2053
2098
|
if (interrupt) interrupt.style.display = status === "running" ? "" : "none";
|
|
2099
|
+
|
|
2100
|
+
// Swap input placeholder so the user knows they can still send extra
|
|
2101
|
+
// info while the agent is working.
|
|
2102
|
+
const inp = $("user-input");
|
|
2103
|
+
if (inp) {
|
|
2104
|
+
const key = status === "running" ? "chat.input.placeholderRunning" : "chat.input.placeholder";
|
|
2105
|
+
inp.setAttribute("data-i18n-placeholder", key);
|
|
2106
|
+
inp.setAttribute("placeholder", I18n.t(key));
|
|
2107
|
+
}
|
|
2054
2108
|
},
|
|
2055
2109
|
|
|
2056
2110
|
/**
|
|
@@ -2410,13 +2464,19 @@ const Sessions = (() => {
|
|
|
2410
2464
|
}
|
|
2411
2465
|
},
|
|
2412
2466
|
|
|
2413
|
-
appendInfo(text) {
|
|
2467
|
+
appendInfo(text, subline) {
|
|
2414
2468
|
Sessions.collapseToolGroup();
|
|
2415
2469
|
const messages = $("messages");
|
|
2416
2470
|
const el = document.createElement("div");
|
|
2417
|
-
el.className = "msg msg-info";
|
|
2471
|
+
el.className = subline ? "msg msg-info msg-info-main" : "msg msg-info";
|
|
2418
2472
|
el.textContent = text;
|
|
2419
2473
|
messages.appendChild(el);
|
|
2474
|
+
if (subline) {
|
|
2475
|
+
const sub = document.createElement("div");
|
|
2476
|
+
sub.className = "msg msg-info-sub";
|
|
2477
|
+
sub.textContent = subline;
|
|
2478
|
+
messages.appendChild(sub);
|
|
2479
|
+
}
|
|
2420
2480
|
_scrollToBottomIfNeeded(messages);
|
|
2421
2481
|
},
|
|
2422
2482
|
|
|
@@ -2490,11 +2550,58 @@ const Sessions = (() => {
|
|
|
2490
2550
|
_getProgressState(id) {
|
|
2491
2551
|
if (!id) return null;
|
|
2492
2552
|
if (!Sessions._sessionProgress[id]) {
|
|
2493
|
-
Sessions._sessionProgress[id] = { el: null, interval: null, startTime: null, type: null, displayText: null };
|
|
2553
|
+
Sessions._sessionProgress[id] = { el: null, interval: null, startTime: null, type: null, displayText: null, metadata: null, lastChunkAt: null };
|
|
2494
2554
|
}
|
|
2495
2555
|
return Sessions._sessionProgress[id];
|
|
2496
2556
|
},
|
|
2497
2557
|
|
|
2558
|
+
// Compact a token count: 1234 → "1.2k", 12345 → "12k", 1234567 → "1.2M".
|
|
2559
|
+
_compactTokenCount(n) {
|
|
2560
|
+
if (n < 1000) return String(n);
|
|
2561
|
+
if (n < 1_000_000) {
|
|
2562
|
+
const k = n / 1000;
|
|
2563
|
+
return k >= 10 ? `${Math.floor(k)}k` : `${k.toFixed(1)}k`;
|
|
2564
|
+
}
|
|
2565
|
+
const m = n / 1_000_000;
|
|
2566
|
+
return m >= 10 ? `${Math.floor(m)}M` : `${m.toFixed(1)}M`;
|
|
2567
|
+
},
|
|
2568
|
+
|
|
2569
|
+
// Render LLM streaming output token count as "↓ 234 tokens".
|
|
2570
|
+
// Returns null when no positive output_tokens — matches CLI behaviour
|
|
2571
|
+
// (input is hidden mid-stream because most providers only ship
|
|
2572
|
+
// input_tokens with the final usage frame).
|
|
2573
|
+
_formatTokenSuffix(metadata) {
|
|
2574
|
+
if (!metadata) return null;
|
|
2575
|
+
const output = metadata.output_tokens;
|
|
2576
|
+
if (output == null || output <= 0) return null;
|
|
2577
|
+
return `↓ ${Sessions._compactTokenCount(output)} tokens`;
|
|
2578
|
+
},
|
|
2579
|
+
|
|
2580
|
+
// Compose the live progress line:
|
|
2581
|
+
// "<text>… (Ns · ↓N tokens · reasoning…)"
|
|
2582
|
+
// The "reasoning" tail surfaces inter-chunk silence so users see
|
|
2583
|
+
// the model is in extended thinking, not stuck. Threshold mirrors
|
|
2584
|
+
// ProgressHandle::IDLE_HINT_THRESHOLD_SECONDS. Animated dots avoid
|
|
2585
|
+
// duplicating the elapsed counter.
|
|
2586
|
+
_composeProgressLine(displayText, startTime, metadata, lastChunkAt) {
|
|
2587
|
+
const now = Date.now();
|
|
2588
|
+
const elapsed = startTime ? Math.floor((now - startTime) / 1000) : 0;
|
|
2589
|
+
const tokenStr = Sessions._formatTokenSuffix(metadata);
|
|
2590
|
+
const parts = [];
|
|
2591
|
+
if (elapsed > 0) parts.push(`${elapsed}s`);
|
|
2592
|
+
if (tokenStr) parts.push(tokenStr);
|
|
2593
|
+
if (tokenStr && lastChunkAt) {
|
|
2594
|
+
const idle = Math.floor((now - lastChunkAt) / 1000);
|
|
2595
|
+
if (idle >= 2) {
|
|
2596
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
2597
|
+
const frame = frames[Math.floor(now / 250) % frames.length];
|
|
2598
|
+
parts.push(`reasoning ${frame} `);
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
if (parts.length === 0) return displayText;
|
|
2602
|
+
return `${displayText}… (${parts.join(" · ")})`;
|
|
2603
|
+
},
|
|
2604
|
+
|
|
2498
2605
|
// Build the display label for a given progress type (pure — no side effects).
|
|
2499
2606
|
_buildDisplayText(text, progress_type, metadata) {
|
|
2500
2607
|
if (progress_type === "thinking") {
|
|
@@ -2530,23 +2637,19 @@ const Sessions = (() => {
|
|
|
2530
2637
|
|
|
2531
2638
|
const el = document.createElement("div");
|
|
2532
2639
|
el.className = "progress-msg";
|
|
2533
|
-
|
|
2534
|
-
// Show elapsed time immediately (not just after first setInterval tick)
|
|
2535
|
-
const initialElapsed = Math.floor((Date.now() - state.startTime) / 1000);
|
|
2536
|
-
el.textContent = initialElapsed > 0
|
|
2537
|
-
? `⟳ ${displayText}… (${initialElapsed}s)`
|
|
2538
|
-
: `⟳ ${displayText}`;
|
|
2640
|
+
el.textContent = Sessions._composeProgressLine(state.displayText, state.startTime, state.metadata, state.lastChunkAt);
|
|
2539
2641
|
messages.appendChild(el);
|
|
2540
2642
|
state.el = el;
|
|
2541
2643
|
_scrollToBottomIfNeeded(messages);
|
|
2542
2644
|
|
|
2543
|
-
//
|
|
2645
|
+
// Tick at 250ms so streaming token counts feel live. The elapsed
|
|
2646
|
+
// counter only displays whole seconds, but token numbers update at
|
|
2647
|
+
// sub-second cadence on fast streams.
|
|
2544
2648
|
state.interval = setInterval(() => {
|
|
2545
|
-
const elapsed = Math.floor((Date.now() - state.startTime) / 1000);
|
|
2546
2649
|
if (state.el) {
|
|
2547
|
-
state.el.textContent =
|
|
2650
|
+
state.el.textContent = Sessions._composeProgressLine(state.displayText, state.startTime, state.metadata, state.lastChunkAt);
|
|
2548
2651
|
}
|
|
2549
|
-
},
|
|
2652
|
+
}, 250);
|
|
2550
2653
|
},
|
|
2551
2654
|
|
|
2552
2655
|
// Detach only the DOM element and timer for a session, preserving logical state
|
|
@@ -2568,54 +2671,53 @@ const Sessions = (() => {
|
|
|
2568
2671
|
const sid = _activeId;
|
|
2569
2672
|
if (!sid) return;
|
|
2570
2673
|
|
|
2571
|
-
const newStartTime
|
|
2572
|
-
const newDisplayText = Sessions._buildDisplayText(text, progress_type, metadata);
|
|
2674
|
+
const newStartTime = startedAt || Date.now();
|
|
2573
2675
|
|
|
2574
|
-
// If this session already has a visible progress indicator (DOM element
|
|
2575
|
-
// attached), update it in-place instead of tear-down/rebuild. This avoids
|
|
2576
|
-
// the jarring flicker when replay_live_state arrives shortly after the
|
|
2577
|
-
// eager-attach on session switch.
|
|
2578
2676
|
const existing = Sessions._sessionProgress[sid];
|
|
2579
2677
|
if (existing && existing.el) {
|
|
2580
|
-
//
|
|
2581
|
-
//
|
|
2678
|
+
// Same start time → same progress phase. Most common case during LLM
|
|
2679
|
+
// streaming (token counts arriving every ~250ms with message: null).
|
|
2680
|
+
// Keep the existing displayText so the random "thinking" verb does
|
|
2681
|
+
// NOT churn on every chunk. Just refresh metadata; the interval tick
|
|
2682
|
+
// will repaint with fresh tokens.
|
|
2582
2683
|
if (existing.startTime === newStartTime) {
|
|
2583
|
-
existing.type
|
|
2684
|
+
existing.type = progress_type;
|
|
2685
|
+
existing.metadata = metadata || {};
|
|
2686
|
+
existing.lastChunkAt = Date.now();
|
|
2687
|
+
// Only adopt a new displayText if the server actually sent one.
|
|
2688
|
+
if (text) existing.displayText = Sessions._buildDisplayText(text, progress_type, metadata);
|
|
2584
2689
|
return;
|
|
2585
2690
|
}
|
|
2586
|
-
// Different start time → new progress phase.
|
|
2587
|
-
//
|
|
2588
|
-
// never sees the indicator disappear/reappear.
|
|
2691
|
+
// Different start time → new progress phase. Update state in-place
|
|
2692
|
+
// and reset the timer base, but reuse the existing DOM element so
|
|
2693
|
+
// the user never sees the indicator disappear/reappear.
|
|
2694
|
+
const newDisplayText = Sessions._buildDisplayText(text, progress_type, metadata);
|
|
2589
2695
|
existing.type = progress_type;
|
|
2590
2696
|
existing.startTime = newStartTime;
|
|
2591
2697
|
existing.displayText = newDisplayText;
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
existing.el.textContent =
|
|
2595
|
-
? `⟳ ${newDisplayText}… (${elapsed}s)`
|
|
2596
|
-
: `⟳ ${newDisplayText}`;
|
|
2597
|
-
// Restart interval with new startTime
|
|
2698
|
+
existing.metadata = metadata || {};
|
|
2699
|
+
existing.lastChunkAt = newStartTime;
|
|
2700
|
+
existing.el.textContent = Sessions._composeProgressLine(newDisplayText, newStartTime, metadata, existing.lastChunkAt);
|
|
2598
2701
|
if (existing.interval) clearInterval(existing.interval);
|
|
2599
2702
|
existing.interval = setInterval(() => {
|
|
2600
|
-
const e = Math.floor((Date.now() - existing.startTime) / 1000);
|
|
2601
2703
|
if (existing.el) {
|
|
2602
|
-
existing.el.textContent =
|
|
2704
|
+
existing.el.textContent = Sessions._composeProgressLine(existing.displayText, existing.startTime, existing.metadata, existing.lastChunkAt);
|
|
2603
2705
|
}
|
|
2604
|
-
},
|
|
2706
|
+
}, 250);
|
|
2605
2707
|
_scrollToBottomIfNeeded($("messages"));
|
|
2606
2708
|
return;
|
|
2607
2709
|
}
|
|
2608
2710
|
|
|
2609
2711
|
// No existing visible progress — create from scratch.
|
|
2610
|
-
// Clear any stale logical state first.
|
|
2611
2712
|
Sessions.clearProgress(sid);
|
|
2612
2713
|
|
|
2613
2714
|
const state = Sessions._getProgressState(sid);
|
|
2614
2715
|
state.type = progress_type;
|
|
2615
2716
|
state.startTime = newStartTime;
|
|
2616
|
-
state.displayText =
|
|
2717
|
+
state.displayText = Sessions._buildDisplayText(text, progress_type, metadata);
|
|
2718
|
+
state.metadata = metadata || {};
|
|
2719
|
+
state.lastChunkAt = newStartTime;
|
|
2617
2720
|
|
|
2618
|
-
// Attach DOM + timer
|
|
2619
2721
|
Sessions._attachProgressUI(sid);
|
|
2620
2722
|
},
|
|
2621
2723
|
|
|
@@ -2655,6 +2757,8 @@ const Sessions = (() => {
|
|
|
2655
2757
|
state.startTime = null;
|
|
2656
2758
|
state.type = null;
|
|
2657
2759
|
state.displayText = null;
|
|
2760
|
+
state.metadata = null;
|
|
2761
|
+
state.lastChunkAt = null;
|
|
2658
2762
|
},
|
|
2659
2763
|
|
|
2660
2764
|
// Delete all progress state for a session (used when session is removed).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={771:function(t){t.exports=e}},n={};function r(e){var o=n[e];if(void 0!==o)return o.exports;var i=n[e]={exports:{}};return t[e](i,i.exports,r),i.exports}r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var o={};return function(){r.d(o,{default:function(){return d}});var e=r(771),t=r.n(e);const n=function(e,t,n){let r=n,o=0;const i=e.length;for(;r<t.length;){const n=t[r];if(o<=0&&t.slice(r,r+i)===e)return r;"\\"===n?r++:"{"===n?o++:"}"===n&&o--,r++}return-1},i=/^\\begin{/;var a=function(e,t){let r;const o=[],a=new RegExp("("+t.map((e=>e.left.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"))).join("|")+")");for(;r=e.search(a),-1!==r;){r>0&&(o.push({type:"text",data:e.slice(0,r)}),e=e.slice(r));const a=t.findIndex((t=>e.startsWith(t.left)));if(r=n(t[a].right,e,t[a].left.length),-1===r)break;const l=e.slice(0,r+t[a].right.length),s=i.test(l)?l:e.slice(t[a].left.length,r);o.push({type:"math",data:s,rawData:l,display:t[a].display}),e=e.slice(r+t[a].right.length)}return""!==e&&o.push({type:"text",data:e}),o};const l=function(e,n){const r=a(e,n.delimiters);if(1===r.length&&"text"===r[0].type)return null;const o=document.createDocumentFragment();for(let e=0;e<r.length;e++)if("text"===r[e].type)o.appendChild(document.createTextNode(r[e].data));else{const i=document.createElement("span");let a=r[e].data;n.displayMode=r[e].display;try{n.preProcess&&(a=n.preProcess(a)),t().render(a,i,n)}catch(i){if(!(i instanceof t().ParseError))throw i;n.errorCallback("KaTeX auto-render: Failed to parse `"+r[e].data+"` with ",i),o.appendChild(document.createTextNode(r[e].rawData));continue}o.appendChild(i)}return o},s=function(e,t){for(let n=0;n<e.childNodes.length;n++){const r=e.childNodes[n];if(3===r.nodeType){let o=r.textContent,i=r.nextSibling,a=0;for(;i&&i.nodeType===Node.TEXT_NODE;)o+=i.textContent,i=i.nextSibling,a++;const s=l(o,t);if(s){for(let e=0;e<a;e++)r.nextSibling.remove();n+=s.childNodes.length-1,e.replaceChild(s,r)}else n+=a}else if(1===r.nodeType){const e=" "+r.className+" ";-1===t.ignoredTags.indexOf(r.nodeName.toLowerCase())&&t.ignoredClasses.every((t=>-1===e.indexOf(" "+t+" ")))&&s(r,t)}}};var d=function(e,t){if(!e)throw new Error("No element provided to render");const n={};for(const e in t)t.hasOwnProperty(e)&&(n[e]=t[e]);n.delimiters=n.delimiters||[{left:"$$",right:"$$",display:!0},{left:"\\(",right:"\\)",display:!1},{left:"\\begin{equation}",right:"\\end{equation}",display:!0},{left:"\\begin{align}",right:"\\end{align}",display:!0},{left:"\\begin{alignat}",right:"\\end{alignat}",display:!0},{left:"\\begin{gather}",right:"\\end{gather}",display:!0},{left:"\\begin{CD}",right:"\\end{CD}",display:!0},{left:"\\[",right:"\\]",display:!0}],n.ignoredTags=n.ignoredTags||["script","noscript","style","textarea","pre","code","option"],n.ignoredClasses=n.ignoredClasses||[],n.errorCallback=n.errorCallback||console.error,n.macros=n.macros||{},s(e,n)}}(),o=o.default}()}));
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(fonts/KaTeX_AMS-Regular.woff2) format("woff2")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Main-Bold.woff2) format("woff2")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Main-Italic.woff2) format("woff2")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Main-Regular.woff2) format("woff2")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Math-Italic.woff2) format("woff2")}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:700;src:url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2")}@font-face{font-family:"KaTeX_SansSerif";font-style:italic;font-weight:400;src:url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2")}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:400;src:url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2")}@font-face{font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Script-Regular.woff2) format("woff2")}@font-face{font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size1-Regular.woff2) format("woff2")}@font-face{font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size2-Regular.woff2) format("woff2")}@font-face{font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size3-Regular.woff2) format("woff2")}@font-face{font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size4-Regular.woff2) format("woff2")}@font-face{font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2")}.katex{font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;text-indent:0;text-rendering:auto}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:"0.16.11"}.katex .katex-mathml{clip:rect(1px,1px,1px,1px);border:0;height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.newline{display:block}.katex .base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathboldfrak,.katex .textboldfrak{font-family:KaTeX_Fraktur;font-weight:700}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .hbox{width:100%}.katex .hbox,.katex .thinbox{display:inline-flex;flex-direction:row}.katex .thinbox{max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline,.katex .hline,.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .rule,.katex .underline .underline-line{min-height:1px}.katex .mspace{display:inline-block}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{border:0 solid;display:inline-block;position:relative}.katex .hline,.katex .overline .overline-line,.katex .underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.root{margin-left:.2777777778em;margin-right:-.5555555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.1666666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.6666666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.4566666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.1466666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.7142857143em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.8571428571em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.1428571429em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.2857142857em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.4285714286em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.7142857143em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.0571428571em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.4685714286em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.9628571429em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.5542857143em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.7777777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.8888888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.1111111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.3044444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.7644444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.5833333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.7283333333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.0733333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.4861111111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.4402777778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.7277777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.2893518519em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.4050925926em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.462962963em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.5208333333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.2002314815em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.4398148148em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.2410800386em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.2892960463em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.337512054em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.3857280617em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.4339440694em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.4821600771em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.5785920926em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.6943105111em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.8331726133em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.1996142719em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.2009646302em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.2411575563em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.2813504823em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.3215434084em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.3617363344em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.4019292605em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.4823151125em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.578778135em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.6945337621em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.8336012862em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .eqn-num:before{content:"(" counter(katexEqnNo) ")";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:"(" counter(mmlEqnNo) ")";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo}
|