@flanksource/clicky-ui 0.2.15 → 0.2.17
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/chat.cjs +16 -0
- package/dist/chat.cjs.map +1 -1
- package/dist/chat.js +17 -1
- package/dist/chat.js.map +1 -1
- package/dist/components/Combobox.cjs +34 -11
- package/dist/components/Combobox.cjs.map +1 -1
- package/dist/components/Combobox.d.ts +8 -0
- package/dist/components/Combobox.d.ts.map +1 -1
- package/dist/components/Combobox.js +34 -11
- package/dist/components/Combobox.js.map +1 -1
- package/dist/components/DateTimePicker.cjs +3 -0
- package/dist/components/DateTimePicker.cjs.map +1 -1
- package/dist/components/DateTimePicker.d.ts +6 -2
- package/dist/components/DateTimePicker.d.ts.map +1 -1
- package/dist/components/DateTimePicker.js +3 -0
- package/dist/components/DateTimePicker.js.map +1 -1
- package/dist/components/Field.cjs +33 -0
- package/dist/components/Field.cjs.map +1 -0
- package/dist/components/Field.d.ts +27 -0
- package/dist/components/Field.d.ts.map +1 -0
- package/dist/components/Field.js +33 -0
- package/dist/components/Field.js.map +1 -0
- package/dist/components/FilterBar.cjs +81 -20
- package/dist/components/FilterBar.cjs.map +1 -1
- package/dist/components/FilterBar.d.ts +30 -0
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +83 -22
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/JsonSchemaForm.cjs +26 -3
- package/dist/components/JsonSchemaForm.cjs.map +1 -1
- package/dist/components/JsonSchemaForm.d.ts.map +1 -1
- package/dist/components/JsonSchemaForm.js +26 -3
- package/dist/components/JsonSchemaForm.js.map +1 -1
- package/dist/components/MultiSelect.cjs +2 -1
- package/dist/components/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect.d.ts +5 -1
- package/dist/components/MultiSelect.d.ts.map +1 -1
- package/dist/components/MultiSelect.js +2 -1
- package/dist/components/MultiSelect.js.map +1 -1
- package/dist/components/json-schema-form-fields.cjs +15 -8
- package/dist/components/json-schema-form-fields.cjs.map +1 -1
- package/dist/components/json-schema-form-fields.d.ts.map +1 -1
- package/dist/components/json-schema-form-fields.js +15 -8
- package/dist/components/json-schema-form-fields.js.map +1 -1
- package/dist/components/json-schema-form-preferences.cjs +4 -0
- package/dist/components/json-schema-form-preferences.cjs.map +1 -1
- package/dist/components/json-schema-form-preferences.d.ts +2 -0
- package/dist/components/json-schema-form-preferences.d.ts.map +1 -1
- package/dist/components/json-schema-form-preferences.js +4 -0
- package/dist/components/json-schema-form-preferences.js.map +1 -1
- package/dist/components/json-schema-form-render.cjs +1 -1
- package/dist/components/json-schema-form-render.cjs.map +1 -1
- package/dist/components/json-schema-form-render.d.ts.map +1 -1
- package/dist/components/json-schema-form-render.js +2 -2
- package/dist/components/json-schema-form-render.js.map +1 -1
- package/dist/components/json-schema-form-types.d.ts +4 -2
- package/dist/components/json-schema-form-types.d.ts.map +1 -1
- package/dist/components/json-schema-form-utils.cjs +13 -0
- package/dist/components/json-schema-form-utils.cjs.map +1 -1
- package/dist/components/json-schema-form-utils.d.ts +2 -0
- package/dist/components/json-schema-form-utils.d.ts.map +1 -1
- package/dist/components/json-schema-form-utils.js +13 -0
- package/dist/components/json-schema-form-utils.js.map +1 -1
- package/dist/components/loading.cjs +21 -8
- package/dist/components/loading.cjs.map +1 -1
- package/dist/components/loading.d.ts +6 -4
- package/dist/components/loading.d.ts.map +1 -1
- package/dist/components/loading.js +21 -8
- package/dist/components/loading.js.map +1 -1
- package/dist/components/use-form.cjs +27 -0
- package/dist/components/use-form.cjs.map +1 -0
- package/dist/components/use-form.d.ts +30 -0
- package/dist/components/use-form.d.ts.map +1 -0
- package/dist/components/use-form.js +27 -0
- package/dist/components/use-form.js.map +1 -0
- package/dist/components.cjs +7 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +4 -1
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +8 -1
- package/dist/components.js.map +1 -1
- package/dist/data/Markdown.cjs +14 -40
- package/dist/data/Markdown.cjs.map +1 -1
- package/dist/data/Markdown.d.ts +3 -0
- package/dist/data/Markdown.d.ts.map +1 -1
- package/dist/data/Markdown.js +14 -40
- package/dist/data/Markdown.js.map +1 -1
- package/dist/data/TaskProgress.cjs +22 -0
- package/dist/data/TaskProgress.cjs.map +1 -1
- package/dist/data/TaskProgress.js +22 -0
- package/dist/data/TaskProgress.js.map +1 -1
- package/dist/data/TaskSnapshot.cjs.map +1 -1
- package/dist/data/TaskSnapshot.d.ts +5 -0
- package/dist/data/TaskSnapshot.d.ts.map +1 -1
- package/dist/data/TaskSnapshot.js.map +1 -1
- package/dist/data/Tree.cjs +32 -1
- package/dist/data/Tree.cjs.map +1 -1
- package/dist/data/Tree.d.ts +9 -1
- package/dist/data/Tree.d.ts.map +1 -1
- package/dist/data/Tree.js +32 -1
- package/dist/data/Tree.js.map +1 -1
- package/dist/data/chat/Attachment.cjs +79 -0
- package/dist/data/chat/Attachment.cjs.map +1 -0
- package/dist/data/chat/Attachment.d.ts +18 -0
- package/dist/data/chat/Attachment.d.ts.map +1 -0
- package/dist/data/chat/Attachment.js +79 -0
- package/dist/data/chat/Attachment.js.map +1 -0
- package/dist/data/chat/Chat.cjs +71 -14
- package/dist/data/chat/Chat.cjs.map +1 -1
- package/dist/data/chat/Chat.d.ts +30 -12
- package/dist/data/chat/Chat.d.ts.map +1 -1
- package/dist/data/chat/Chat.fixtures.d.ts +9 -0
- package/dist/data/chat/Chat.fixtures.d.ts.map +1 -1
- package/dist/data/chat/Chat.js +73 -16
- package/dist/data/chat/Chat.js.map +1 -1
- package/dist/data/chat/Conversation.cjs +32 -14
- package/dist/data/chat/Conversation.cjs.map +1 -1
- package/dist/data/chat/Conversation.d.ts +5 -3
- package/dist/data/chat/Conversation.d.ts.map +1 -1
- package/dist/data/chat/Conversation.js +34 -16
- package/dist/data/chat/Conversation.js.map +1 -1
- package/dist/data/chat/Message.cjs +73 -14
- package/dist/data/chat/Message.cjs.map +1 -1
- package/dist/data/chat/Message.d.ts +11 -4
- package/dist/data/chat/Message.d.ts.map +1 -1
- package/dist/data/chat/Message.js +75 -16
- package/dist/data/chat/Message.js.map +1 -1
- package/dist/data/chat/MessageActions.cjs +54 -0
- package/dist/data/chat/MessageActions.cjs.map +1 -0
- package/dist/data/chat/MessageActions.d.ts +11 -0
- package/dist/data/chat/MessageActions.d.ts.map +1 -0
- package/dist/data/chat/MessageActions.js +54 -0
- package/dist/data/chat/MessageActions.js.map +1 -0
- package/dist/data/chat/ModelSelector.cjs +43 -0
- package/dist/data/chat/ModelSelector.cjs.map +1 -0
- package/dist/data/chat/ModelSelector.d.ts +22 -0
- package/dist/data/chat/ModelSelector.d.ts.map +1 -0
- package/dist/data/chat/ModelSelector.js +43 -0
- package/dist/data/chat/ModelSelector.js.map +1 -0
- package/dist/data/chat/PromptInput.cjs +28 -15
- package/dist/data/chat/PromptInput.cjs.map +1 -1
- package/dist/data/chat/PromptInput.d.ts +14 -8
- package/dist/data/chat/PromptInput.d.ts.map +1 -1
- package/dist/data/chat/PromptInput.js +28 -15
- package/dist/data/chat/PromptInput.js.map +1 -1
- package/dist/data/chat/Reasoning.cjs +37 -0
- package/dist/data/chat/Reasoning.cjs.map +1 -0
- package/dist/data/chat/Reasoning.d.ts +11 -0
- package/dist/data/chat/Reasoning.d.ts.map +1 -0
- package/dist/data/chat/Reasoning.js +37 -0
- package/dist/data/chat/Reasoning.js.map +1 -0
- package/dist/data/chat/Suggestion.cjs +20 -0
- package/dist/data/chat/Suggestion.cjs.map +1 -0
- package/dist/data/chat/Suggestion.d.ts +10 -0
- package/dist/data/chat/Suggestion.d.ts.map +1 -0
- package/dist/data/chat/Suggestion.js +20 -0
- package/dist/data/chat/Suggestion.js.map +1 -0
- package/dist/data/chat/ToolCall.cjs +26 -3
- package/dist/data/chat/ToolCall.cjs.map +1 -1
- package/dist/data/chat/ToolCall.d.ts +5 -1
- package/dist/data/chat/ToolCall.d.ts.map +1 -1
- package/dist/data/chat/ToolCall.js +26 -3
- package/dist/data/chat/ToolCall.js.map +1 -1
- package/dist/data/chat/index.d.ts +7 -2
- package/dist/data/chat/index.d.ts.map +1 -1
- package/dist/data/chat/types.cjs +16 -0
- package/dist/data/chat/types.cjs.map +1 -1
- package/dist/data/chat/types.d.ts +29 -2
- package/dist/data/chat/types.d.ts.map +1 -1
- package/dist/data/chat/types.js +16 -0
- package/dist/data/chat/types.js.map +1 -1
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/hooks/use-task-run.cjs +33 -20
- package/dist/hooks/use-task-run.cjs.map +1 -1
- package/dist/hooks/use-task-run.d.ts.map +1 -1
- package/dist/hooks/use-task-run.js +33 -20
- package/dist/hooks/use-task-run.js.map +1 -1
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/overlay/Modal.cjs +27 -23
- package/dist/overlay/Modal.cjs.map +1 -1
- package/dist/overlay/Modal.d.ts +7 -1
- package/dist/overlay/Modal.d.ts.map +1 -1
- package/dist/overlay/Modal.js +27 -23
- package/dist/overlay/Modal.js.map +1 -1
- package/dist/rpc/FilterForm.cjs +4 -4
- package/dist/rpc/FilterForm.cjs.map +1 -1
- package/dist/rpc/FilterForm.js +4 -4
- package/dist/rpc/FilterForm.js.map +1 -1
- package/dist/rpc/OperationCatalog.cjs +17 -21
- package/dist/rpc/OperationCatalog.cjs.map +1 -1
- package/dist/rpc/OperationCatalog.d.ts +3 -3
- package/dist/rpc/OperationCatalog.d.ts.map +1 -1
- package/dist/rpc/OperationCatalog.js +18 -22
- package/dist/rpc/OperationCatalog.js.map +1 -1
- package/dist/rpc/formMetadata.cjs +5 -3
- package/dist/rpc/formMetadata.cjs.map +1 -1
- package/dist/rpc/formMetadata.d.ts.map +1 -1
- package/dist/rpc/formMetadata.js +5 -3
- package/dist/rpc/formMetadata.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -1
package/dist/components.js
CHANGED
|
@@ -10,10 +10,12 @@ import { DatePicker } from "./components/DatePicker.js";
|
|
|
10
10
|
import { DateTimePicker } from "./components/DateTimePicker.js";
|
|
11
11
|
import { TimeRange } from "./components/TimeRange.js";
|
|
12
12
|
import { RangeSlider } from "./components/RangeSlider.js";
|
|
13
|
-
import { FilterBar } from "./components/FilterBar.js";
|
|
13
|
+
import { FilterBar, applyFilterExtensions } from "./components/FilterBar.js";
|
|
14
14
|
import { MultiSelect } from "./components/MultiSelect.js";
|
|
15
15
|
import { Select } from "./components/select.js";
|
|
16
16
|
import { Combobox } from "./components/Combobox.js";
|
|
17
|
+
import { Field } from "./components/Field.js";
|
|
18
|
+
import { useForm } from "./components/use-form.js";
|
|
17
19
|
import { TreePickerField } from "./components/TreePickerField.js";
|
|
18
20
|
import { WorkloadPicker } from "./components/WorkloadPicker.js";
|
|
19
21
|
import { kindForValue, loadedWorkloads, parseWorkloadKey, workloadKey } from "./components/workload-picker-utils.js";
|
|
@@ -30,6 +32,7 @@ import { DetailEmptyState, Section } from "./layout/Section.js";
|
|
|
30
32
|
import { DropdownMenu } from "./overlay/DropdownMenu.js";
|
|
31
33
|
import { HoverCard } from "./overlay/HoverCard.js";
|
|
32
34
|
import { Modal } from "./overlay/Modal.js";
|
|
35
|
+
import { useModalStack } from "./overlay/modalStack.js";
|
|
33
36
|
export {
|
|
34
37
|
Button,
|
|
35
38
|
CLICKY_FORMAT_OPTIONS,
|
|
@@ -42,6 +45,7 @@ export {
|
|
|
42
45
|
DensitySwitcher,
|
|
43
46
|
DetailEmptyState,
|
|
44
47
|
DropdownMenu,
|
|
48
|
+
Field,
|
|
45
49
|
FilterBar,
|
|
46
50
|
FormatOptionsDropdown,
|
|
47
51
|
HoverCard,
|
|
@@ -62,6 +66,7 @@ export {
|
|
|
62
66
|
TimeRange,
|
|
63
67
|
TreePickerField,
|
|
64
68
|
WorkloadPicker,
|
|
69
|
+
applyFilterExtensions,
|
|
65
70
|
buttonVariants,
|
|
66
71
|
effectiveProperties,
|
|
67
72
|
isOpenStringMap,
|
|
@@ -71,6 +76,8 @@ export {
|
|
|
71
76
|
parseWorkloadKey,
|
|
72
77
|
readPreferences,
|
|
73
78
|
resolveControl,
|
|
79
|
+
useForm,
|
|
80
|
+
useModalStack,
|
|
74
81
|
workloadKey,
|
|
75
82
|
writePreferences
|
|
76
83
|
};
|
package/dist/components.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"components.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/data/Markdown.cjs
CHANGED
|
@@ -26,56 +26,30 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
26
26
|
const react = require("react");
|
|
27
27
|
const utils = require("../lib/utils.cjs");
|
|
28
28
|
let loadPromise = null;
|
|
29
|
-
function
|
|
30
|
-
if (loadPromise)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const cls = depth <= 1 ? "font-bold text-base mt-2 mb-1" : depth === 2 ? "font-semibold text-sm mt-2 mb-1" : "font-semibold text-sm mt-2 mb-1";
|
|
37
|
-
return `<h${depth} class="${cls}">${text}</h${depth}>`;
|
|
38
|
-
};
|
|
39
|
-
renderer.link = ({ href, text }) => `<a href="${href}" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">${text}</a>`;
|
|
40
|
-
renderer.blockquote = ({ text }) => `<blockquote class="border-l-2 border-border pl-2 text-muted-foreground italic my-1">${text}</blockquote>`;
|
|
41
|
-
renderer.tablerow = ({ text }) => `<tr class="border-b border-border">${text}</tr>`;
|
|
42
|
-
renderer.list = function(token) {
|
|
43
|
-
const tag = token.ordered ? "ol" : "ul";
|
|
44
|
-
const cls = token.ordered ? "list-decimal ml-4 my-1" : "list-disc ml-4 my-1";
|
|
45
|
-
const body = token.items.map((item) => this.listitem(item)).join("");
|
|
46
|
-
return `<${tag} class="${cls}">${body}</${tag}>`;
|
|
47
|
-
};
|
|
48
|
-
renderer.listitem = ({ text }) => `<li class="my-0.5">${text}</li>`;
|
|
49
|
-
renderer.image = ({ href, text }) => `<img src="${href}" alt="${text}" class="max-w-full rounded my-1" />`;
|
|
50
|
-
renderer.hr = () => '<hr class="border-border my-2" />';
|
|
51
|
-
renderer.paragraph = ({ text }) => `<p class="mt-1.5">${text}</p>`;
|
|
52
|
-
return new Marked({ renderer, gfm: true, breaks: true });
|
|
53
|
-
});
|
|
29
|
+
function loadStreamdown() {
|
|
30
|
+
if (!loadPromise) {
|
|
31
|
+
loadPromise = import("streamdown").then((m) => m.Streamdown).catch((err) => {
|
|
32
|
+
console.warn("clicky-ui: streamdown not available; rendering markdown as plain text", err);
|
|
33
|
+
return null;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
54
36
|
return loadPromise;
|
|
55
37
|
}
|
|
56
38
|
function Markdown({ text, className }) {
|
|
57
|
-
const [
|
|
39
|
+
const [Streamdown, setStreamdown] = react.useState(null);
|
|
58
40
|
react.useEffect(() => {
|
|
59
41
|
let cancelled = false;
|
|
60
|
-
|
|
61
|
-
if (!cancelled)
|
|
62
|
-
}).catch(() => {
|
|
63
|
-
if (!cancelled) setHtml(text);
|
|
42
|
+
loadStreamdown().then((comp) => {
|
|
43
|
+
if (!cancelled) setStreamdown(() => comp);
|
|
64
44
|
});
|
|
65
45
|
return () => {
|
|
66
46
|
cancelled = true;
|
|
67
47
|
};
|
|
68
|
-
}, [
|
|
69
|
-
if (
|
|
70
|
-
return /* @__PURE__ */ jsxRuntime.jsx("pre", { className: utils.cn("
|
|
48
|
+
}, []);
|
|
49
|
+
if (!Streamdown) {
|
|
50
|
+
return /* @__PURE__ */ jsxRuntime.jsx("pre", { className: utils.cn("whitespace-pre-wrap text-sm", className), children: text });
|
|
71
51
|
}
|
|
72
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
73
|
-
"div",
|
|
74
|
-
{
|
|
75
|
-
className: utils.cn("prose prose-sm max-w-none dark:prose-invert", className),
|
|
76
|
-
dangerouslySetInnerHTML: { __html: html }
|
|
77
|
-
}
|
|
78
|
-
);
|
|
52
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: utils.cn("prose prose-sm max-w-none dark:prose-invert", className), children: /* @__PURE__ */ jsxRuntime.jsx(Streamdown, { parseIncompleteMarkdown: true, children: text }) });
|
|
79
53
|
}
|
|
80
54
|
exports.Markdown = Markdown;
|
|
81
55
|
//# sourceMappingURL=Markdown.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Markdown.cjs","sources":["../../src/data/Markdown.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type MarkdownProps = {\n /** Markdown source text. */\n text: string;\n /** Classes applied to the rendered markdown wrapper. */\n className?: string;\n};\n\ntype
|
|
1
|
+
{"version":3,"file":"Markdown.cjs","sources":["../../src/data/Markdown.tsx"],"sourcesContent":["import { useEffect, useState, type ComponentType } from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type MarkdownProps = {\n /** Markdown source text. */\n text: string;\n /** Classes applied to the rendered markdown wrapper. */\n className?: string;\n};\n\ntype StreamdownComponent = ComponentType<{\n children: string;\n className?: string;\n parseIncompleteMarkdown?: boolean;\n}>;\n\nlet loadPromise: Promise<StreamdownComponent | null> | null = null;\n\n// Streamdown is an optional peer dependency (like marked/shiki). It is loaded on\n// demand so the core library does not force it on consumers; while it loads (or\n// if it is absent) we fall back to preformatted text.\nfunction loadStreamdown(): Promise<StreamdownComponent | null> {\n if (!loadPromise) {\n loadPromise = import(\"streamdown\")\n .then((m) => m.Streamdown as unknown as StreamdownComponent)\n .catch((err) => {\n console.warn(\"clicky-ui: streamdown not available; rendering markdown as plain text\", err);\n return null;\n });\n }\n return loadPromise;\n}\n\n/** Renders markdown with Streamdown, which is purpose-built for streaming LLM\n * output (it gracefully handles incomplete markdown mid-stream). Falls back to\n * preformatted text until Streamdown loads or when it is not installed. */\nexport function Markdown({ text, className }: MarkdownProps) {\n const [Streamdown, setStreamdown] = useState<StreamdownComponent | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n loadStreamdown().then((comp) => {\n if (!cancelled) setStreamdown(() => comp);\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n if (!Streamdown) {\n return <pre className={cn(\"whitespace-pre-wrap text-sm\", className)}>{text}</pre>;\n }\n\n return (\n <div className={cn(\"prose prose-sm max-w-none dark:prose-invert\", className)}>\n <Streamdown parseIncompleteMarkdown>{text}</Streamdown>\n </div>\n );\n}\n"],"names":["useState","useEffect","cn","jsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBA,IAAI,cAA0D;AAK9D,SAAS,iBAAsD;AAC7D,MAAI,CAAC,aAAa;AAChB,kBAAc,OAAO,YAAY,EAC9B,KAAK,CAAC,MAAM,EAAE,UAA4C,EAC1D,MAAM,CAAC,QAAQ;AACd,cAAQ,KAAK,yEAAyE,GAAG;AACzF,aAAO;AAAA,IACT,CAAC;AAAA,EACL;AACA,SAAO;AACT;AAKO,SAAS,SAAS,EAAE,MAAM,aAA4B;AAC3D,QAAM,CAAC,YAAY,aAAa,IAAIA,MAAAA,SAAqC,IAAI;AAE7EC,QAAAA,UAAU,MAAM;AACd,QAAI,YAAY;AAChB,mBAAA,EAAiB,KAAK,CAAC,SAAS;AAC9B,UAAI,CAAC,UAAW,eAAc,MAAM,IAAI;AAAA,IAC1C,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAA,CAAE;AAEL,MAAI,CAAC,YAAY;AACf,0CAAQ,OAAA,EAAI,WAAWC,MAAAA,GAAG,+BAA+B,SAAS,GAAI,UAAA,MAAK;AAAA,EAC7E;AAEA,SACEC,2BAAAA,IAAC,OAAA,EAAI,WAAWD,MAAAA,GAAG,+CAA+C,SAAS,GACzE,UAAAC,2BAAAA,IAAC,YAAA,EAAW,yBAAuB,MAAE,UAAA,KAAA,CAAK,GAC5C;AAEJ;;"}
|
package/dist/data/Markdown.d.ts
CHANGED
|
@@ -4,5 +4,8 @@ export type MarkdownProps = {
|
|
|
4
4
|
/** Classes applied to the rendered markdown wrapper. */
|
|
5
5
|
className?: string;
|
|
6
6
|
};
|
|
7
|
+
/** Renders markdown with Streamdown, which is purpose-built for streaming LLM
|
|
8
|
+
* output (it gracefully handles incomplete markdown mid-stream). Falls back to
|
|
9
|
+
* preformatted text until Streamdown loads or when it is not installed. */
|
|
7
10
|
export declare function Markdown({ text, className }: MarkdownProps): import("react/jsx-runtime").JSX.Element;
|
|
8
11
|
//# sourceMappingURL=Markdown.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../src/data/Markdown.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG;IAC1B,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;
|
|
1
|
+
{"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../src/data/Markdown.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG;IAC1B,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAyBF;;4EAE4E;AAC5E,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,aAAa,2CAsB1D"}
|
package/dist/data/Markdown.js
CHANGED
|
@@ -2,56 +2,30 @@ import { jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState, useEffect } from "react";
|
|
3
3
|
import { cn } from "../lib/utils.js";
|
|
4
4
|
let loadPromise = null;
|
|
5
|
-
function
|
|
6
|
-
if (loadPromise)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const cls = depth <= 1 ? "font-bold text-base mt-2 mb-1" : depth === 2 ? "font-semibold text-sm mt-2 mb-1" : "font-semibold text-sm mt-2 mb-1";
|
|
13
|
-
return `<h${depth} class="${cls}">${text}</h${depth}>`;
|
|
14
|
-
};
|
|
15
|
-
renderer.link = ({ href, text }) => `<a href="${href}" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">${text}</a>`;
|
|
16
|
-
renderer.blockquote = ({ text }) => `<blockquote class="border-l-2 border-border pl-2 text-muted-foreground italic my-1">${text}</blockquote>`;
|
|
17
|
-
renderer.tablerow = ({ text }) => `<tr class="border-b border-border">${text}</tr>`;
|
|
18
|
-
renderer.list = function(token) {
|
|
19
|
-
const tag = token.ordered ? "ol" : "ul";
|
|
20
|
-
const cls = token.ordered ? "list-decimal ml-4 my-1" : "list-disc ml-4 my-1";
|
|
21
|
-
const body = token.items.map((item) => this.listitem(item)).join("");
|
|
22
|
-
return `<${tag} class="${cls}">${body}</${tag}>`;
|
|
23
|
-
};
|
|
24
|
-
renderer.listitem = ({ text }) => `<li class="my-0.5">${text}</li>`;
|
|
25
|
-
renderer.image = ({ href, text }) => `<img src="${href}" alt="${text}" class="max-w-full rounded my-1" />`;
|
|
26
|
-
renderer.hr = () => '<hr class="border-border my-2" />';
|
|
27
|
-
renderer.paragraph = ({ text }) => `<p class="mt-1.5">${text}</p>`;
|
|
28
|
-
return new Marked({ renderer, gfm: true, breaks: true });
|
|
29
|
-
});
|
|
5
|
+
function loadStreamdown() {
|
|
6
|
+
if (!loadPromise) {
|
|
7
|
+
loadPromise = import("streamdown").then((m) => m.Streamdown).catch((err) => {
|
|
8
|
+
console.warn("clicky-ui: streamdown not available; rendering markdown as plain text", err);
|
|
9
|
+
return null;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
30
12
|
return loadPromise;
|
|
31
13
|
}
|
|
32
14
|
function Markdown({ text, className }) {
|
|
33
|
-
const [
|
|
15
|
+
const [Streamdown, setStreamdown] = useState(null);
|
|
34
16
|
useEffect(() => {
|
|
35
17
|
let cancelled = false;
|
|
36
|
-
|
|
37
|
-
if (!cancelled)
|
|
38
|
-
}).catch(() => {
|
|
39
|
-
if (!cancelled) setHtml(text);
|
|
18
|
+
loadStreamdown().then((comp) => {
|
|
19
|
+
if (!cancelled) setStreamdown(() => comp);
|
|
40
20
|
});
|
|
41
21
|
return () => {
|
|
42
22
|
cancelled = true;
|
|
43
23
|
};
|
|
44
|
-
}, [
|
|
45
|
-
if (
|
|
46
|
-
return /* @__PURE__ */ jsx("pre", { className: cn("
|
|
24
|
+
}, []);
|
|
25
|
+
if (!Streamdown) {
|
|
26
|
+
return /* @__PURE__ */ jsx("pre", { className: cn("whitespace-pre-wrap text-sm", className), children: text });
|
|
47
27
|
}
|
|
48
|
-
return /* @__PURE__ */ jsx(
|
|
49
|
-
"div",
|
|
50
|
-
{
|
|
51
|
-
className: cn("prose prose-sm max-w-none dark:prose-invert", className),
|
|
52
|
-
dangerouslySetInnerHTML: { __html: html }
|
|
53
|
-
}
|
|
54
|
-
);
|
|
28
|
+
return /* @__PURE__ */ jsx("div", { className: cn("prose prose-sm max-w-none dark:prose-invert", className), children: /* @__PURE__ */ jsx(Streamdown, { parseIncompleteMarkdown: true, children: text }) });
|
|
55
29
|
}
|
|
56
30
|
export {
|
|
57
31
|
Markdown
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Markdown.js","sources":["../../src/data/Markdown.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type MarkdownProps = {\n /** Markdown source text. */\n text: string;\n /** Classes applied to the rendered markdown wrapper. */\n className?: string;\n};\n\ntype
|
|
1
|
+
{"version":3,"file":"Markdown.js","sources":["../../src/data/Markdown.tsx"],"sourcesContent":["import { useEffect, useState, type ComponentType } from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type MarkdownProps = {\n /** Markdown source text. */\n text: string;\n /** Classes applied to the rendered markdown wrapper. */\n className?: string;\n};\n\ntype StreamdownComponent = ComponentType<{\n children: string;\n className?: string;\n parseIncompleteMarkdown?: boolean;\n}>;\n\nlet loadPromise: Promise<StreamdownComponent | null> | null = null;\n\n// Streamdown is an optional peer dependency (like marked/shiki). It is loaded on\n// demand so the core library does not force it on consumers; while it loads (or\n// if it is absent) we fall back to preformatted text.\nfunction loadStreamdown(): Promise<StreamdownComponent | null> {\n if (!loadPromise) {\n loadPromise = import(\"streamdown\")\n .then((m) => m.Streamdown as unknown as StreamdownComponent)\n .catch((err) => {\n console.warn(\"clicky-ui: streamdown not available; rendering markdown as plain text\", err);\n return null;\n });\n }\n return loadPromise;\n}\n\n/** Renders markdown with Streamdown, which is purpose-built for streaming LLM\n * output (it gracefully handles incomplete markdown mid-stream). Falls back to\n * preformatted text until Streamdown loads or when it is not installed. */\nexport function Markdown({ text, className }: MarkdownProps) {\n const [Streamdown, setStreamdown] = useState<StreamdownComponent | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n loadStreamdown().then((comp) => {\n if (!cancelled) setStreamdown(() => comp);\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n if (!Streamdown) {\n return <pre className={cn(\"whitespace-pre-wrap text-sm\", className)}>{text}</pre>;\n }\n\n return (\n <div className={cn(\"prose prose-sm max-w-none dark:prose-invert\", className)}>\n <Streamdown parseIncompleteMarkdown>{text}</Streamdown>\n </div>\n );\n}\n"],"names":[],"mappings":";;;AAgBA,IAAI,cAA0D;AAK9D,SAAS,iBAAsD;AAC7D,MAAI,CAAC,aAAa;AAChB,kBAAc,OAAO,YAAY,EAC9B,KAAK,CAAC,MAAM,EAAE,UAA4C,EAC1D,MAAM,CAAC,QAAQ;AACd,cAAQ,KAAK,yEAAyE,GAAG;AACzF,aAAO;AAAA,IACT,CAAC;AAAA,EACL;AACA,SAAO;AACT;AAKO,SAAS,SAAS,EAAE,MAAM,aAA4B;AAC3D,QAAM,CAAC,YAAY,aAAa,IAAI,SAAqC,IAAI;AAE7E,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,mBAAA,EAAiB,KAAK,CAAC,SAAS;AAC9B,UAAI,CAAC,UAAW,eAAc,MAAM,IAAI;AAAA,IAC1C,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAA,CAAE;AAEL,MAAI,CAAC,YAAY;AACf,+BAAQ,OAAA,EAAI,WAAW,GAAG,+BAA+B,SAAS,GAAI,UAAA,MAAK;AAAA,EAC7E;AAEA,SACE,oBAAC,OAAA,EAAI,WAAW,GAAG,+CAA+C,SAAS,GACzE,UAAA,oBAAC,YAAA,EAAW,yBAAuB,MAAE,UAAA,KAAA,CAAK,GAC5C;AAEJ;"}
|
|
@@ -92,6 +92,9 @@ function TaskRow({ task: t }) {
|
|
|
92
92
|
const logs = t.logs ?? [];
|
|
93
93
|
const hasLogs = logs.length > 0;
|
|
94
94
|
const latestWarn = t.error ? void 0 : logs.filter((l) => l.level === "warn").at(-1);
|
|
95
|
+
const done = t.progress ?? 0;
|
|
96
|
+
const hasProgress = typeof t.maxValue === "number" && t.maxValue > 0 && done > 0;
|
|
97
|
+
const pct = hasProgress ? Math.round(done / t.maxValue * 100) : 0;
|
|
95
98
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
96
99
|
"div",
|
|
97
100
|
{
|
|
@@ -112,6 +115,25 @@ function TaskRow({ task: t }) {
|
|
|
112
115
|
] }),
|
|
113
116
|
t.duration && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: t.duration })
|
|
114
117
|
] }),
|
|
118
|
+
(t.description || hasProgress) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-0.5 flex items-center gap-2", children: [
|
|
119
|
+
t.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-xs text-muted-foreground", children: t.description }),
|
|
120
|
+
hasProgress && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: [
|
|
121
|
+
done,
|
|
122
|
+
"/",
|
|
123
|
+
t.maxValue,
|
|
124
|
+
" · ",
|
|
125
|
+
pct,
|
|
126
|
+
"%"
|
|
127
|
+
] })
|
|
128
|
+
] }),
|
|
129
|
+
hasProgress && t.status === "running" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
130
|
+
ProgressBar.ProgressBar,
|
|
131
|
+
{
|
|
132
|
+
segments: [{ count: done, color: "bg-blue-500", label: "done" }],
|
|
133
|
+
total: t.maxValue,
|
|
134
|
+
height: "h-1"
|
|
135
|
+
}
|
|
136
|
+
) }),
|
|
115
137
|
expanded && hasLogs && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2", children: logs.map((l, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("text-xs", taskStatus.logLevelColor(l.level)), children: [
|
|
116
138
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1 font-mono text-muted-foreground", children: l.level.substring(0, 3) }),
|
|
117
139
|
l.message
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskProgress.cjs","sources":["../../src/data/TaskProgress.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport type { LogEntry, TaskSnapshot } from \"./TaskSnapshot\";\nimport {\n bucketTasks,\n logLevelColor,\n taskSegments,\n taskStatusBg,\n taskStatusColor,\n taskStatusIcon,\n} from \"./task-status\";\n\n// TaskProgress renders one or more clicky task runs (groups) and their child\n// tasks: a segmented progress bar per group plus collapsible per-task rows with\n// status icon, duration, error, and expandable logs. Presentational only —\n// callers feed it snapshots from useTaskRun (SSE) or any other source.\n\n// Iconify web component, loaded by the host app (same as clicky's task UI). The\n// intrinsic element keeps TaskProgress free of clicky-ui's icon registry.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n \"iconify-icon\": { icon: string; class?: string };\n }\n }\n}\n\nconst MAX_COMPLETED = 5;\nconst MAX_PENDING = 3;\n\nexport interface TaskProgressProps {\n snapshots: TaskSnapshot[];\n title?: string;\n /** Tighter spacing for embedding in a panel. */\n compact?: boolean;\n className?: string;\n}\n\nexport function TaskProgress({ snapshots, title, compact, className }: TaskProgressProps) {\n const groups = snapshots.filter((s) => s.type === \"group\");\n const tasks = snapshots.filter((s) => s.type === \"task\");\n\n if (groups.length === 0) {\n return (\n <div className={cn(\"text-sm text-muted-foreground\", className)}>\n {title ? `${title}: ` : \"\"}No tasks yet.\n </div>\n );\n }\n\n return (\n <div className={cn(compact ? \"space-y-2\" : \"space-y-3\", className)}>\n {title && <h2 className=\"text-sm font-semibold\">{title}</h2>}\n {groups.map((g) => (\n <TaskGroupCard\n key={g.groupId || g.id}\n group={g}\n tasks={tasks.filter((t) => t.groupId === g.groupId || t.group === g.id)}\n compact={compact}\n />\n ))}\n </div>\n );\n}\n\nfunction isFailedOrWarn(t: TaskSnapshot): boolean {\n return t.status === \"failed\" || t.status === \"FAIL\" || t.status === \"ERR\" || t.status === \"warning\";\n}\n\nfunction TaskGroupCard({\n group: g,\n tasks,\n compact,\n}: {\n group: TaskSnapshot;\n tasks: TaskSnapshot[];\n compact: boolean | undefined;\n}) {\n const [showAll, setShowAll] = useState(false);\n const total = g.total ?? tasks.length;\n const counts = bucketTasks(tasks);\n const done = counts.ok + counts.warn + counts.fail;\n const progress = total > 0 ? `${done}/${total}` : \"\";\n\n const running = tasks.filter((t) => t.status === \"running\");\n const pending = tasks.filter((t) => t.status === \"pending\");\n const completed = tasks.filter((t) => t.status !== \"running\" && t.status !== \"pending\");\n const alwaysShow = completed.filter(isFailedOrWarn);\n const collapsible = completed.filter((t) => !isFailedOrWarn(t));\n\n const hiddenCompleted = collapsible.length - MAX_COMPLETED;\n const collapseCompleted = !showAll && hiddenCompleted > 0;\n const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;\n\n const hiddenPending = pending.length - MAX_PENDING;\n const collapsePending = !showAll && hiddenPending > 0;\n const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;\n\n const isTerminal = g.status !== \"running\" && g.status !== \"pending\";\n\n return (\n <div className={cn(\"rounded-lg border bg-card\", compact ? \"p-3\" : \"p-4\")}>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"flex items-center gap-1.5 text-sm font-semibold\">\n <iconify-icon icon={taskStatusIcon(g.status)} class={taskStatusColor(g.status)} />\n <span>{g.name}</span>\n {progress && <span className=\"text-xs text-muted-foreground\">{progress}</span>}\n {g.kind && (\n <span className=\"rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {g.kind}\n </span>\n )}\n </h3>\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(g.status),\n taskStatusBg(g.status),\n )}\n >\n {g.status}\n </span>\n )}\n </div>\n\n {total > 0 && (\n <div className=\"mb-3\">\n <ProgressBar segments={taskSegments(counts)} total={total} height=\"h-1.5\" />\n </div>\n )}\n\n {hiddenCompleted > 0 || hiddenPending > 0 ? (\n <button\n type=\"button\"\n className=\"border-b py-1.5 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? \"▲ collapse\"\n : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`}\n </button>\n ) : null}\n\n {[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => (\n <TaskRow key={t.id} task={t} />\n ))}\n </div>\n );\n}\n\nfunction TaskRow({ task: t }: { task: TaskSnapshot }) {\n const [expanded, setExpanded] = useState(false);\n const logs: LogEntry[] = t.logs ?? [];\n const hasLogs = logs.length > 0;\n // Promote the latest warning message inline so a `warning` row shows its\n // reason without expanding. Suppressed when an error is already shown.\n const latestWarn = t.error ? undefined : logs.filter((l) => l.level === \"warn\").at(-1);\n\n return (\n <div\n className={cn(\n \"flex items-start gap-3 border-b py-2 last:border-0\",\n hasLogs && \"-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50\",\n )}\n onClick={hasLogs ? () => setExpanded((v) => !v) : undefined}\n >\n <iconify-icon icon={taskStatusIcon(t.status)} class={cn(taskStatusColor(t.status), \"mt-0.5 text-lg\")} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-sm font-medium\">\n <span className=\"truncate\">{t.name}</span>\n {t.error && <span className=\"truncate text-xs font-normal text-red-500\">{t.error}</span>}\n {latestWarn && (\n <span className={cn(\"truncate text-xs font-normal\", logLevelColor(\"warn\"))}>\n {latestWarn.message}\n </span>\n )}\n {hasLogs && (\n <span className=\"inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground\">\n {logs.length}\n </span>\n )}\n </span>\n {t.duration && <span className=\"shrink-0 text-xs text-muted-foreground\">{t.duration}</span>}\n </div>\n {expanded && hasLogs && (\n <div className=\"mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2\">\n {logs.map((l, i) => (\n <div key={i} className={cn(\"text-xs\", logLevelColor(l.level))}>\n <span className=\"mr-1 font-mono text-muted-foreground\">{l.level.substring(0, 3)}</span>\n {l.message}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":["cn","jsxs","jsx","useState","bucketTasks","taskStatusIcon","taskStatusColor","taskStatusBg","ProgressBar","taskSegments","logLevelColor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TaskProgress.cjs","sources":["../../src/data/TaskProgress.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport type { LogEntry, TaskSnapshot } from \"./TaskSnapshot\";\nimport {\n bucketTasks,\n logLevelColor,\n taskSegments,\n taskStatusBg,\n taskStatusColor,\n taskStatusIcon,\n} from \"./task-status\";\n\n// TaskProgress renders one or more clicky task runs (groups) and their child\n// tasks: a segmented progress bar per group plus collapsible per-task rows with\n// status icon, duration, error, and expandable logs. Presentational only —\n// callers feed it snapshots from useTaskRun (SSE) or any other source.\n\n// Iconify web component, loaded by the host app (same as clicky's task UI). The\n// intrinsic element keeps TaskProgress free of clicky-ui's icon registry.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n \"iconify-icon\": { icon: string; class?: string };\n }\n }\n}\n\nconst MAX_COMPLETED = 5;\nconst MAX_PENDING = 3;\n\nexport interface TaskProgressProps {\n snapshots: TaskSnapshot[];\n title?: string;\n /** Tighter spacing for embedding in a panel. */\n compact?: boolean;\n className?: string;\n}\n\nexport function TaskProgress({ snapshots, title, compact, className }: TaskProgressProps) {\n const groups = snapshots.filter((s) => s.type === \"group\");\n const tasks = snapshots.filter((s) => s.type === \"task\");\n\n if (groups.length === 0) {\n return (\n <div className={cn(\"text-sm text-muted-foreground\", className)}>\n {title ? `${title}: ` : \"\"}No tasks yet.\n </div>\n );\n }\n\n return (\n <div className={cn(compact ? \"space-y-2\" : \"space-y-3\", className)}>\n {title && <h2 className=\"text-sm font-semibold\">{title}</h2>}\n {groups.map((g) => (\n <TaskGroupCard\n key={g.groupId || g.id}\n group={g}\n tasks={tasks.filter((t) => t.groupId === g.groupId || t.group === g.id)}\n compact={compact}\n />\n ))}\n </div>\n );\n}\n\nfunction isFailedOrWarn(t: TaskSnapshot): boolean {\n return t.status === \"failed\" || t.status === \"FAIL\" || t.status === \"ERR\" || t.status === \"warning\";\n}\n\nfunction TaskGroupCard({\n group: g,\n tasks,\n compact,\n}: {\n group: TaskSnapshot;\n tasks: TaskSnapshot[];\n compact: boolean | undefined;\n}) {\n const [showAll, setShowAll] = useState(false);\n const total = g.total ?? tasks.length;\n const counts = bucketTasks(tasks);\n const done = counts.ok + counts.warn + counts.fail;\n const progress = total > 0 ? `${done}/${total}` : \"\";\n\n const running = tasks.filter((t) => t.status === \"running\");\n const pending = tasks.filter((t) => t.status === \"pending\");\n const completed = tasks.filter((t) => t.status !== \"running\" && t.status !== \"pending\");\n const alwaysShow = completed.filter(isFailedOrWarn);\n const collapsible = completed.filter((t) => !isFailedOrWarn(t));\n\n const hiddenCompleted = collapsible.length - MAX_COMPLETED;\n const collapseCompleted = !showAll && hiddenCompleted > 0;\n const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;\n\n const hiddenPending = pending.length - MAX_PENDING;\n const collapsePending = !showAll && hiddenPending > 0;\n const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;\n\n const isTerminal = g.status !== \"running\" && g.status !== \"pending\";\n\n return (\n <div className={cn(\"rounded-lg border bg-card\", compact ? \"p-3\" : \"p-4\")}>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"flex items-center gap-1.5 text-sm font-semibold\">\n <iconify-icon icon={taskStatusIcon(g.status)} class={taskStatusColor(g.status)} />\n <span>{g.name}</span>\n {progress && <span className=\"text-xs text-muted-foreground\">{progress}</span>}\n {g.kind && (\n <span className=\"rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {g.kind}\n </span>\n )}\n </h3>\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(g.status),\n taskStatusBg(g.status),\n )}\n >\n {g.status}\n </span>\n )}\n </div>\n\n {total > 0 && (\n <div className=\"mb-3\">\n <ProgressBar segments={taskSegments(counts)} total={total} height=\"h-1.5\" />\n </div>\n )}\n\n {hiddenCompleted > 0 || hiddenPending > 0 ? (\n <button\n type=\"button\"\n className=\"border-b py-1.5 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? \"▲ collapse\"\n : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`}\n </button>\n ) : null}\n\n {[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => (\n <TaskRow key={t.id} task={t} />\n ))}\n </div>\n );\n}\n\nfunction TaskRow({ task: t }: { task: TaskSnapshot }) {\n const [expanded, setExpanded] = useState(false);\n const logs: LogEntry[] = t.logs ?? [];\n const hasLogs = logs.length > 0;\n // Promote the latest warning message inline so a `warning` row shows its\n // reason without expanding. Suppressed when an error is already shown.\n const latestWarn = t.error ? undefined : logs.filter((l) => l.level === \"warn\").at(-1);\n // Bounded per-task progress (e.g. records emitted in a phase): render an x/y\n // count, a percentage, and a thin bar while the task runs.\n const done = t.progress ?? 0;\n // Only surface bounded progress once work has actually started — an untouched\n // task should not render a \"0/100 · 0%\" placeholder.\n const hasProgress = typeof t.maxValue === \"number\" && t.maxValue > 0 && done > 0;\n const pct = hasProgress ? Math.round((done / (t.maxValue as number)) * 100) : 0;\n\n return (\n <div\n className={cn(\n \"flex items-start gap-3 border-b py-2 last:border-0\",\n hasLogs && \"-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50\",\n )}\n onClick={hasLogs ? () => setExpanded((v) => !v) : undefined}\n >\n <iconify-icon icon={taskStatusIcon(t.status)} class={cn(taskStatusColor(t.status), \"mt-0.5 text-lg\")} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-sm font-medium\">\n <span className=\"truncate\">{t.name}</span>\n {t.error && <span className=\"truncate text-xs font-normal text-red-500\">{t.error}</span>}\n {latestWarn && (\n <span className={cn(\"truncate text-xs font-normal\", logLevelColor(\"warn\"))}>\n {latestWarn.message}\n </span>\n )}\n {hasLogs && (\n <span className=\"inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground\">\n {logs.length}\n </span>\n )}\n </span>\n {t.duration && <span className=\"shrink-0 text-xs text-muted-foreground\">{t.duration}</span>}\n </div>\n {(t.description || hasProgress) && (\n <div className=\"mt-0.5 flex items-center gap-2\">\n {t.description && (\n <span className=\"min-w-0 flex-1 truncate text-xs text-muted-foreground\">{t.description}</span>\n )}\n {hasProgress && (\n <span className=\"shrink-0 text-xs tabular-nums text-muted-foreground\">\n {done}/{t.maxValue} · {pct}%\n </span>\n )}\n </div>\n )}\n {hasProgress && t.status === \"running\" && (\n <div className=\"mt-1\">\n <ProgressBar\n segments={[{ count: done, color: \"bg-blue-500\", label: \"done\" }]}\n total={t.maxValue as number}\n height=\"h-1\"\n />\n </div>\n )}\n {expanded && hasLogs && (\n <div className=\"mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2\">\n {logs.map((l, i) => (\n <div key={i} className={cn(\"text-xs\", logLevelColor(l.level))}>\n <span className=\"mr-1 font-mono text-muted-foreground\">{l.level.substring(0, 3)}</span>\n {l.message}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":["cn","jsxs","jsx","useState","bucketTasks","taskStatusIcon","taskStatusColor","taskStatusBg","ProgressBar","taskSegments","logLevelColor"],"mappings":"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"}
|
|
@@ -90,6 +90,9 @@ function TaskRow({ task: t }) {
|
|
|
90
90
|
const logs = t.logs ?? [];
|
|
91
91
|
const hasLogs = logs.length > 0;
|
|
92
92
|
const latestWarn = t.error ? void 0 : logs.filter((l) => l.level === "warn").at(-1);
|
|
93
|
+
const done = t.progress ?? 0;
|
|
94
|
+
const hasProgress = typeof t.maxValue === "number" && t.maxValue > 0 && done > 0;
|
|
95
|
+
const pct = hasProgress ? Math.round(done / t.maxValue * 100) : 0;
|
|
93
96
|
return /* @__PURE__ */ jsxs(
|
|
94
97
|
"div",
|
|
95
98
|
{
|
|
@@ -110,6 +113,25 @@ function TaskRow({ task: t }) {
|
|
|
110
113
|
] }),
|
|
111
114
|
t.duration && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: t.duration })
|
|
112
115
|
] }),
|
|
116
|
+
(t.description || hasProgress) && /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex items-center gap-2", children: [
|
|
117
|
+
t.description && /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs text-muted-foreground", children: t.description }),
|
|
118
|
+
hasProgress && /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: [
|
|
119
|
+
done,
|
|
120
|
+
"/",
|
|
121
|
+
t.maxValue,
|
|
122
|
+
" · ",
|
|
123
|
+
pct,
|
|
124
|
+
"%"
|
|
125
|
+
] })
|
|
126
|
+
] }),
|
|
127
|
+
hasProgress && t.status === "running" && /* @__PURE__ */ jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsx(
|
|
128
|
+
ProgressBar,
|
|
129
|
+
{
|
|
130
|
+
segments: [{ count: done, color: "bg-blue-500", label: "done" }],
|
|
131
|
+
total: t.maxValue,
|
|
132
|
+
height: "h-1"
|
|
133
|
+
}
|
|
134
|
+
) }),
|
|
113
135
|
expanded && hasLogs && /* @__PURE__ */ jsx("div", { className: "mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2", children: logs.map((l, i) => /* @__PURE__ */ jsxs("div", { className: cn("text-xs", logLevelColor(l.level)), children: [
|
|
114
136
|
/* @__PURE__ */ jsx("span", { className: "mr-1 font-mono text-muted-foreground", children: l.level.substring(0, 3) }),
|
|
115
137
|
l.message
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskProgress.js","sources":["../../src/data/TaskProgress.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport type { LogEntry, TaskSnapshot } from \"./TaskSnapshot\";\nimport {\n bucketTasks,\n logLevelColor,\n taskSegments,\n taskStatusBg,\n taskStatusColor,\n taskStatusIcon,\n} from \"./task-status\";\n\n// TaskProgress renders one or more clicky task runs (groups) and their child\n// tasks: a segmented progress bar per group plus collapsible per-task rows with\n// status icon, duration, error, and expandable logs. Presentational only —\n// callers feed it snapshots from useTaskRun (SSE) or any other source.\n\n// Iconify web component, loaded by the host app (same as clicky's task UI). The\n// intrinsic element keeps TaskProgress free of clicky-ui's icon registry.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n \"iconify-icon\": { icon: string; class?: string };\n }\n }\n}\n\nconst MAX_COMPLETED = 5;\nconst MAX_PENDING = 3;\n\nexport interface TaskProgressProps {\n snapshots: TaskSnapshot[];\n title?: string;\n /** Tighter spacing for embedding in a panel. */\n compact?: boolean;\n className?: string;\n}\n\nexport function TaskProgress({ snapshots, title, compact, className }: TaskProgressProps) {\n const groups = snapshots.filter((s) => s.type === \"group\");\n const tasks = snapshots.filter((s) => s.type === \"task\");\n\n if (groups.length === 0) {\n return (\n <div className={cn(\"text-sm text-muted-foreground\", className)}>\n {title ? `${title}: ` : \"\"}No tasks yet.\n </div>\n );\n }\n\n return (\n <div className={cn(compact ? \"space-y-2\" : \"space-y-3\", className)}>\n {title && <h2 className=\"text-sm font-semibold\">{title}</h2>}\n {groups.map((g) => (\n <TaskGroupCard\n key={g.groupId || g.id}\n group={g}\n tasks={tasks.filter((t) => t.groupId === g.groupId || t.group === g.id)}\n compact={compact}\n />\n ))}\n </div>\n );\n}\n\nfunction isFailedOrWarn(t: TaskSnapshot): boolean {\n return t.status === \"failed\" || t.status === \"FAIL\" || t.status === \"ERR\" || t.status === \"warning\";\n}\n\nfunction TaskGroupCard({\n group: g,\n tasks,\n compact,\n}: {\n group: TaskSnapshot;\n tasks: TaskSnapshot[];\n compact: boolean | undefined;\n}) {\n const [showAll, setShowAll] = useState(false);\n const total = g.total ?? tasks.length;\n const counts = bucketTasks(tasks);\n const done = counts.ok + counts.warn + counts.fail;\n const progress = total > 0 ? `${done}/${total}` : \"\";\n\n const running = tasks.filter((t) => t.status === \"running\");\n const pending = tasks.filter((t) => t.status === \"pending\");\n const completed = tasks.filter((t) => t.status !== \"running\" && t.status !== \"pending\");\n const alwaysShow = completed.filter(isFailedOrWarn);\n const collapsible = completed.filter((t) => !isFailedOrWarn(t));\n\n const hiddenCompleted = collapsible.length - MAX_COMPLETED;\n const collapseCompleted = !showAll && hiddenCompleted > 0;\n const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;\n\n const hiddenPending = pending.length - MAX_PENDING;\n const collapsePending = !showAll && hiddenPending > 0;\n const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;\n\n const isTerminal = g.status !== \"running\" && g.status !== \"pending\";\n\n return (\n <div className={cn(\"rounded-lg border bg-card\", compact ? \"p-3\" : \"p-4\")}>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"flex items-center gap-1.5 text-sm font-semibold\">\n <iconify-icon icon={taskStatusIcon(g.status)} class={taskStatusColor(g.status)} />\n <span>{g.name}</span>\n {progress && <span className=\"text-xs text-muted-foreground\">{progress}</span>}\n {g.kind && (\n <span className=\"rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {g.kind}\n </span>\n )}\n </h3>\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(g.status),\n taskStatusBg(g.status),\n )}\n >\n {g.status}\n </span>\n )}\n </div>\n\n {total > 0 && (\n <div className=\"mb-3\">\n <ProgressBar segments={taskSegments(counts)} total={total} height=\"h-1.5\" />\n </div>\n )}\n\n {hiddenCompleted > 0 || hiddenPending > 0 ? (\n <button\n type=\"button\"\n className=\"border-b py-1.5 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? \"▲ collapse\"\n : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`}\n </button>\n ) : null}\n\n {[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => (\n <TaskRow key={t.id} task={t} />\n ))}\n </div>\n );\n}\n\nfunction TaskRow({ task: t }: { task: TaskSnapshot }) {\n const [expanded, setExpanded] = useState(false);\n const logs: LogEntry[] = t.logs ?? [];\n const hasLogs = logs.length > 0;\n // Promote the latest warning message inline so a `warning` row shows its\n // reason without expanding. Suppressed when an error is already shown.\n const latestWarn = t.error ? undefined : logs.filter((l) => l.level === \"warn\").at(-1);\n\n return (\n <div\n className={cn(\n \"flex items-start gap-3 border-b py-2 last:border-0\",\n hasLogs && \"-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50\",\n )}\n onClick={hasLogs ? () => setExpanded((v) => !v) : undefined}\n >\n <iconify-icon icon={taskStatusIcon(t.status)} class={cn(taskStatusColor(t.status), \"mt-0.5 text-lg\")} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-sm font-medium\">\n <span className=\"truncate\">{t.name}</span>\n {t.error && <span className=\"truncate text-xs font-normal text-red-500\">{t.error}</span>}\n {latestWarn && (\n <span className={cn(\"truncate text-xs font-normal\", logLevelColor(\"warn\"))}>\n {latestWarn.message}\n </span>\n )}\n {hasLogs && (\n <span className=\"inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground\">\n {logs.length}\n </span>\n )}\n </span>\n {t.duration && <span className=\"shrink-0 text-xs text-muted-foreground\">{t.duration}</span>}\n </div>\n {expanded && hasLogs && (\n <div className=\"mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2\">\n {logs.map((l, i) => (\n <div key={i} className={cn(\"text-xs\", logLevelColor(l.level))}>\n <span className=\"mr-1 font-mono text-muted-foreground\">{l.level.substring(0, 3)}</span>\n {l.message}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TaskProgress.js","sources":["../../src/data/TaskProgress.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport type { LogEntry, TaskSnapshot } from \"./TaskSnapshot\";\nimport {\n bucketTasks,\n logLevelColor,\n taskSegments,\n taskStatusBg,\n taskStatusColor,\n taskStatusIcon,\n} from \"./task-status\";\n\n// TaskProgress renders one or more clicky task runs (groups) and their child\n// tasks: a segmented progress bar per group plus collapsible per-task rows with\n// status icon, duration, error, and expandable logs. Presentational only —\n// callers feed it snapshots from useTaskRun (SSE) or any other source.\n\n// Iconify web component, loaded by the host app (same as clicky's task UI). The\n// intrinsic element keeps TaskProgress free of clicky-ui's icon registry.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n \"iconify-icon\": { icon: string; class?: string };\n }\n }\n}\n\nconst MAX_COMPLETED = 5;\nconst MAX_PENDING = 3;\n\nexport interface TaskProgressProps {\n snapshots: TaskSnapshot[];\n title?: string;\n /** Tighter spacing for embedding in a panel. */\n compact?: boolean;\n className?: string;\n}\n\nexport function TaskProgress({ snapshots, title, compact, className }: TaskProgressProps) {\n const groups = snapshots.filter((s) => s.type === \"group\");\n const tasks = snapshots.filter((s) => s.type === \"task\");\n\n if (groups.length === 0) {\n return (\n <div className={cn(\"text-sm text-muted-foreground\", className)}>\n {title ? `${title}: ` : \"\"}No tasks yet.\n </div>\n );\n }\n\n return (\n <div className={cn(compact ? \"space-y-2\" : \"space-y-3\", className)}>\n {title && <h2 className=\"text-sm font-semibold\">{title}</h2>}\n {groups.map((g) => (\n <TaskGroupCard\n key={g.groupId || g.id}\n group={g}\n tasks={tasks.filter((t) => t.groupId === g.groupId || t.group === g.id)}\n compact={compact}\n />\n ))}\n </div>\n );\n}\n\nfunction isFailedOrWarn(t: TaskSnapshot): boolean {\n return t.status === \"failed\" || t.status === \"FAIL\" || t.status === \"ERR\" || t.status === \"warning\";\n}\n\nfunction TaskGroupCard({\n group: g,\n tasks,\n compact,\n}: {\n group: TaskSnapshot;\n tasks: TaskSnapshot[];\n compact: boolean | undefined;\n}) {\n const [showAll, setShowAll] = useState(false);\n const total = g.total ?? tasks.length;\n const counts = bucketTasks(tasks);\n const done = counts.ok + counts.warn + counts.fail;\n const progress = total > 0 ? `${done}/${total}` : \"\";\n\n const running = tasks.filter((t) => t.status === \"running\");\n const pending = tasks.filter((t) => t.status === \"pending\");\n const completed = tasks.filter((t) => t.status !== \"running\" && t.status !== \"pending\");\n const alwaysShow = completed.filter(isFailedOrWarn);\n const collapsible = completed.filter((t) => !isFailedOrWarn(t));\n\n const hiddenCompleted = collapsible.length - MAX_COMPLETED;\n const collapseCompleted = !showAll && hiddenCompleted > 0;\n const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;\n\n const hiddenPending = pending.length - MAX_PENDING;\n const collapsePending = !showAll && hiddenPending > 0;\n const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;\n\n const isTerminal = g.status !== \"running\" && g.status !== \"pending\";\n\n return (\n <div className={cn(\"rounded-lg border bg-card\", compact ? \"p-3\" : \"p-4\")}>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"flex items-center gap-1.5 text-sm font-semibold\">\n <iconify-icon icon={taskStatusIcon(g.status)} class={taskStatusColor(g.status)} />\n <span>{g.name}</span>\n {progress && <span className=\"text-xs text-muted-foreground\">{progress}</span>}\n {g.kind && (\n <span className=\"rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {g.kind}\n </span>\n )}\n </h3>\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(g.status),\n taskStatusBg(g.status),\n )}\n >\n {g.status}\n </span>\n )}\n </div>\n\n {total > 0 && (\n <div className=\"mb-3\">\n <ProgressBar segments={taskSegments(counts)} total={total} height=\"h-1.5\" />\n </div>\n )}\n\n {hiddenCompleted > 0 || hiddenPending > 0 ? (\n <button\n type=\"button\"\n className=\"border-b py-1.5 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? \"▲ collapse\"\n : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`}\n </button>\n ) : null}\n\n {[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => (\n <TaskRow key={t.id} task={t} />\n ))}\n </div>\n );\n}\n\nfunction TaskRow({ task: t }: { task: TaskSnapshot }) {\n const [expanded, setExpanded] = useState(false);\n const logs: LogEntry[] = t.logs ?? [];\n const hasLogs = logs.length > 0;\n // Promote the latest warning message inline so a `warning` row shows its\n // reason without expanding. Suppressed when an error is already shown.\n const latestWarn = t.error ? undefined : logs.filter((l) => l.level === \"warn\").at(-1);\n // Bounded per-task progress (e.g. records emitted in a phase): render an x/y\n // count, a percentage, and a thin bar while the task runs.\n const done = t.progress ?? 0;\n // Only surface bounded progress once work has actually started — an untouched\n // task should not render a \"0/100 · 0%\" placeholder.\n const hasProgress = typeof t.maxValue === \"number\" && t.maxValue > 0 && done > 0;\n const pct = hasProgress ? Math.round((done / (t.maxValue as number)) * 100) : 0;\n\n return (\n <div\n className={cn(\n \"flex items-start gap-3 border-b py-2 last:border-0\",\n hasLogs && \"-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50\",\n )}\n onClick={hasLogs ? () => setExpanded((v) => !v) : undefined}\n >\n <iconify-icon icon={taskStatusIcon(t.status)} class={cn(taskStatusColor(t.status), \"mt-0.5 text-lg\")} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-sm font-medium\">\n <span className=\"truncate\">{t.name}</span>\n {t.error && <span className=\"truncate text-xs font-normal text-red-500\">{t.error}</span>}\n {latestWarn && (\n <span className={cn(\"truncate text-xs font-normal\", logLevelColor(\"warn\"))}>\n {latestWarn.message}\n </span>\n )}\n {hasLogs && (\n <span className=\"inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground\">\n {logs.length}\n </span>\n )}\n </span>\n {t.duration && <span className=\"shrink-0 text-xs text-muted-foreground\">{t.duration}</span>}\n </div>\n {(t.description || hasProgress) && (\n <div className=\"mt-0.5 flex items-center gap-2\">\n {t.description && (\n <span className=\"min-w-0 flex-1 truncate text-xs text-muted-foreground\">{t.description}</span>\n )}\n {hasProgress && (\n <span className=\"shrink-0 text-xs tabular-nums text-muted-foreground\">\n {done}/{t.maxValue} · {pct}%\n </span>\n )}\n </div>\n )}\n {hasProgress && t.status === \"running\" && (\n <div className=\"mt-1\">\n <ProgressBar\n segments={[{ count: done, color: \"bg-blue-500\", label: \"done\" }]}\n total={t.maxValue as number}\n height=\"h-1\"\n />\n </div>\n )}\n {expanded && hasLogs && (\n <div className=\"mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2\">\n {logs.map((l, i) => (\n <div key={i} className={cn(\"text-xs\", logLevelColor(l.level))}>\n <span className=\"mr-1 font-mono text-muted-foreground\">{l.level.substring(0, 3)}</span>\n {l.message}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskSnapshot.cjs","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"TaskSnapshot.cjs","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n /** Per-task (type === \"task\") live stage label and bounded progress. */\n description?: string;\n progress?: number;\n /** Progress denominator; 0/undefined means the task has no bounded progress. */\n maxValue?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":";;AAgEA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,iBAAiB,QAAqC;AACpE,SAAO,CAAC,CAAC,UAAU,kBAAkB,IAAI,MAAM;AACjD;AAIO,SAAS,kBAAkB,WAAoC;AACpE,QAAM,SAAS,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AACzD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,MAAM,CAAC,MAAM,iBAAiB,EAAE,MAAM,CAAC;AACvD;;;"}
|
|
@@ -20,6 +20,11 @@ export interface TaskSnapshot {
|
|
|
20
20
|
completed?: number;
|
|
21
21
|
failed?: number;
|
|
22
22
|
running?: number;
|
|
23
|
+
/** Per-task (type === "task") live stage label and bounded progress. */
|
|
24
|
+
description?: string;
|
|
25
|
+
progress?: number;
|
|
26
|
+
/** Progress denominator; 0/undefined means the task has no bounded progress. */
|
|
27
|
+
maxValue?: number;
|
|
23
28
|
/** Stable parent-run id (tasks) or the run's own id (groups). */
|
|
24
29
|
groupId?: string;
|
|
25
30
|
kind?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskSnapshot.d.ts","sourceRoot":"","sources":["../../src/data/TaskSnapshot.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;IAClB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAKD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAeD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAID,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,OAAO,CAIpE"}
|
|
1
|
+
{"version":3,"file":"TaskSnapshot.d.ts","sourceRoot":"","sources":["../../src/data/TaskSnapshot.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;IAClB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAKD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAeD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAID,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,OAAO,CAIpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskSnapshot.js","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TaskSnapshot.js","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n /** Per-task (type === \"task\") live stage label and bounded progress. */\n description?: string;\n progress?: number;\n /** Progress denominator; 0/undefined means the task has no bounded progress. */\n maxValue?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":"AAgEA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,iBAAiB,QAAqC;AACpE,SAAO,CAAC,CAAC,UAAU,kBAAkB,IAAI,MAAM;AACjD;AAIO,SAAS,kBAAkB,WAAoC;AACpE,QAAM,SAAS,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AACzD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,MAAM,CAAC,MAAM,iBAAiB,EAAE,MAAM,CAAC;AACvD;"}
|
package/dist/data/Tree.cjs
CHANGED
|
@@ -9,6 +9,29 @@ const UiSearch = require("../icons/components/UiSearch.cjs");
|
|
|
9
9
|
const UiClose = require("../icons/components/UiClose.cjs");
|
|
10
10
|
const UiExpandAll = require("../icons/components/UiExpandAll.cjs");
|
|
11
11
|
const UiCollapseAll = require("../icons/components/UiCollapseAll.cjs");
|
|
12
|
+
function ancestorPathKeys(roots, getChildren, getKey, target) {
|
|
13
|
+
const targetKey = getKey(target);
|
|
14
|
+
const path = [];
|
|
15
|
+
const visit = (node) => {
|
|
16
|
+
if (getKey(node) === targetKey) return true;
|
|
17
|
+
for (const child of getChildren(node) ?? []) {
|
|
18
|
+
if (visit(child)) {
|
|
19
|
+
path.push(getKey(node));
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
25
|
+
for (const root of roots) {
|
|
26
|
+
if (visit(root)) return path.length ? new Set(path) : null;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function mergeKeySets(a, b) {
|
|
31
|
+
if (!a) return b;
|
|
32
|
+
if (!b) return a;
|
|
33
|
+
return /* @__PURE__ */ new Set([...a, ...b]);
|
|
34
|
+
}
|
|
12
35
|
function countTreeEdges(roots, getChildren) {
|
|
13
36
|
let total = 0;
|
|
14
37
|
const stack = [...roots];
|
|
@@ -106,6 +129,7 @@ function Tree({
|
|
|
106
129
|
onExpandAllChange,
|
|
107
130
|
toolbarClassName,
|
|
108
131
|
getSearchText,
|
|
132
|
+
revealSelected = false,
|
|
109
133
|
...nodeProps
|
|
110
134
|
}) {
|
|
111
135
|
const [internalExpandAll, setInternalExpandAll] = react.useState(null);
|
|
@@ -139,7 +163,14 @@ function Tree({
|
|
|
139
163
|
);
|
|
140
164
|
const treeRoots = filteredTree.roots;
|
|
141
165
|
const filteredChildren = filteredTree.filteredChildren;
|
|
142
|
-
const
|
|
166
|
+
const revealKeys = react.useMemo(
|
|
167
|
+
() => revealSelected && nodeProps.selected ? ancestorPathKeys(roots, nodeProps.getChildren, nodeProps.getKey, nodeProps.selected) : null,
|
|
168
|
+
[revealSelected, roots, nodeProps.getChildren, nodeProps.getKey, nodeProps.selected]
|
|
169
|
+
);
|
|
170
|
+
const forcedOpenKeys = react.useMemo(
|
|
171
|
+
() => mergeKeySets(filteredTree.forcedOpenKeys, revealKeys),
|
|
172
|
+
[filteredTree.forcedOpenKeys, revealKeys]
|
|
173
|
+
);
|
|
143
174
|
const effectiveGetChildren = filteredChildren ? (node) => filteredChildren.get(nodeProps.getKey(node)) ?? [] : nodeProps.getChildren;
|
|
144
175
|
const effectiveExpandAll = expandAll;
|
|
145
176
|
const effectiveEmpty = activeFilter.trim() ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-4 text-sm text-muted-foreground", children: "No matching tree nodes." }) : empty;
|