@kodaris/krubble-app-components 1.0.115 → 1.0.117
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/dist/chatbot.d.ts +71 -4
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +500 -19
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +498 -19
- package/dist/krubble-app.bundled.js.map +1 -1
- package/dist/krubble-app.bundled.min.js +278 -32
- package/dist/krubble-app.bundled.min.js.map +1 -1
- package/dist/krubble-app.umd.js +498 -19
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +277 -31
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/chatbot.js
CHANGED
|
@@ -339,11 +339,26 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
339
339
|
*/
|
|
340
340
|
this._drawerOpen = false;
|
|
341
341
|
/**
|
|
342
|
-
* Which view the drawer shows — settings or
|
|
343
|
-
*
|
|
344
|
-
*
|
|
342
|
+
* Which view the drawer shows — settings, debug, or a single tool call's full
|
|
343
|
+
* detail. One drawer holds all three; the header buttons switch settings/debug
|
|
344
|
+
* and clicking a tool line switches to `toolDetail`. The content/width swap
|
|
345
|
+
* inside. Never null, so the content stays visible while the drawer slides shut.
|
|
345
346
|
*/
|
|
346
347
|
this._drawerView = 'settings';
|
|
348
|
+
/**
|
|
349
|
+
* The tool call whose full input/output the `toolDetail` drawer view shows —
|
|
350
|
+
* the clicked `tool_use` message and its paired `tool_result` message, stored
|
|
351
|
+
* whole. The render reads `.data.*` off them (and formats for display) so we
|
|
352
|
+
* don't duplicate the message shape here. Transient — set on click, never
|
|
353
|
+
* persisted (its bytes can be large and the view is a peek). Null when none.
|
|
354
|
+
*/
|
|
355
|
+
this._toolDetail = null;
|
|
356
|
+
/** Which tool-detail block ('input' | 'output') just flashed "Copied" (null = none). */
|
|
357
|
+
this._toolDetailCopied = null;
|
|
358
|
+
/** Whether the tool-detail Input section is expanded. Both open on open. */
|
|
359
|
+
this._toolDetailInputOpen = true;
|
|
360
|
+
/** Whether the tool-detail Output section is expanded. */
|
|
361
|
+
this._toolDetailOutputOpen = true;
|
|
347
362
|
/** Current width of the drawer in px (user-resizable, persisted). */
|
|
348
363
|
this._drawerWidth = 430;
|
|
349
364
|
/** True while the user is dragging the drawer's resize handle. */
|
|
@@ -862,6 +877,12 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
862
877
|
}
|
|
863
878
|
_saveState() {
|
|
864
879
|
try {
|
|
880
|
+
// `toolDetail` is a transient peek, not a saved view — persist only the two
|
|
881
|
+
// real preference views so a reload never reopens a stale tool.
|
|
882
|
+
let persistedDrawerView = this._drawerView;
|
|
883
|
+
if (persistedDrawerView === 'toolDetail') {
|
|
884
|
+
persistedDrawerView = 'settings';
|
|
885
|
+
}
|
|
865
886
|
const state = {
|
|
866
887
|
opened: String(this.opened),
|
|
867
888
|
message: this._inputValue,
|
|
@@ -870,7 +891,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
870
891
|
showToolInputs: String(this._showToolInputs),
|
|
871
892
|
showConfirmationInput: String(this._showConfirmationInput),
|
|
872
893
|
drawerOpen: String(this._drawerOpen),
|
|
873
|
-
drawerView:
|
|
894
|
+
drawerView: persistedDrawerView,
|
|
874
895
|
drawerWidth: String(this._drawerWidth),
|
|
875
896
|
};
|
|
876
897
|
if (this._panelEl) {
|
|
@@ -1918,7 +1939,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1918
1939
|
}
|
|
1919
1940
|
});
|
|
1920
1941
|
}
|
|
1921
|
-
/** Clear the in-flight confirmation flags (error paths, or once resolved). */
|
|
1922
1942
|
/**
|
|
1923
1943
|
* Capture ambient "where is the user / what are they looking at" context at
|
|
1924
1944
|
* send time, so the AI is grounded without the host passing anything in.
|
|
@@ -2014,6 +2034,164 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
2014
2034
|
this._drawerOpen = false;
|
|
2015
2035
|
this._saveState();
|
|
2016
2036
|
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Open the drawer to a single tool call's full input/output. Called when the
|
|
2039
|
+
* user clicks a tool-activity line in the transcript. Independent of the
|
|
2040
|
+
* `_showToolInputs` setting — this is the on-demand "show me everything for
|
|
2041
|
+
* THIS call" peek, so it works even when inline inputs are hidden. `result` is
|
|
2042
|
+
* the paired `tool_result` message data (may be absent if the tool hasn't
|
|
2043
|
+
* returned yet).
|
|
2044
|
+
*/
|
|
2045
|
+
_openToolDetail(use) {
|
|
2046
|
+
// Pair the tool_use to the tool_result that immediately follows it (matched by
|
|
2047
|
+
// toolUseId) — the same pairing the transcript render uses for the ⎿ preview.
|
|
2048
|
+
const next = this.messages[this.messages.indexOf(use) + 1];
|
|
2049
|
+
this._toolDetail = {
|
|
2050
|
+
use,
|
|
2051
|
+
result: null,
|
|
2052
|
+
};
|
|
2053
|
+
if (next?.type === 'tool_result' && next.data?.toolUseId === use.data?.toolUseId) {
|
|
2054
|
+
this._toolDetail.result = next;
|
|
2055
|
+
}
|
|
2056
|
+
this._toolDetailCopied = null;
|
|
2057
|
+
this._toolDetailInputOpen = true;
|
|
2058
|
+
this._toolDetailOutputOpen = true;
|
|
2059
|
+
this._drawerView = 'toolDetail';
|
|
2060
|
+
this._drawerOpen = true;
|
|
2061
|
+
this._saveState();
|
|
2062
|
+
// Reset the panel scroll so a newly-opened tool starts at the top, rather
|
|
2063
|
+
// than inheriting the previous tool's scroll position. Wait for the render.
|
|
2064
|
+
this.updateComplete.then(() => {
|
|
2065
|
+
if (this._drawerBodyEl) {
|
|
2066
|
+
this._drawerBodyEl.scrollTop = 0;
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
/** Toggle the tool-detail Input section open/closed. */
|
|
2071
|
+
_handleToggleToolDetailInput() {
|
|
2072
|
+
this._toolDetailInputOpen = !this._toolDetailInputOpen;
|
|
2073
|
+
}
|
|
2074
|
+
/** Toggle the tool-detail Output section open/closed. */
|
|
2075
|
+
_handleToggleToolDetailOutput() {
|
|
2076
|
+
this._toolDetailOutputOpen = !this._toolDetailOutputOpen;
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Pretty-print a tool input/result for the detail view. The values arrive as
|
|
2080
|
+
* strings that usually hold JSON; reindent when they do, otherwise show as-is.
|
|
2081
|
+
* Never throws — falls back to the original text.
|
|
2082
|
+
*
|
|
2083
|
+
* A large/overflowing result is stashed server-side and returned as a preview
|
|
2084
|
+
* wrapped in `<<<uuid_BEGIN>>> … <<<uuid_END>>>` markers, followed by a paging
|
|
2085
|
+
* note. Two things make `JSON.parse` unusable here: the marker wrapper isn't
|
|
2086
|
+
* JSON, and the payload between the markers is a truncated SLICE of a bigger
|
|
2087
|
+
* blob (cut off mid-string), so it never parses on its own. So we reindent the
|
|
2088
|
+
* payload STRUCTURALLY (see `_reindentJson`) rather than parse it, and keep the
|
|
2089
|
+
* markers + note exactly where they were so it still reads as a paged response.
|
|
2090
|
+
*/
|
|
2091
|
+
_formatToolJson(value) {
|
|
2092
|
+
if (value === undefined || value === null) {
|
|
2093
|
+
return '';
|
|
2094
|
+
}
|
|
2095
|
+
const raw = String(value);
|
|
2096
|
+
const marker = raw.match(/(<<<[0-9a-f-]+_BEGIN>>>\n?)([\s\S]*?)(\n?<<<[0-9a-f-]+_END>>>)/);
|
|
2097
|
+
if (marker && marker.index != null) {
|
|
2098
|
+
// Splice by index rather than String.replace — the reindented JSON can
|
|
2099
|
+
// contain `$` sequences (e.g. "$dtoRef") that replace() treats as special.
|
|
2100
|
+
const start = marker.index + marker[1].length;
|
|
2101
|
+
const end = start + marker[2].length;
|
|
2102
|
+
return raw.slice(0, start) + this._reindentJson(marker[2]) + raw.slice(end);
|
|
2103
|
+
}
|
|
2104
|
+
return this._reindentJson(raw);
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Reindent JSON-ish text for display. Tries a strict parse+stringify first (the
|
|
2108
|
+
* clean path for complete, valid JSON). If that fails — which it does for the
|
|
2109
|
+
* truncated slices returned by the stashed-response pager — it falls back to a
|
|
2110
|
+
* structural walk that inserts newlines/indentation around `{}[],:` while
|
|
2111
|
+
* tracking string state (so braces and commas INSIDE string values, and escaped
|
|
2112
|
+
* quotes, are left alone). This formats even incomplete JSON. Non-JSON text
|
|
2113
|
+
* (no leading `{`/`[`) is returned untouched.
|
|
2114
|
+
*/
|
|
2115
|
+
_reindentJson(text) {
|
|
2116
|
+
const trimmed = text.trim();
|
|
2117
|
+
if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
|
|
2118
|
+
return text;
|
|
2119
|
+
}
|
|
2120
|
+
try {
|
|
2121
|
+
return JSON.stringify(JSON.parse(trimmed), null, 2);
|
|
2122
|
+
}
|
|
2123
|
+
catch {
|
|
2124
|
+
// Fall through to the tolerant structural reindent below.
|
|
2125
|
+
}
|
|
2126
|
+
let out = '';
|
|
2127
|
+
let depth = 0;
|
|
2128
|
+
let inString = false;
|
|
2129
|
+
let escaped = false;
|
|
2130
|
+
const pad = (level) => '\n' + ' '.repeat(Math.max(0, level));
|
|
2131
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
2132
|
+
const ch = trimmed[i];
|
|
2133
|
+
if (inString) {
|
|
2134
|
+
out += ch;
|
|
2135
|
+
if (escaped) {
|
|
2136
|
+
escaped = false;
|
|
2137
|
+
}
|
|
2138
|
+
else if (ch === '\\') {
|
|
2139
|
+
escaped = true;
|
|
2140
|
+
}
|
|
2141
|
+
else if (ch === '"') {
|
|
2142
|
+
inString = false;
|
|
2143
|
+
}
|
|
2144
|
+
continue;
|
|
2145
|
+
}
|
|
2146
|
+
if (ch === '"') {
|
|
2147
|
+
inString = true;
|
|
2148
|
+
out += ch;
|
|
2149
|
+
}
|
|
2150
|
+
else if (ch === '{' || ch === '[') {
|
|
2151
|
+
depth++;
|
|
2152
|
+
out += ch + pad(depth);
|
|
2153
|
+
}
|
|
2154
|
+
else if (ch === '}' || ch === ']') {
|
|
2155
|
+
depth--;
|
|
2156
|
+
out += pad(depth) + ch;
|
|
2157
|
+
}
|
|
2158
|
+
else if (ch === ',') {
|
|
2159
|
+
out += ch + pad(depth);
|
|
2160
|
+
}
|
|
2161
|
+
else if (ch === ':') {
|
|
2162
|
+
out += ': ';
|
|
2163
|
+
}
|
|
2164
|
+
else if (ch === ' ' || ch === '\n' || ch === '\t' || ch === '\r') {
|
|
2165
|
+
// Collapse the source's own whitespace between tokens — our pad() owns layout.
|
|
2166
|
+
}
|
|
2167
|
+
else {
|
|
2168
|
+
out += ch;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return out;
|
|
2172
|
+
}
|
|
2173
|
+
/** Copy the tool-detail Input to the clipboard. Stops propagation so the click
|
|
2174
|
+
* doesn't also toggle the (parent) section header. */
|
|
2175
|
+
_handleCopyToolDetailInput(e) {
|
|
2176
|
+
e.stopPropagation();
|
|
2177
|
+
this._copyToolDetail('input', this._formatToolJson(this._toolDetail?.use.data?.toolInput));
|
|
2178
|
+
}
|
|
2179
|
+
/** Copy the tool-detail Output to the clipboard (see input handler). */
|
|
2180
|
+
_handleCopyToolDetailOutput(e) {
|
|
2181
|
+
e.stopPropagation();
|
|
2182
|
+
this._copyToolDetail('output', this._formatToolJson(this._toolDetail?.result?.data?.toolResultString));
|
|
2183
|
+
}
|
|
2184
|
+
/** Write one tool-detail block to the clipboard and flash "Copied" on it. */
|
|
2185
|
+
_copyToolDetail(block, content) {
|
|
2186
|
+
navigator.clipboard.writeText(content).then(() => {
|
|
2187
|
+
this._toolDetailCopied = block;
|
|
2188
|
+
setTimeout(() => {
|
|
2189
|
+
if (this._toolDetailCopied === block) {
|
|
2190
|
+
this._toolDetailCopied = null;
|
|
2191
|
+
}
|
|
2192
|
+
}, 2000);
|
|
2193
|
+
}).catch(() => { });
|
|
2194
|
+
}
|
|
2017
2195
|
_handleDrawerResizeMouseDown(e) {
|
|
2018
2196
|
this._isDrawerResizing = true;
|
|
2019
2197
|
this._drawerResizeStartX = e.clientX;
|
|
@@ -2126,10 +2304,9 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
2126
2304
|
// deliberately never show). Adopt it as the header label and pass it
|
|
2127
2305
|
// up so consumers can update their own chrome, e.g. a breadcrumb.
|
|
2128
2306
|
if (data.data?.title) {
|
|
2129
|
-
|
|
2130
|
-
this.label = title;
|
|
2307
|
+
this.label = data.data.title;
|
|
2131
2308
|
this.dispatchEvent(new CustomEvent('conversation-title', {
|
|
2132
|
-
detail: { title },
|
|
2309
|
+
detail: { title: data.data.title },
|
|
2133
2310
|
bubbles: true,
|
|
2134
2311
|
composed: true,
|
|
2135
2312
|
}));
|
|
@@ -2621,6 +2798,16 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
2621
2798
|
const isDebug = this._drawerView === 'debug'
|
|
2622
2799
|
&& this.variant === 'inline'
|
|
2623
2800
|
&& this._aiFrameworkConfig.debugTelemetryEnabled === true;
|
|
2801
|
+
let title = 'Settings';
|
|
2802
|
+
let body = this._renderSettingsContent();
|
|
2803
|
+
if (isDebug) {
|
|
2804
|
+
title = 'Debug';
|
|
2805
|
+
body = this._renderDebugContent();
|
|
2806
|
+
}
|
|
2807
|
+
else if (this._drawerView === 'toolDetail' && this._toolDetail) {
|
|
2808
|
+
title = this._toolDetail.use.data?.displayName || this._toolDetail.use.data?.toolName || 'Tool';
|
|
2809
|
+
body = this._renderToolDetailContent();
|
|
2810
|
+
}
|
|
2624
2811
|
return html `
|
|
2625
2812
|
<div
|
|
2626
2813
|
class=${classMap({
|
|
@@ -2635,7 +2822,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
2635
2822
|
@mousedown=${this._handleDrawerResizeMouseDown}
|
|
2636
2823
|
></div>
|
|
2637
2824
|
<div class="drawer__header">
|
|
2638
|
-
<span class="drawer__title">${
|
|
2825
|
+
<span class="drawer__title">${title}</span>
|
|
2639
2826
|
<button
|
|
2640
2827
|
class="header-action"
|
|
2641
2828
|
@click=${this._handleCloseDrawer}
|
|
@@ -2658,7 +2845,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
2658
2845
|
</button>
|
|
2659
2846
|
</div>
|
|
2660
2847
|
<div class="drawer__body">
|
|
2661
|
-
${
|
|
2848
|
+
${body}
|
|
2662
2849
|
</div>
|
|
2663
2850
|
</div>
|
|
2664
2851
|
`;
|
|
@@ -2692,6 +2879,129 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
2692
2879
|
</div>
|
|
2693
2880
|
`;
|
|
2694
2881
|
}
|
|
2882
|
+
/**
|
|
2883
|
+
* The Tool detail view's body — the full input and output for one tool call,
|
|
2884
|
+
* pretty-printed in scrollable code blocks with a per-block copy button. This
|
|
2885
|
+
* is the "occasionally see everything" path: the transcript stays terse, and
|
|
2886
|
+
* clicking a tool line drops its complete input/output here, uncapped (the
|
|
2887
|
+
* blocks scroll rather than truncating).
|
|
2888
|
+
*/
|
|
2889
|
+
_renderToolDetailContent() {
|
|
2890
|
+
return html `
|
|
2891
|
+
<div class="tool-detail">
|
|
2892
|
+
<div class="tool-detail__section">
|
|
2893
|
+
<button
|
|
2894
|
+
type="button"
|
|
2895
|
+
class="tool-detail__section-head"
|
|
2896
|
+
aria-expanded=${this._toolDetailInputOpen}
|
|
2897
|
+
@click=${this._handleToggleToolDetailInput}
|
|
2898
|
+
>
|
|
2899
|
+
<span class=${classMap({
|
|
2900
|
+
'tool-detail__caret': true,
|
|
2901
|
+
'tool-detail__caret--open': this._toolDetailInputOpen,
|
|
2902
|
+
})}>›</span>
|
|
2903
|
+
<span class="tool-detail__section-title">Input</span>
|
|
2904
|
+
<span
|
|
2905
|
+
class="tool-detail__copy"
|
|
2906
|
+
role="button"
|
|
2907
|
+
@click=${this._handleCopyToolDetailInput}
|
|
2908
|
+
>
|
|
2909
|
+
${this._toolDetailCopied === 'input'
|
|
2910
|
+
? html `
|
|
2911
|
+
<svg
|
|
2912
|
+
viewBox="0 0 24 24"
|
|
2913
|
+
fill="none"
|
|
2914
|
+
stroke="currentColor"
|
|
2915
|
+
stroke-width="1.5"
|
|
2916
|
+
stroke-linecap="round"
|
|
2917
|
+
stroke-linejoin="round"
|
|
2918
|
+
>
|
|
2919
|
+
<path d="M20 6 9 17l-5-5" />
|
|
2920
|
+
</svg>
|
|
2921
|
+
`
|
|
2922
|
+
: html `
|
|
2923
|
+
<svg
|
|
2924
|
+
viewBox="0 0 24 24"
|
|
2925
|
+
fill="none"
|
|
2926
|
+
stroke="currentColor"
|
|
2927
|
+
stroke-width="1.5"
|
|
2928
|
+
stroke-linecap="round"
|
|
2929
|
+
stroke-linejoin="round"
|
|
2930
|
+
>
|
|
2931
|
+
<rect
|
|
2932
|
+
x="9"
|
|
2933
|
+
y="9"
|
|
2934
|
+
width="13"
|
|
2935
|
+
height="13"
|
|
2936
|
+
rx="2"
|
|
2937
|
+
/>
|
|
2938
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
2939
|
+
</svg>
|
|
2940
|
+
`}
|
|
2941
|
+
</span>
|
|
2942
|
+
</button>
|
|
2943
|
+
${this._toolDetailInputOpen
|
|
2944
|
+
? html `<pre class="tool-detail__code">${this._formatToolJson(this._toolDetail.use.data?.toolInput) || '(no input)'}</pre>`
|
|
2945
|
+
: nothing}
|
|
2946
|
+
</div>
|
|
2947
|
+
<div class="tool-detail__section">
|
|
2948
|
+
<button
|
|
2949
|
+
type="button"
|
|
2950
|
+
class="tool-detail__section-head"
|
|
2951
|
+
aria-expanded=${this._toolDetailOutputOpen}
|
|
2952
|
+
@click=${this._handleToggleToolDetailOutput}
|
|
2953
|
+
>
|
|
2954
|
+
<span class=${classMap({
|
|
2955
|
+
'tool-detail__caret': true,
|
|
2956
|
+
'tool-detail__caret--open': this._toolDetailOutputOpen,
|
|
2957
|
+
})}>›</span>
|
|
2958
|
+
<span class="tool-detail__section-title">Output</span>
|
|
2959
|
+
<span
|
|
2960
|
+
class="tool-detail__copy"
|
|
2961
|
+
role="button"
|
|
2962
|
+
@click=${this._handleCopyToolDetailOutput}
|
|
2963
|
+
>
|
|
2964
|
+
${this._toolDetailCopied === 'output'
|
|
2965
|
+
? html `
|
|
2966
|
+
<svg
|
|
2967
|
+
viewBox="0 0 24 24"
|
|
2968
|
+
fill="none"
|
|
2969
|
+
stroke="currentColor"
|
|
2970
|
+
stroke-width="1.5"
|
|
2971
|
+
stroke-linecap="round"
|
|
2972
|
+
stroke-linejoin="round"
|
|
2973
|
+
>
|
|
2974
|
+
<path d="M20 6 9 17l-5-5" />
|
|
2975
|
+
</svg>
|
|
2976
|
+
`
|
|
2977
|
+
: html `
|
|
2978
|
+
<svg
|
|
2979
|
+
viewBox="0 0 24 24"
|
|
2980
|
+
fill="none"
|
|
2981
|
+
stroke="currentColor"
|
|
2982
|
+
stroke-width="1.5"
|
|
2983
|
+
stroke-linecap="round"
|
|
2984
|
+
stroke-linejoin="round"
|
|
2985
|
+
>
|
|
2986
|
+
<rect
|
|
2987
|
+
x="9"
|
|
2988
|
+
y="9"
|
|
2989
|
+
width="13"
|
|
2990
|
+
height="13"
|
|
2991
|
+
rx="2"
|
|
2992
|
+
/>
|
|
2993
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
2994
|
+
</svg>
|
|
2995
|
+
`}
|
|
2996
|
+
</span>
|
|
2997
|
+
</button>
|
|
2998
|
+
${this._toolDetailOutputOpen
|
|
2999
|
+
? html `<pre class="tool-detail__code">${this._formatToolJson(this._toolDetail.result?.data?.toolResultString) || '(no output yet)'}</pre>`
|
|
3000
|
+
: nothing}
|
|
3001
|
+
</div>
|
|
3002
|
+
</div>
|
|
3003
|
+
`;
|
|
3004
|
+
}
|
|
2695
3005
|
/** The Debug view's body — telemetry scopes and the per-iteration table. */
|
|
2696
3006
|
_renderDebugContent() {
|
|
2697
3007
|
return html `
|
|
@@ -3035,9 +3345,9 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
3035
3345
|
let resultPreview = '';
|
|
3036
3346
|
let resultRemainder = 0;
|
|
3037
3347
|
if (msg.type === 'tool_use') {
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
result =
|
|
3348
|
+
if (this.messages[index + 1]?.type === 'tool_result'
|
|
3349
|
+
&& this.messages[index + 1].data?.toolUseId === msg.data?.toolUseId) {
|
|
3350
|
+
result = this.messages[index + 1].data;
|
|
3041
3351
|
}
|
|
3042
3352
|
if (this._showToolInputs && msg.data?.toolInput) {
|
|
3043
3353
|
inputSummary = String(msg.data.toolInput).replace(/\s+/g, ' ').trim();
|
|
@@ -3053,8 +3363,17 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
3053
3363
|
catch {
|
|
3054
3364
|
formatted = String(result.toolResultString);
|
|
3055
3365
|
}
|
|
3366
|
+
// Cap the inline preview by BOTH lines and characters. Line-only
|
|
3367
|
+
// truncation blows up on results whose first line is huge (e.g. a
|
|
3368
|
+
// minified-JSON blob or a stashed-response marker with a 30k-char
|
|
3369
|
+
// opening line) — 3 lines then still dumps tens of thousands of
|
|
3370
|
+
// chars into the transcript. Char-cap first (content-blind, like
|
|
3371
|
+
// the backend chokepoint), then keep at most 3 lines of that.
|
|
3056
3372
|
const lines = formatted.split('\n');
|
|
3057
3373
|
resultPreview = lines.slice(0, 3).join('\n');
|
|
3374
|
+
if (resultPreview.length > 200) {
|
|
3375
|
+
resultPreview = `${resultPreview.slice(0, 200)}…`;
|
|
3376
|
+
}
|
|
3058
3377
|
resultRemainder = Math.max(0, lines.length - 3);
|
|
3059
3378
|
}
|
|
3060
3379
|
}
|
|
@@ -3083,24 +3402,32 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
3083
3402
|
})}>
|
|
3084
3403
|
${msg.type === 'tool_use' ? html `
|
|
3085
3404
|
<div class="tool-activity-block">
|
|
3086
|
-
<
|
|
3405
|
+
<button
|
|
3406
|
+
type="button"
|
|
3407
|
+
class=${classMap({
|
|
3087
3408
|
'tool-activity': true,
|
|
3088
3409
|
'tool-activity--error': result?.toolResultStatus === 'error',
|
|
3089
|
-
})}
|
|
3410
|
+
})}
|
|
3411
|
+
@click=${() => this._openToolDetail(msg)}
|
|
3412
|
+
>
|
|
3090
3413
|
<span class="tool-activity__chevron">›</span>
|
|
3091
3414
|
<span class="tool-activity__label">${msg.data?.displayName || msg.data?.toolName || 'Tool'}</span>
|
|
3092
3415
|
${inputSummary ? html `<span class="tool-activity__input">(${inputSummary})</span>` : nothing}
|
|
3093
|
-
</
|
|
3416
|
+
</button>
|
|
3094
3417
|
${this._showToolInputs && result?.toolResultString ? html `
|
|
3095
|
-
<
|
|
3418
|
+
<button
|
|
3419
|
+
type="button"
|
|
3420
|
+
class=${classMap({
|
|
3096
3421
|
'tool-activity__result': true,
|
|
3097
3422
|
'tool-activity__result--error': result.toolResultStatus === 'error',
|
|
3098
|
-
})}
|
|
3423
|
+
})}
|
|
3424
|
+
@click=${() => this._openToolDetail(msg)}
|
|
3425
|
+
>
|
|
3099
3426
|
<span class="tool-activity__connector">⎿</span>
|
|
3100
3427
|
<pre class="tool-activity__result-body">${resultPreview}${resultRemainder > 0
|
|
3101
3428
|
? html `\n<span class="tool-activity__more">… +${resultRemainder} lines</span>`
|
|
3102
3429
|
: nothing}</pre>
|
|
3103
|
-
</
|
|
3430
|
+
</button>
|
|
3104
3431
|
` : nothing}
|
|
3105
3432
|
</div>
|
|
3106
3433
|
` : nothing}
|
|
@@ -3705,6 +4032,102 @@ KRChatbot.styles = css `
|
|
|
3705
4032
|
padding: 6px 0;
|
|
3706
4033
|
}
|
|
3707
4034
|
|
|
4035
|
+
/* Tool detail view — the full input/output for one tool call. */
|
|
4036
|
+
.tool-detail {
|
|
4037
|
+
display: flex;
|
|
4038
|
+
flex-direction: column;
|
|
4039
|
+
gap: 24px;
|
|
4040
|
+
padding: 16px 20px;
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
.tool-detail__section {
|
|
4044
|
+
display: flex;
|
|
4045
|
+
flex-direction: column;
|
|
4046
|
+
gap: 8px;
|
|
4047
|
+
min-width: 0;
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
/* Clickable section header — toggles the code block below. Button chrome is
|
|
4051
|
+
stripped so it reads as a heading row. */
|
|
4052
|
+
.tool-detail__section-head {
|
|
4053
|
+
display: flex;
|
|
4054
|
+
align-items: center;
|
|
4055
|
+
gap: 8px;
|
|
4056
|
+
appearance: none;
|
|
4057
|
+
background: none;
|
|
4058
|
+
border: none;
|
|
4059
|
+
width: 100%;
|
|
4060
|
+
font-family: inherit;
|
|
4061
|
+
text-align: left;
|
|
4062
|
+
cursor: pointer;
|
|
4063
|
+
border-radius: 4px;
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
.tool-detail__section-head:focus-visible {
|
|
4067
|
+
outline: 2px solid var(--kr-chatbot-primary);
|
|
4068
|
+
outline-offset: 2px;
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4071
|
+
/* Disclosure caret — the same "›" used on tool lines; rotates down when open. */
|
|
4072
|
+
.tool-detail__caret {
|
|
4073
|
+
flex-shrink: 0;
|
|
4074
|
+
font-size: 16px;
|
|
4075
|
+
color: rgba(0, 0, 0, 0.55);
|
|
4076
|
+
transition: transform 0.15s ease;
|
|
4077
|
+
}
|
|
4078
|
+
|
|
4079
|
+
.tool-detail__caret--open {
|
|
4080
|
+
transform: rotate(90deg);
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
.tool-detail__section-title {
|
|
4084
|
+
font-size: 12px;
|
|
4085
|
+
font-weight: 700;
|
|
4086
|
+
text-transform: uppercase;
|
|
4087
|
+
letter-spacing: 0.06em;
|
|
4088
|
+
color: #000000;
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
/* Copy icon-button — same treatment as the per-message copy button. */
|
|
4092
|
+
.tool-detail__copy {
|
|
4093
|
+
margin-left: auto;
|
|
4094
|
+
display: inline-flex;
|
|
4095
|
+
align-items: center;
|
|
4096
|
+
justify-content: center;
|
|
4097
|
+
width: 26px;
|
|
4098
|
+
height: 26px;
|
|
4099
|
+
padding: 0;
|
|
4100
|
+
background: none;
|
|
4101
|
+
border: none;
|
|
4102
|
+
border-radius: 6px;
|
|
4103
|
+
color: #000000;
|
|
4104
|
+
cursor: pointer;
|
|
4105
|
+
transition: background-color 0.15s, color 0.15s;
|
|
4106
|
+
}
|
|
4107
|
+
|
|
4108
|
+
.tool-detail__copy:hover {
|
|
4109
|
+
background: rgba(0, 0, 0, 0.06);
|
|
4110
|
+
}
|
|
4111
|
+
|
|
4112
|
+
.tool-detail__copy svg {
|
|
4113
|
+
width: 14px;
|
|
4114
|
+
height: 14px;
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
/* The code block — pretty-printed JSON, shown in full (no inner scroll). The
|
|
4118
|
+
drawer body is the only scroll container, so long inputs/outputs extend the
|
|
4119
|
+
page and you scroll the panel, not a nested box. */
|
|
4120
|
+
.tool-detail__code {
|
|
4121
|
+
margin: 0;
|
|
4122
|
+
padding: 0 18px;
|
|
4123
|
+
font-family: 'SF Mono', 'Fira Code', Monaco, monospace;
|
|
4124
|
+
font-size: 12px;
|
|
4125
|
+
line-height: 1.5;
|
|
4126
|
+
color: #000000;
|
|
4127
|
+
white-space: pre-wrap;
|
|
4128
|
+
word-break: break-word;
|
|
4129
|
+
}
|
|
4130
|
+
|
|
3708
4131
|
/* Messages */
|
|
3709
4132
|
/* Full-width scroll container — keeps the scrollbar at the panel edge. */
|
|
3710
4133
|
.messages {
|
|
@@ -3801,6 +4224,9 @@ KRChatbot.styles = css `
|
|
|
3801
4224
|
/* Tool-activity line — "the assistant ran X" row in the transcript. Same text
|
|
3802
4225
|
style as any other message (black, 14px); a leading › chevron marks it as a
|
|
3803
4226
|
step (in the same text style, not an icon). */
|
|
4227
|
+
/* The tool-activity line and its result preview are <button>s (they open the
|
|
4228
|
+
tool-detail drawer on click) — strip the native button chrome so they read
|
|
4229
|
+
as plain transcript text, then re-add the layout. */
|
|
3804
4230
|
.tool-activity {
|
|
3805
4231
|
align-self: flex-start;
|
|
3806
4232
|
display: flex;
|
|
@@ -3810,6 +4236,22 @@ KRChatbot.styles = css `
|
|
|
3810
4236
|
color: #000000;
|
|
3811
4237
|
/* Keep the chevron + name + input on a single line; input truncates if long. */
|
|
3812
4238
|
white-space: nowrap;
|
|
4239
|
+
/* Button reset */
|
|
4240
|
+
appearance: none;
|
|
4241
|
+
background: none;
|
|
4242
|
+
border: none;
|
|
4243
|
+
margin: 0;
|
|
4244
|
+
padding: 2px 4px;
|
|
4245
|
+
font-family: inherit;
|
|
4246
|
+
text-align: left;
|
|
4247
|
+
cursor: pointer;
|
|
4248
|
+
border-radius: 4px;
|
|
4249
|
+
max-width: 100%;
|
|
4250
|
+
}
|
|
4251
|
+
|
|
4252
|
+
.tool-activity:focus-visible {
|
|
4253
|
+
outline: 2px solid var(--kr-chatbot-primary);
|
|
4254
|
+
outline-offset: 1px;
|
|
3813
4255
|
}
|
|
3814
4256
|
|
|
3815
4257
|
.tool-activity--error {
|
|
@@ -3845,6 +4287,16 @@ KRChatbot.styles = css `
|
|
|
3845
4287
|
gap: 4px;
|
|
3846
4288
|
}
|
|
3847
4289
|
|
|
4290
|
+
/* One hover for the whole tool block: hovering anywhere over the header or its
|
|
4291
|
+
result underlines the tool name, input, and result together as a single
|
|
4292
|
+
link, rather than lighting up header and result separately. */
|
|
4293
|
+
.tool-activity-block:hover .tool-activity__label,
|
|
4294
|
+
.tool-activity-block:hover .tool-activity__input,
|
|
4295
|
+
.tool-activity-block:hover .tool-activity__result-body {
|
|
4296
|
+
text-decoration: underline;
|
|
4297
|
+
text-decoration-color: rgba(0, 0, 0, 0.25);
|
|
4298
|
+
}
|
|
4299
|
+
|
|
3848
4300
|
/* The result block indented under the activity line with a ⎿ tree connector,
|
|
3849
4301
|
like Claude CLI's tool output preview. Uses the normal message text style
|
|
3850
4302
|
(14px, black) — only the ⎿ connector is muted. */
|
|
@@ -3860,6 +4312,20 @@ KRChatbot.styles = css `
|
|
|
3860
4312
|
margin-left: 6px;
|
|
3861
4313
|
font-size: 14px;
|
|
3862
4314
|
color: #000000;
|
|
4315
|
+
/* Button reset — this preview row is also a click target for the detail view. */
|
|
4316
|
+
appearance: none;
|
|
4317
|
+
background: none;
|
|
4318
|
+
border: none;
|
|
4319
|
+
padding: 0;
|
|
4320
|
+
font-family: inherit;
|
|
4321
|
+
text-align: left;
|
|
4322
|
+
cursor: pointer;
|
|
4323
|
+
}
|
|
4324
|
+
|
|
4325
|
+
.tool-activity__result:focus-visible {
|
|
4326
|
+
outline: 2px solid var(--kr-chatbot-primary);
|
|
4327
|
+
outline-offset: 1px;
|
|
4328
|
+
border-radius: 4px;
|
|
3863
4329
|
}
|
|
3864
4330
|
|
|
3865
4331
|
.tool-activity__result--error {
|
|
@@ -5325,6 +5791,18 @@ __decorate([
|
|
|
5325
5791
|
__decorate([
|
|
5326
5792
|
state()
|
|
5327
5793
|
], KRChatbot.prototype, "_drawerView", void 0);
|
|
5794
|
+
__decorate([
|
|
5795
|
+
state()
|
|
5796
|
+
], KRChatbot.prototype, "_toolDetail", void 0);
|
|
5797
|
+
__decorate([
|
|
5798
|
+
state()
|
|
5799
|
+
], KRChatbot.prototype, "_toolDetailCopied", void 0);
|
|
5800
|
+
__decorate([
|
|
5801
|
+
state()
|
|
5802
|
+
], KRChatbot.prototype, "_toolDetailInputOpen", void 0);
|
|
5803
|
+
__decorate([
|
|
5804
|
+
state()
|
|
5805
|
+
], KRChatbot.prototype, "_toolDetailOutputOpen", void 0);
|
|
5328
5806
|
__decorate([
|
|
5329
5807
|
state()
|
|
5330
5808
|
], KRChatbot.prototype, "_drawerWidth", void 0);
|
|
@@ -5406,6 +5884,9 @@ __decorate([
|
|
|
5406
5884
|
__decorate([
|
|
5407
5885
|
query('.skill-menu')
|
|
5408
5886
|
], KRChatbot.prototype, "_skillMenuEl", void 0);
|
|
5887
|
+
__decorate([
|
|
5888
|
+
query('.drawer__body')
|
|
5889
|
+
], KRChatbot.prototype, "_drawerBodyEl", void 0);
|
|
5409
5890
|
KRChatbot = __decorate([
|
|
5410
5891
|
customElement('kr-chatbot')
|
|
5411
5892
|
], KRChatbot);
|