@knime/product-features 3.0.0 → 3.0.1
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/CHANGELOG.md +9 -0
- package/dist/ai.css +17 -16
- package/dist/ai.js +1 -15
- package/dist/ai.js.map +1 -1
- package/dist/dataTransfer.js +33 -3
- package/dist/dataTransfer.js.map +1 -1
- package/dist/{rfcErrors-B2gYd5aG.js → rfcErrors-CUq6KIxS.js} +2 -2
- package/dist/{rfcErrors-B2gYd5aG.js.map → rfcErrors-CUq6KIxS.js.map} +1 -1
- package/dist/rfcErrors.js +1 -1
- package/dist/src/ai/KdsPromptInput/KdsPromptInput.vue.d.ts.map +1 -1
- package/dist/src/dataTransfer/upload/useFileUpload.d.ts.map +1 -1
- package/dist/toasts.js +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @knime/product-features
|
|
2
2
|
|
|
3
|
+
## 3.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9cf30f3: `KdsPromptInput`: grow with its content via the native CSS `field-sizing` instead of resizing through JavaScript, so editing long text no longer jumps the surrounding scroll container to the top.
|
|
8
|
+
- 6d8e48f: - `useFileUpload`:
|
|
9
|
+
- `start` now returns `uploadId`s in the same order as the provided files, instead of the order the server's manifest response happened to list them in.
|
|
10
|
+
- files that resolve to the same upload path (e.g. same name from a different folder) are no longer silently dropped: the colliding file is uploaded and stored on the server under a disambiguated name (e.g. `file (1).txt`) instead.
|
|
11
|
+
|
|
3
12
|
## 3.0.0
|
|
4
13
|
|
|
5
14
|
### Major Changes
|
package/dist/ai.css
CHANGED
|
@@ -181,13 +181,13 @@
|
|
|
181
181
|
var(--kds-spacing-container-1x);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
.prompt-input[data-v-
|
|
184
|
+
.prompt-input[data-v-e3abc825] {
|
|
185
185
|
display: flex;
|
|
186
186
|
flex-direction: column;
|
|
187
187
|
width: 100%;
|
|
188
188
|
}
|
|
189
189
|
.input-container {
|
|
190
|
-
&[data-v-
|
|
190
|
+
&[data-v-e3abc825] {
|
|
191
191
|
position: relative;
|
|
192
192
|
display: flex;
|
|
193
193
|
gap: var(--kds-spacing-container-0-5x);
|
|
@@ -201,19 +201,19 @@
|
|
|
201
201
|
border: var(--kds-border-action-input);
|
|
202
202
|
border-radius: var(--kds-border-radius-container-0-37x);
|
|
203
203
|
}
|
|
204
|
-
&[data-v-
|
|
204
|
+
&[data-v-e3abc825]:has(textarea:focus:not(:disabled)) {
|
|
205
205
|
outline: var(--kds-border-action-focused);
|
|
206
206
|
outline-offset: var(--kds-spacing-offset-focus);
|
|
207
207
|
}
|
|
208
|
-
&[data-v-
|
|
208
|
+
&[data-v-e3abc825]:has(textarea:hover:not(:disabled, :focus)) {
|
|
209
209
|
background: var(--kds-color-background-input-hover);
|
|
210
210
|
}
|
|
211
|
-
&.disabled[data-v-
|
|
211
|
+
&.disabled[data-v-e3abc825] {
|
|
212
212
|
cursor: default;
|
|
213
213
|
border: var(--kds-border-action-disabled);
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
|
-
.text-input-wrapper[data-v-
|
|
216
|
+
.text-input-wrapper[data-v-e3abc825] {
|
|
217
217
|
display: flex;
|
|
218
218
|
flex: 1 0 0;
|
|
219
219
|
align-items: center;
|
|
@@ -227,11 +227,12 @@
|
|
|
227
227
|
padding-left: var(--kds-spacing-container-0-37x);
|
|
228
228
|
}
|
|
229
229
|
.text-input {
|
|
230
|
-
&[data-v-
|
|
230
|
+
&[data-v-e3abc825] {
|
|
231
231
|
width: 100%;
|
|
232
232
|
min-width: 0;
|
|
233
233
|
|
|
234
234
|
/* Cap textarea growth at 4 lines, scroll beyond that. */
|
|
235
|
+
field-sizing: content;
|
|
235
236
|
max-height: calc(4 * 1lh);
|
|
236
237
|
padding: 0;
|
|
237
238
|
overflow-y: auto;
|
|
@@ -243,32 +244,32 @@
|
|
|
243
244
|
background: transparent;
|
|
244
245
|
border: none;
|
|
245
246
|
}
|
|
246
|
-
&[data-v-
|
|
247
|
+
&[data-v-e3abc825]::placeholder {
|
|
247
248
|
color: var(--kds-color-text-and-icon-subtle);
|
|
248
249
|
}
|
|
249
250
|
&:disabled {
|
|
250
|
-
&[data-v-
|
|
251
|
+
&[data-v-e3abc825] {
|
|
251
252
|
color: var(--kds-color-text-and-icon-disabled);
|
|
252
253
|
cursor: default;
|
|
253
254
|
}
|
|
254
|
-
&[data-v-
|
|
255
|
+
&[data-v-e3abc825]::placeholder {
|
|
255
256
|
color: var(--kds-color-text-and-icon-disabled);
|
|
256
257
|
}
|
|
257
258
|
}
|
|
258
|
-
&[data-v-
|
|
259
|
+
&[data-v-e3abc825]::-webkit-scrollbar {
|
|
259
260
|
display: none;
|
|
260
261
|
}
|
|
261
262
|
}
|
|
262
|
-
.action[data-v-
|
|
263
|
+
.action[data-v-e3abc825] {
|
|
263
264
|
display: flex;
|
|
264
265
|
flex-shrink: 0;
|
|
265
266
|
align-items: flex-end;
|
|
266
267
|
}
|
|
267
|
-
.progress-bar[data-v-
|
|
268
|
+
.progress-bar[data-v-e3abc825] {
|
|
268
269
|
position: absolute;
|
|
269
270
|
inset: -1px 0 auto;
|
|
270
271
|
}
|
|
271
|
-
.subtext[data-v-
|
|
272
|
+
.subtext[data-v-e3abc825] {
|
|
272
273
|
display: flex;
|
|
273
274
|
flex-wrap: wrap;
|
|
274
275
|
gap: var(--kds-spacing-container-0-25x) var(--kds-spacing-container-0-5x);
|
|
@@ -278,7 +279,7 @@
|
|
|
278
279
|
font: var(--kds-font-base-subtext-small);
|
|
279
280
|
color: var(--kds-color-text-and-icon-neutral);
|
|
280
281
|
}
|
|
281
|
-
.subtext-left[data-v-
|
|
282
|
+
.subtext-left[data-v-e3abc825] {
|
|
282
283
|
/*
|
|
283
284
|
* Take all remaining inline space so the right block is pushed to the
|
|
284
285
|
* end. `min-width: 0` lets the ellipsis kick in when the right block
|
|
@@ -290,7 +291,7 @@
|
|
|
290
291
|
text-overflow: ellipsis;
|
|
291
292
|
white-space: nowrap;
|
|
292
293
|
}
|
|
293
|
-
.subtext-right[data-v-
|
|
294
|
+
.subtext-right[data-v-e3abc825] {
|
|
294
295
|
/*
|
|
295
296
|
* On row 1 (alongside .subtext-left) the right block stays at its
|
|
296
297
|
* intrinsic width — line-breaking in flex layout is decided from
|
package/dist/ai.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
|
|
2
2
|
import { KeepAlive, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeModels, normalizeClass, onMounted, openBlock, reactive, ref, renderSlot, toDisplayString, unref, useModel, useTemplateRef, vModelText, watch, withCtx, withDirectives, withModifiers } from "vue";
|
|
3
|
-
import { useResizeObserver } from "@vueuse/core";
|
|
4
3
|
import { KdsButton, KdsCheckbox, KdsCheckboxGroup, KdsIcon, KdsLink, KdsProgressBar, KdsRadioButtonGroup, KdsTextInput } from "@knime/kds-components";
|
|
5
4
|
import './ai.css';//#endregion
|
|
6
5
|
//#region src/ai/KdsPromptInput/AttachableSurface.vue
|
|
@@ -555,19 +554,6 @@ var KdsPromptInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
|
|
|
555
554
|
const hasInquiries = computed(() => __props.permissions.length > 0 || __props.questions.length > 0);
|
|
556
555
|
const isDisabled = computed(() => __props.disabled || hasInquiries.value);
|
|
557
556
|
const canSubmit = computed(() => !isDisabled.value && !__props.loading && modelValue.value.trim().length > 0);
|
|
558
|
-
function resizeTextarea() {
|
|
559
|
-
const el = textareaRef.value;
|
|
560
|
-
if (!el) return;
|
|
561
|
-
requestAnimationFrame(() => {
|
|
562
|
-
if (!textareaRef.value) return;
|
|
563
|
-
el.style.height = "auto";
|
|
564
|
-
const scrollHeight = el.scrollHeight;
|
|
565
|
-
if (scrollHeight <= el.offsetHeight) return;
|
|
566
|
-
el.style.height = `${scrollHeight}px`;
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
useResizeObserver(textareaRef, resizeTextarea);
|
|
570
|
-
watch(modelValue, resizeTextarea, { immediate: true });
|
|
571
557
|
function onSubmit() {
|
|
572
558
|
if (!canSubmit.value) return;
|
|
573
559
|
emit("submit", modelValue.value.trim());
|
|
@@ -634,7 +620,7 @@ var KdsPromptInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
|
|
|
634
620
|
]);
|
|
635
621
|
};
|
|
636
622
|
}
|
|
637
|
-
}), [["__scopeId", "data-v-
|
|
623
|
+
}), [["__scopeId", "data-v-e3abc825"]]);
|
|
638
624
|
//#endregion
|
|
639
625
|
//#region src/ai/KdsPromptInput/enums.ts
|
|
640
626
|
var kdsPromptInputStatusType = {
|
package/dist/ai.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai.js","names":[],"sources":["../src/ai/KdsPromptInput/AttachableSurface.vue","../src/ai/KdsPromptInput/AttachableSurface.vue","../src/ai/KdsPromptInput/InlineStatusBanner.vue","../src/ai/KdsPromptInput/InlineStatusBanner.vue","../src/ai/KdsPromptInput/inquiries/PermissionCard.vue","../src/ai/KdsPromptInput/inquiries/PermissionCard.vue","../src/ai/KdsPromptInput/inquiries/QuestionCard.vue","../src/ai/KdsPromptInput/inquiries/QuestionCard.vue","../src/ai/KdsPromptInput/inquiries/usePermissionPhase.ts","../src/ai/KdsPromptInput/inquiries/useQuestionPhase.ts","../src/ai/KdsPromptInput/inquiries/InquiryStack.vue","../src/ai/KdsPromptInput/inquiries/InquiryStack.vue","../src/ai/KdsPromptInput/KdsPromptInput.vue","../src/ai/KdsPromptInput/KdsPromptInput.vue","../src/ai/KdsPromptInput/enums.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Wrapper for a surface that attaches directly above the prompt input\n * (or above another attachable surface).\n *\n * Used by the status banner and inquiry cards.\n */\nconst { tone = \"neutral\" } = defineProps<{\n /**\n * Palette of the surface:\n * - `neutral` — default surface with a neutral border (inquiry cards).\n * - `danger` — danger-muted background (error status).\n * - `warning` — warning-muted background (warning status).\n * - `info` — info-muted background (note status).\n */\n tone?: \"neutral\" | \"danger\" | \"warning\" | \"info\";\n}>();\n</script>\n\n<template>\n <div :class=\"['attachable-surface', tone]\">\n <slot />\n </div>\n</template>\n\n<style scoped>\n.attachable-surface {\n margin-bottom: calc(-1 * var(--kds-spacing-container-0-25x));\n background-color: var(--surface-background);\n border: var(--surface-border);\n border-bottom: none;\n border-top-left-radius: var(--kds-border-radius-container-0-50x);\n border-top-right-radius: var(--kds-border-radius-container-0-50x);\n\n &.neutral {\n --surface-background: var(--kds-color-background-input-initial);\n --surface-border: var(--kds-border-action-default);\n }\n\n &.danger {\n --surface-background: var(--kds-color-background-static-danger-muted);\n --surface-border: var(--kds-border-base-danger);\n }\n\n &.warning {\n --surface-background: var(--kds-color-background-static-warning-muted);\n --surface-border: var(--kds-border-base-warning);\n }\n\n &.info {\n --surface-background: var(--kds-color-background-static-info-muted);\n --surface-border: var(--kds-border-base-info);\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * Wrapper for a surface that attaches directly above the prompt input\n * (or above another attachable surface).\n *\n * Used by the status banner and inquiry cards.\n */\nconst { tone = \"neutral\" } = defineProps<{\n /**\n * Palette of the surface:\n * - `neutral` — default surface with a neutral border (inquiry cards).\n * - `danger` — danger-muted background (error status).\n * - `warning` — warning-muted background (warning status).\n * - `info` — info-muted background (note status).\n */\n tone?: \"neutral\" | \"danger\" | \"warning\" | \"info\";\n}>();\n</script>\n\n<template>\n <div :class=\"['attachable-surface', tone]\">\n <slot />\n </div>\n</template>\n\n<style scoped>\n.attachable-surface {\n margin-bottom: calc(-1 * var(--kds-spacing-container-0-25x));\n background-color: var(--surface-background);\n border: var(--surface-border);\n border-bottom: none;\n border-top-left-radius: var(--kds-border-radius-container-0-50x);\n border-top-right-radius: var(--kds-border-radius-container-0-50x);\n\n &.neutral {\n --surface-background: var(--kds-color-background-input-initial);\n --surface-border: var(--kds-border-action-default);\n }\n\n &.danger {\n --surface-background: var(--kds-color-background-static-danger-muted);\n --surface-border: var(--kds-border-base-danger);\n }\n\n &.warning {\n --surface-background: var(--kds-color-background-static-warning-muted);\n --surface-border: var(--kds-border-base-warning);\n }\n\n &.info {\n --surface-background: var(--kds-color-background-static-info-muted);\n --surface-border: var(--kds-border-base-info);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { type HTMLAttributes, computed } from \"vue\";\n\nimport { KdsButton, KdsIcon, type KdsIconName } from \"@knime/kds-components\";\n\nimport AttachableSurface from \"./AttachableSurface.vue\";\nimport type { KdsPromptInputStatus, KdsPromptInputStatusType } from \"./types\";\n\nconst { status } = defineProps<{\n status: KdsPromptInputStatus;\n}>();\n\nconst emit = defineEmits<{\n buttonClick: [];\n}>();\n\n// `error` and `warning` interrupt the user, so they are announced\n// assertively; a `note` is ambient and only announced politely.\nconst statusMapper = {\n error: { tone: \"danger\", icon: \"circle-error\", role: \"alert\" },\n warning: { tone: \"warning\", icon: \"warning\", role: \"alert\" },\n note: { tone: \"info\", icon: \"circle-info\", role: \"status\" },\n} as const satisfies Record<\n KdsPromptInputStatusType,\n { tone: string; icon: KdsIconName; role: HTMLAttributes[\"role\"] }\n>;\n\nconst appearance = computed(() => statusMapper[status.type]);\n</script>\n\n<template>\n <AttachableSurface :tone=\"appearance.tone\">\n <div :class=\"['inline-status-banner', status.type]\" :role=\"appearance.role\">\n <span class=\"icon-wrapper\">\n <KdsIcon :name=\"appearance.icon\" size=\"small\" class=\"icon\" />\n </span>\n <!--\n tabindex=\"0\" so keyboard users can scroll the message when it\n overflows the 2-line cap (axe rule: scrollable-region-focusable).\n -->\n <span class=\"text\" tabindex=\"0\">{{ status.message }}</span>\n <KdsButton\n v-if=\"status.buttonLabel\"\n class=\"action\"\n size=\"small\"\n variant=\"filled\"\n :label=\"status.buttonLabel\"\n @click=\"emit('buttonClick')\"\n />\n </div>\n </AttachableSurface>\n</template>\n\n<style scoped>\n.inline-status-banner {\n display: flex;\n gap: var(--kds-spacing-container-0-37x);\n align-items: center;\n\n /*\n * Horizontal / top padding subtract the surface's border width so the\n * content box matches the design (where the border is drawn outside the\n * layout box). The bottom adds an extra 0.25x to compensate for the 0.25x\n * the surface tucks behind whatever sits below it, keeping the content\n * vertically centered against the visible portion of the banner.\n */\n padding: calc(\n var(--kds-spacing-container-0-5x) - var(--kds-core-border-width-xs)\n )\n calc(var(--kds-spacing-container-0-5x) - var(--kds-core-border-width-xs))\n calc(var(--kds-spacing-container-0-5x) + var(--kds-spacing-container-0-25x));\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n\n .icon-wrapper,\n .action {\n flex-shrink: 0;\n }\n\n .icon-wrapper {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-1-5x);\n }\n\n &.error .icon {\n color: var(--kds-color-text-and-icon-danger);\n }\n\n &.warning .icon {\n color: var(--kds-color-text-and-icon-warning);\n }\n\n &.note .icon {\n color: var(--kds-color-text-and-icon-info);\n }\n}\n\n.text {\n flex: 1 0 0;\n min-width: 0;\n\n /* Allow up to 2 lines, scroll beyond that. */\n max-height: calc(2 * 1lh);\n overflow-y: auto;\n overflow-wrap: anywhere;\n scrollbar-width: thin;\n\n &:focus-visible {\n outline: var(--kds-border-action-focused);\n outline-offset: var(--kds-spacing-offset-focus);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { type HTMLAttributes, computed } from \"vue\";\n\nimport { KdsButton, KdsIcon, type KdsIconName } from \"@knime/kds-components\";\n\nimport AttachableSurface from \"./AttachableSurface.vue\";\nimport type { KdsPromptInputStatus, KdsPromptInputStatusType } from \"./types\";\n\nconst { status } = defineProps<{\n status: KdsPromptInputStatus;\n}>();\n\nconst emit = defineEmits<{\n buttonClick: [];\n}>();\n\n// `error` and `warning` interrupt the user, so they are announced\n// assertively; a `note` is ambient and only announced politely.\nconst statusMapper = {\n error: { tone: \"danger\", icon: \"circle-error\", role: \"alert\" },\n warning: { tone: \"warning\", icon: \"warning\", role: \"alert\" },\n note: { tone: \"info\", icon: \"circle-info\", role: \"status\" },\n} as const satisfies Record<\n KdsPromptInputStatusType,\n { tone: string; icon: KdsIconName; role: HTMLAttributes[\"role\"] }\n>;\n\nconst appearance = computed(() => statusMapper[status.type]);\n</script>\n\n<template>\n <AttachableSurface :tone=\"appearance.tone\">\n <div :class=\"['inline-status-banner', status.type]\" :role=\"appearance.role\">\n <span class=\"icon-wrapper\">\n <KdsIcon :name=\"appearance.icon\" size=\"small\" class=\"icon\" />\n </span>\n <!--\n tabindex=\"0\" so keyboard users can scroll the message when it\n overflows the 2-line cap (axe rule: scrollable-region-focusable).\n -->\n <span class=\"text\" tabindex=\"0\">{{ status.message }}</span>\n <KdsButton\n v-if=\"status.buttonLabel\"\n class=\"action\"\n size=\"small\"\n variant=\"filled\"\n :label=\"status.buttonLabel\"\n @click=\"emit('buttonClick')\"\n />\n </div>\n </AttachableSurface>\n</template>\n\n<style scoped>\n.inline-status-banner {\n display: flex;\n gap: var(--kds-spacing-container-0-37x);\n align-items: center;\n\n /*\n * Horizontal / top padding subtract the surface's border width so the\n * content box matches the design (where the border is drawn outside the\n * layout box). The bottom adds an extra 0.25x to compensate for the 0.25x\n * the surface tucks behind whatever sits below it, keeping the content\n * vertically centered against the visible portion of the banner.\n */\n padding: calc(\n var(--kds-spacing-container-0-5x) - var(--kds-core-border-width-xs)\n )\n calc(var(--kds-spacing-container-0-5x) - var(--kds-core-border-width-xs))\n calc(var(--kds-spacing-container-0-5x) + var(--kds-spacing-container-0-25x));\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n\n .icon-wrapper,\n .action {\n flex-shrink: 0;\n }\n\n .icon-wrapper {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-1-5x);\n }\n\n &.error .icon {\n color: var(--kds-color-text-and-icon-danger);\n }\n\n &.warning .icon {\n color: var(--kds-color-text-and-icon-warning);\n }\n\n &.note .icon {\n color: var(--kds-color-text-and-icon-info);\n }\n}\n\n.text {\n flex: 1 0 0;\n min-width: 0;\n\n /* Allow up to 2 lines, scroll beyond that. */\n max-height: calc(2 * 1lh);\n overflow-y: auto;\n overflow-wrap: anywhere;\n scrollbar-width: thin;\n\n &:focus-visible {\n outline: var(--kds-border-action-focused);\n outline-offset: var(--kds-spacing-offset-focus);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onMounted, ref, useTemplateRef } from \"vue\";\n\nimport { KdsButton, KdsCheckbox } from \"@knime/kds-components\";\n\nimport type {\n KdsPromptInputPermission,\n KdsPromptInputPermissionDecision,\n} from \"../types\";\n\nconst { permission } = defineProps<{\n permission: KdsPromptInputPermission;\n}>();\n\nconst emit = defineEmits<{\n decide: [decision: Omit<KdsPromptInputPermissionDecision, \"id\">];\n}>();\n\nconst isRememberChoiceChecked = ref(false);\n\nconst decide = (decision: \"allow\" | \"deny\") => {\n emit(\"decide\", {\n decision,\n ...(permission.remember && {\n remember: isRememberChoiceChecked.value,\n }),\n });\n};\n\n// Focus the \"Allow\" button on render so the user can proceed quickly by\n// pressing \"Enter\".\nconst allowButtonRef =\n useTemplateRef<InstanceType<typeof KdsButton>>(\"allowButton\");\nonMounted(() => {\n (allowButtonRef.value?.$el as HTMLElement | undefined)?.focus();\n});\n\nconst handleKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n decide(\"allow\");\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n decide(\"deny\");\n }\n};\n</script>\n\n<template>\n <div class=\"inquiry-card\" @keydown=\"handleKeydown\">\n <span class=\"title\">{{ permission.headline }}</span>\n\n <div class=\"content\">\n <p v-if=\"permission.description\" class=\"description\">\n {{ permission.description }}\n </p>\n\n <KdsCheckbox\n v-if=\"permission.remember\"\n v-model=\"isRememberChoiceChecked\"\n :label=\"permission.remember.label\"\n :sub-text=\"permission.remember.subtext\"\n />\n\n <div class=\"action-buttons\">\n <KdsButton\n variant=\"transparent\"\n size=\"small\"\n label=\"Deny\"\n @click=\"decide('deny')\"\n />\n <KdsButton\n ref=\"allowButton\"\n variant=\"outlined\"\n size=\"small\"\n label=\"Allow\"\n @click=\"decide('allow')\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.inquiry-card {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n\n & .title {\n font: var(--kds-font-base-title-medium-strong);\n color: var(--kds-color-text-and-icon-neutral);\n overflow-wrap: anywhere;\n }\n\n & .content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-5x);\n\n & .description {\n margin: 0;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-subtle);\n overflow-wrap: anywhere;\n }\n\n & .action-buttons {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n justify-content: end;\n }\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onMounted, ref, useTemplateRef } from \"vue\";\n\nimport { KdsButton, KdsCheckbox } from \"@knime/kds-components\";\n\nimport type {\n KdsPromptInputPermission,\n KdsPromptInputPermissionDecision,\n} from \"../types\";\n\nconst { permission } = defineProps<{\n permission: KdsPromptInputPermission;\n}>();\n\nconst emit = defineEmits<{\n decide: [decision: Omit<KdsPromptInputPermissionDecision, \"id\">];\n}>();\n\nconst isRememberChoiceChecked = ref(false);\n\nconst decide = (decision: \"allow\" | \"deny\") => {\n emit(\"decide\", {\n decision,\n ...(permission.remember && {\n remember: isRememberChoiceChecked.value,\n }),\n });\n};\n\n// Focus the \"Allow\" button on render so the user can proceed quickly by\n// pressing \"Enter\".\nconst allowButtonRef =\n useTemplateRef<InstanceType<typeof KdsButton>>(\"allowButton\");\nonMounted(() => {\n (allowButtonRef.value?.$el as HTMLElement | undefined)?.focus();\n});\n\nconst handleKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n decide(\"allow\");\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n decide(\"deny\");\n }\n};\n</script>\n\n<template>\n <div class=\"inquiry-card\" @keydown=\"handleKeydown\">\n <span class=\"title\">{{ permission.headline }}</span>\n\n <div class=\"content\">\n <p v-if=\"permission.description\" class=\"description\">\n {{ permission.description }}\n </p>\n\n <KdsCheckbox\n v-if=\"permission.remember\"\n v-model=\"isRememberChoiceChecked\"\n :label=\"permission.remember.label\"\n :sub-text=\"permission.remember.subtext\"\n />\n\n <div class=\"action-buttons\">\n <KdsButton\n variant=\"transparent\"\n size=\"small\"\n label=\"Deny\"\n @click=\"decide('deny')\"\n />\n <KdsButton\n ref=\"allowButton\"\n variant=\"outlined\"\n size=\"small\"\n label=\"Allow\"\n @click=\"decide('allow')\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.inquiry-card {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n\n & .title {\n font: var(--kds-font-base-title-medium-strong);\n color: var(--kds-color-text-and-icon-neutral);\n overflow-wrap: anywhere;\n }\n\n & .content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-5x);\n\n & .description {\n margin: 0;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-subtle);\n overflow-wrap: anywhere;\n }\n\n & .action-buttons {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n justify-content: end;\n }\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from \"vue\";\n\nimport {\n KdsButton,\n KdsCheckboxGroup,\n KdsRadioButtonGroup,\n KdsTextInput,\n} from \"@knime/kds-components\";\n\nimport type {\n KdsPromptInputQuestion,\n KdsPromptInputQuestionAnswer,\n} from \"../types\";\n\n/**\n * A single pending question, shown one at a time and paginated by the parent\n * stack.\n */\nconst props = defineProps<{\n question: KdsPromptInputQuestion;\n /** Zero-based position of this question within the stack. */\n pageIndex: number;\n /** Total number of questions in the stack. */\n pageCount: number;\n /**\n * True when every *other* question in the stack has already been resolved\n * (answered or skipped). Combined with this card's own selection it decides\n * whether \"Submit\" is enabled on the last page.\n */\n othersAnswered: boolean;\n}>();\n\nconst emit = defineEmits<{\n /** Move to the previous question without recording an answer. */\n back: [];\n /** Skip this question (records `skipped: true`) and move on. */\n skip: [];\n /** Record this answer and advance to the next question. */\n next: [answer: Omit<KdsPromptInputQuestionAnswer, \"id\">];\n /** Record this answer and finalize every inquiry. */\n submit: [answer: Omit<KdsPromptInputQuestionAnswer, \"id\">];\n}>();\n\n/**\n * Synthetic id of the trailing \"Type your own answer\" option. Prefixed so it\n * cannot collide with consumer-provided option ids.\n */\nconst FREEFORM_OPTION_ID = \"__kds-prompt-input-freeform__\";\n\nconst hasOptions = computed(() => (props.question.options ?? []).length > 0);\n\nconst optionValues = computed(() => [\n ...(props.question.options ?? []).map((option) => ({\n id: option.id,\n text: option.label,\n helperText: option.subText,\n })),\n { id: FREEFORM_OPTION_ID, text: \"Type your own answer\" },\n]);\n\nconst selectedSingleId = ref(\"\");\nconst selectedMultiIds = ref<string[]>([]);\nconst freeformText = ref(\"\");\n\n// Whether the user has skipped this question. Kept alongside the selection so\n// the parent's `KeepAlive` preserves it: paging back to a skipped question\n// still shows it as \"(Skipped)\".\nconst isSkipped = ref(false);\n\nconst selectedIds = computed(() => {\n if (props.question.multiple) {\n return selectedMultiIds.value;\n }\n return selectedSingleId.value ? [selectedSingleId.value] : [];\n});\n\n// The freeform input is always active for option-less questions; otherwise\n// it appears once the \"Type your own answer\" option is selected.\nconst isFreeformActive = computed(\n () => !hasOptions.value || selectedIds.value.includes(FREEFORM_OPTION_ID),\n);\n\nconst selectedOptionIds = computed(() =>\n selectedIds.value.filter((id) => id !== FREEFORM_OPTION_ID),\n);\n\nconst canAnswer = computed(\n () =>\n selectedOptionIds.value.length > 0 ||\n (isFreeformActive.value && freeformText.value.trim().length > 0),\n);\n\n// Making a real selection clears the skipped mark — the two are mutually\n// exclusive. (Skipping itself clears the selection, so this never fires on\n// skip.)\nwatch(canAnswer, (answerable) => {\n if (answerable) {\n isSkipped.value = false;\n }\n});\n\nconst isLast = computed(() => props.pageIndex === props.pageCount - 1);\n// The pager only makes sense across multiple questions. \"Back\" is shown only\n// once there is an earlier question to return to (i.e. past the first page);\n// its slot in the action row is always reserved so the layout stays stable.\nconst showPagination = computed(() => props.pageCount > 1);\nconst showBack = computed(() => props.pageIndex > 0);\n\nconst primaryLabel = computed(() => (isLast.value ? \"Submit\" : \"Next\"));\n// The current question counts as resolved once it is answered or skipped. A\n// skipped question stays resolved when the user pages back to it, so the\n// primary button must remain enabled there — not only after a fresh answer.\nconst currentResolved = computed(() => canAnswer.value || isSkipped.value);\nconst canProceed = computed(() =>\n // \"Next\" just needs the current question resolved. \"Submit\" additionally\n // needs every *other* question resolved (answered or skipped).\n isLast.value\n ? props.othersAnswered && currentResolved.value\n : currentResolved.value,\n);\n\nconst buildAnswer = (): Omit<KdsPromptInputQuestionAnswer, \"id\"> => {\n if (isSkipped.value) {\n return { selectedOptionIds: [], skipped: true };\n }\n const freeform = isFreeformActive.value ? freeformText.value.trim() : \"\";\n return {\n selectedOptionIds: selectedOptionIds.value,\n ...(freeform && { freeformInput: freeform }),\n };\n};\n\nconst proceed = () => {\n if (!canProceed.value) {\n return;\n }\n if (isLast.value) {\n emit(\"submit\", buildAnswer());\n } else {\n emit(\"next\", buildAnswer());\n }\n};\n\nconst skip = () => {\n selectedSingleId.value = \"\";\n selectedMultiIds.value = [];\n freeformText.value = \"\";\n isSkipped.value = true;\n emit(\"skip\");\n};\n\nconst handleKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n proceed();\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n skip();\n }\n};\n</script>\n\n<template>\n <div class=\"inquiry-card\" @keydown=\"handleKeydown\">\n <div class=\"title-row\">\n <span class=\"title\"\n >{{ question.headline\n }}<span v-if=\"isSkipped\" class=\"skipped-tag\">(Skipped)</span></span\n >\n <span v-if=\"showPagination\" class=\"pagination\">\n {{ pageIndex + 1 }} of {{ pageCount }}\n </span>\n </div>\n\n <div class=\"content\">\n <p v-if=\"question.description\" class=\"description\">\n {{ question.description }}\n </p>\n\n <KdsRadioButtonGroup\n v-if=\"!question.multiple && hasOptions\"\n v-model=\"selectedSingleId\"\n :possible-values=\"optionValues\"\n alignment=\"vertical\"\n />\n <KdsCheckboxGroup\n v-if=\"question.multiple && hasOptions\"\n v-model=\"selectedMultiIds\"\n :possible-values=\"optionValues\"\n alignment=\"vertical\"\n />\n\n <KdsTextInput\n v-if=\"isFreeformActive\"\n v-model=\"freeformText\"\n ariaLabel=\"Type your own answer\"\n placeholder=\"Type your own answer...\"\n />\n\n <div class=\"action-buttons\">\n <div class=\"nav-secondary\">\n <KdsButton\n v-if=\"showBack\"\n variant=\"transparent\"\n size=\"small\"\n label=\"Back\"\n @click=\"emit('back')\"\n />\n </div>\n <div class=\"nav-primary\">\n <KdsButton\n variant=\"transparent\"\n size=\"small\"\n label=\"Skip\"\n @click=\"skip\"\n />\n <KdsButton\n variant=\"outlined\"\n size=\"small\"\n :label=\"primaryLabel\"\n :disabled=\"!canProceed\"\n @click=\"proceed\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.inquiry-card {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n\n & .title-row {\n display: flex;\n gap: var(--kds-spacing-container-1x);\n align-items: center;\n\n & .title {\n flex: 1 1 0;\n min-width: 0;\n font: var(--kds-font-base-title-medium-strong);\n color: var(--kds-color-text-and-icon-neutral);\n overflow-wrap: anywhere;\n\n & .skipped-tag {\n margin-left: var(--kds-spacing-container-0-25x);\n font: var(--kds-font-base-title-medium);\n color: var(--kds-color-text-and-icon-subtle);\n }\n }\n\n & .pagination {\n flex-shrink: 0;\n font: var(--kds-font-base-title-medium);\n color: var(--kds-color-text-and-icon-neutral);\n white-space: nowrap;\n }\n }\n\n & .content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-5x);\n\n & .description {\n margin: 0;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-subtle);\n overflow-wrap: anywhere;\n }\n\n & .action-buttons {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n justify-content: space-between;\n\n & .nav-primary {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n }\n }\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from \"vue\";\n\nimport {\n KdsButton,\n KdsCheckboxGroup,\n KdsRadioButtonGroup,\n KdsTextInput,\n} from \"@knime/kds-components\";\n\nimport type {\n KdsPromptInputQuestion,\n KdsPromptInputQuestionAnswer,\n} from \"../types\";\n\n/**\n * A single pending question, shown one at a time and paginated by the parent\n * stack.\n */\nconst props = defineProps<{\n question: KdsPromptInputQuestion;\n /** Zero-based position of this question within the stack. */\n pageIndex: number;\n /** Total number of questions in the stack. */\n pageCount: number;\n /**\n * True when every *other* question in the stack has already been resolved\n * (answered or skipped). Combined with this card's own selection it decides\n * whether \"Submit\" is enabled on the last page.\n */\n othersAnswered: boolean;\n}>();\n\nconst emit = defineEmits<{\n /** Move to the previous question without recording an answer. */\n back: [];\n /** Skip this question (records `skipped: true`) and move on. */\n skip: [];\n /** Record this answer and advance to the next question. */\n next: [answer: Omit<KdsPromptInputQuestionAnswer, \"id\">];\n /** Record this answer and finalize every inquiry. */\n submit: [answer: Omit<KdsPromptInputQuestionAnswer, \"id\">];\n}>();\n\n/**\n * Synthetic id of the trailing \"Type your own answer\" option. Prefixed so it\n * cannot collide with consumer-provided option ids.\n */\nconst FREEFORM_OPTION_ID = \"__kds-prompt-input-freeform__\";\n\nconst hasOptions = computed(() => (props.question.options ?? []).length > 0);\n\nconst optionValues = computed(() => [\n ...(props.question.options ?? []).map((option) => ({\n id: option.id,\n text: option.label,\n helperText: option.subText,\n })),\n { id: FREEFORM_OPTION_ID, text: \"Type your own answer\" },\n]);\n\nconst selectedSingleId = ref(\"\");\nconst selectedMultiIds = ref<string[]>([]);\nconst freeformText = ref(\"\");\n\n// Whether the user has skipped this question. Kept alongside the selection so\n// the parent's `KeepAlive` preserves it: paging back to a skipped question\n// still shows it as \"(Skipped)\".\nconst isSkipped = ref(false);\n\nconst selectedIds = computed(() => {\n if (props.question.multiple) {\n return selectedMultiIds.value;\n }\n return selectedSingleId.value ? [selectedSingleId.value] : [];\n});\n\n// The freeform input is always active for option-less questions; otherwise\n// it appears once the \"Type your own answer\" option is selected.\nconst isFreeformActive = computed(\n () => !hasOptions.value || selectedIds.value.includes(FREEFORM_OPTION_ID),\n);\n\nconst selectedOptionIds = computed(() =>\n selectedIds.value.filter((id) => id !== FREEFORM_OPTION_ID),\n);\n\nconst canAnswer = computed(\n () =>\n selectedOptionIds.value.length > 0 ||\n (isFreeformActive.value && freeformText.value.trim().length > 0),\n);\n\n// Making a real selection clears the skipped mark — the two are mutually\n// exclusive. (Skipping itself clears the selection, so this never fires on\n// skip.)\nwatch(canAnswer, (answerable) => {\n if (answerable) {\n isSkipped.value = false;\n }\n});\n\nconst isLast = computed(() => props.pageIndex === props.pageCount - 1);\n// The pager only makes sense across multiple questions. \"Back\" is shown only\n// once there is an earlier question to return to (i.e. past the first page);\n// its slot in the action row is always reserved so the layout stays stable.\nconst showPagination = computed(() => props.pageCount > 1);\nconst showBack = computed(() => props.pageIndex > 0);\n\nconst primaryLabel = computed(() => (isLast.value ? \"Submit\" : \"Next\"));\n// The current question counts as resolved once it is answered or skipped. A\n// skipped question stays resolved when the user pages back to it, so the\n// primary button must remain enabled there — not only after a fresh answer.\nconst currentResolved = computed(() => canAnswer.value || isSkipped.value);\nconst canProceed = computed(() =>\n // \"Next\" just needs the current question resolved. \"Submit\" additionally\n // needs every *other* question resolved (answered or skipped).\n isLast.value\n ? props.othersAnswered && currentResolved.value\n : currentResolved.value,\n);\n\nconst buildAnswer = (): Omit<KdsPromptInputQuestionAnswer, \"id\"> => {\n if (isSkipped.value) {\n return { selectedOptionIds: [], skipped: true };\n }\n const freeform = isFreeformActive.value ? freeformText.value.trim() : \"\";\n return {\n selectedOptionIds: selectedOptionIds.value,\n ...(freeform && { freeformInput: freeform }),\n };\n};\n\nconst proceed = () => {\n if (!canProceed.value) {\n return;\n }\n if (isLast.value) {\n emit(\"submit\", buildAnswer());\n } else {\n emit(\"next\", buildAnswer());\n }\n};\n\nconst skip = () => {\n selectedSingleId.value = \"\";\n selectedMultiIds.value = [];\n freeformText.value = \"\";\n isSkipped.value = true;\n emit(\"skip\");\n};\n\nconst handleKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n proceed();\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n skip();\n }\n};\n</script>\n\n<template>\n <div class=\"inquiry-card\" @keydown=\"handleKeydown\">\n <div class=\"title-row\">\n <span class=\"title\"\n >{{ question.headline\n }}<span v-if=\"isSkipped\" class=\"skipped-tag\">(Skipped)</span></span\n >\n <span v-if=\"showPagination\" class=\"pagination\">\n {{ pageIndex + 1 }} of {{ pageCount }}\n </span>\n </div>\n\n <div class=\"content\">\n <p v-if=\"question.description\" class=\"description\">\n {{ question.description }}\n </p>\n\n <KdsRadioButtonGroup\n v-if=\"!question.multiple && hasOptions\"\n v-model=\"selectedSingleId\"\n :possible-values=\"optionValues\"\n alignment=\"vertical\"\n />\n <KdsCheckboxGroup\n v-if=\"question.multiple && hasOptions\"\n v-model=\"selectedMultiIds\"\n :possible-values=\"optionValues\"\n alignment=\"vertical\"\n />\n\n <KdsTextInput\n v-if=\"isFreeformActive\"\n v-model=\"freeformText\"\n ariaLabel=\"Type your own answer\"\n placeholder=\"Type your own answer...\"\n />\n\n <div class=\"action-buttons\">\n <div class=\"nav-secondary\">\n <KdsButton\n v-if=\"showBack\"\n variant=\"transparent\"\n size=\"small\"\n label=\"Back\"\n @click=\"emit('back')\"\n />\n </div>\n <div class=\"nav-primary\">\n <KdsButton\n variant=\"transparent\"\n size=\"small\"\n label=\"Skip\"\n @click=\"skip\"\n />\n <KdsButton\n variant=\"outlined\"\n size=\"small\"\n :label=\"primaryLabel\"\n :disabled=\"!canProceed\"\n @click=\"proceed\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.inquiry-card {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n\n & .title-row {\n display: flex;\n gap: var(--kds-spacing-container-1x);\n align-items: center;\n\n & .title {\n flex: 1 1 0;\n min-width: 0;\n font: var(--kds-font-base-title-medium-strong);\n color: var(--kds-color-text-and-icon-neutral);\n overflow-wrap: anywhere;\n\n & .skipped-tag {\n margin-left: var(--kds-spacing-container-0-25x);\n font: var(--kds-font-base-title-medium);\n color: var(--kds-color-text-and-icon-subtle);\n }\n }\n\n & .pagination {\n flex-shrink: 0;\n font: var(--kds-font-base-title-medium);\n color: var(--kds-color-text-and-icon-neutral);\n white-space: nowrap;\n }\n }\n\n & .content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-5x);\n\n & .description {\n margin: 0;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-subtle);\n overflow-wrap: anywhere;\n }\n\n & .action-buttons {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n justify-content: space-between;\n\n & .nav-primary {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n }\n }\n }\n}\n</style>\n","import { computed, reactive } from \"vue\";\n\nimport type {\n KdsPromptInputPermission,\n KdsPromptInputPermissionDecision,\n} from \"../types\";\n\ntype PermissionDecision = Omit<KdsPromptInputPermissionDecision, \"id\">;\n\n/**\n * The permission phase: pending permissions resolved like a stack, with no\n * pager. The current permission is always the first still-undecided one;\n * deciding it pops it off and reveals the next. Buffered decisions persist\n * until {@link reset} so the parent can gather them into a single `respond`\n * once every phase is complete.\n *\n * @param permissions Getter for the pending permissions (kept reactive).\n */\nexport function usePermissionPhase(\n permissions: () => KdsPromptInputPermission[],\n) {\n // Keyed by the permission's own id; never shared with the question phase.\n const decisions = reactive(new Map<string, PermissionDecision>());\n\n /** The first permission not yet decided, or `undefined` once none remain. */\n const current = computed(() =>\n permissions().find((permission) => !decisions.has(permission.id)),\n );\n\n const isComplete = computed(() =>\n permissions().every((permission) => decisions.has(permission.id)),\n );\n\n /**\n * Record a decision for `permission`. Returns `false` (a no-op) when the\n * permission was already decided — e.g. a double click landing before the\n * stack pops the card off — so the caller can avoid emitting `respond` twice.\n *\n * A remembered decision also resolves every other undecided permission of the\n * same `remember.group` (e.g. repeated calls of one tool), so the user is not\n * asked again for a choice they just saved. Those never surface — the group\n * is popped silently.\n */\n const decide = (\n permission: KdsPromptInputPermission,\n value: PermissionDecision,\n ): boolean => {\n if (decisions.has(permission.id)) {\n return false;\n }\n decisions.set(permission.id, value);\n\n const group = permission.remember?.group;\n if (group && value.remember) {\n for (const other of permissions()) {\n if (other.remember?.group === group && !decisions.has(other.id)) {\n decisions.set(other.id, value);\n }\n }\n }\n return true;\n };\n\n /** Buffered decisions in prop order, ready for the `respond` payload. */\n const toOrderedDecisions = (): KdsPromptInputPermissionDecision[] =>\n permissions().map((permission) => ({\n id: permission.id,\n ...(decisions.get(permission.id) as PermissionDecision),\n }));\n\n const reset = () => decisions.clear();\n\n return { current, isComplete, decide, toOrderedDecisions, reset };\n}\n","import { computed, reactive, ref } from \"vue\";\n\nimport type {\n KdsPromptInputQuestion,\n KdsPromptInputQuestionAnswer,\n} from \"../types\";\n\ntype QuestionAnswer = Omit<KdsPromptInputQuestionAnswer, \"id\">;\n\n/**\n * The question phase: pending questions paginated one at a time. The user moves\n * forward with {@link answer} + {@link advance} (or {@link skip}) and back with\n * {@link back}, changing earlier selections freely. Buffered answers persist\n * until {@link reset} so the parent can gather them into a single `respond`\n * once every phase is complete.\n *\n * @param questions Getter for the pending questions (kept reactive).\n */\nexport function useQuestionPhase(questions: () => KdsPromptInputQuestion[]) {\n // Keyed by the question's own id; never shared with the permission phase.\n const answers = reactive(new Map<string, QuestionAnswer>());\n const index = ref(0);\n\n const current = computed(() => questions()[index.value]);\n const pageCount = computed(() => questions().length);\n\n const isComplete = computed(() =>\n questions().every((question) => answers.has(question.id)),\n );\n\n /**\n * True when every question *other than the current one* has been resolved.\n * The current card supplies its own answer live, so together they decide\n * whether \"Submit\" is enabled on the last page.\n */\n const othersAnswered = computed(() =>\n questions().every(\n (question, i) => i === index.value || answers.has(question.id),\n ),\n );\n\n const advance = () => {\n index.value = Math.min(index.value + 1, questions().length - 1);\n };\n\n const back = () => {\n if (index.value > 0) {\n index.value -= 1;\n }\n };\n\n const answer = (question: KdsPromptInputQuestion, value: QuestionAnswer) => {\n answers.set(question.id, value);\n };\n\n const skip = (question: KdsPromptInputQuestion) => {\n answers.set(question.id, { selectedOptionIds: [], skipped: true });\n };\n\n /** Buffered answers in prop order, ready for the `respond` payload. */\n const toOrderedAnswers = (): KdsPromptInputQuestionAnswer[] =>\n questions().map((question) => ({\n id: question.id,\n ...(answers.get(question.id) as QuestionAnswer),\n }));\n\n const reset = () => {\n answers.clear();\n index.value = 0;\n };\n\n return {\n current,\n index,\n pageCount,\n isComplete,\n othersAnswered,\n advance,\n back,\n answer,\n skip,\n toOrderedAnswers,\n reset,\n };\n}\n","<script setup lang=\"ts\">\nimport { watch } from \"vue\";\n\nimport AttachableSurface from \"../AttachableSurface.vue\";\nimport type {\n KdsPromptInputInquiryResponse,\n KdsPromptInputPermission,\n KdsPromptInputPermissionDecision,\n KdsPromptInputQuestion,\n KdsPromptInputQuestionAnswer,\n} from \"../types\";\n\nimport PermissionCard from \"./PermissionCard.vue\";\nimport QuestionCard from \"./QuestionCard.vue\";\nimport { usePermissionPhase } from \"./usePermissionPhase\";\nimport { useQuestionPhase } from \"./useQuestionPhase\";\n\n/**\n * The pending permissions and questions, attached as a surface above the\n * input. The two are resolved in distinct, sequential phases, see\n * the corresponding composables:\n *\n * - {@link usePermissionPhase} is first. Deciding one permission pops it off and reveals the next.\n * - {@link useQuestionPhase} follows once every permission is addressed: a stack\n * the user moves through with \"Next\" / \"Skip\" and \"Back\".\n *\n * This component only sequences the two phases and, once both are complete,\n * emits a single `respond` carrying every decision (all-or-nothing per turn).\n * For the common single-inquiry case this degenerates to one card that responds\n * immediately.\n */\nconst { permissions, questions } = defineProps<{\n permissions: KdsPromptInputPermission[];\n questions: KdsPromptInputQuestion[];\n}>();\n\nconst emit = defineEmits<{\n respond: [response: KdsPromptInputInquiryResponse];\n}>();\n\nconst {\n current: currentPermission,\n isComplete: permissionsComplete,\n decide: decidePermission,\n toOrderedDecisions,\n reset: resetPermissions,\n} = usePermissionPhase(() => permissions);\n\nconst {\n current: currentQuestion,\n index: questionIndex,\n pageCount: questionCount,\n isComplete: questionsComplete,\n othersAnswered,\n advance: advanceQuestion,\n back: goBack,\n answer: recordAnswer,\n skip: skipQuestion,\n toOrderedAnswers,\n reset: resetQuestions,\n} = useQuestionPhase(() => questions);\n\n// A new set of pending inquiries invalidates both phases' buffered state.\nwatch(\n () => [...permissions, ...questions].map((inquiry) => inquiry.id).join(\",\"),\n () => {\n resetPermissions();\n resetQuestions();\n },\n);\n\n// Emit once both phases are resolved. Either the last permission decision (when there\n// are no questions) or the question \"Submit\".\nconst respondIfComplete = () => {\n if (!permissionsComplete.value || !questionsComplete.value) {\n return;\n }\n emit(\"respond\", {\n permissions: toOrderedDecisions(),\n questions: toOrderedAnswers(),\n });\n};\n\nconst onDecidePermission = (\n permission: KdsPromptInputPermission,\n decision: Omit<KdsPromptInputPermissionDecision, \"id\">,\n) => {\n // `decidePermission` is a no-op (returns false) for an already-resolved card,\n // so a double click cannot fire `respond` twice.\n if (decidePermission(permission, decision)) {\n respondIfComplete();\n }\n};\n\nconst onQuestionNext = (\n question: KdsPromptInputQuestion,\n answer: Omit<KdsPromptInputQuestionAnswer, \"id\">,\n) => {\n recordAnswer(question, answer);\n advanceQuestion();\n};\n\nconst onQuestionSkip = (question: KdsPromptInputQuestion) => {\n // Skip resolves the question as skipped and moves on.\n // On the last question `advanceQuestion` is a clamp\n // leaving \"Submit\" as the only way forward.\n skipQuestion(question);\n advanceQuestion();\n};\n\nconst onQuestionSubmit = (\n question: KdsPromptInputQuestion,\n answer: Omit<KdsPromptInputQuestionAnswer, \"id\">,\n) => {\n recordAnswer(question, answer);\n respondIfComplete();\n};\n</script>\n\n<template>\n <AttachableSurface v-if=\"currentPermission || currentQuestion\" tone=\"neutral\">\n <div class=\"inquiry-stack\">\n <PermissionCard\n v-if=\"currentPermission\"\n :key=\"currentPermission.id\"\n :permission=\"currentPermission\"\n @decide=\"(decision) => onDecidePermission(currentPermission!, decision)\"\n />\n\n <!-- KeepAlive preserves each question's in-progress state (selected\n options, freeform text) while the user pages back and forth before\n confirming. The cache lives only as long as the stack itself, which\n unmounts once the turn's inquiries are resolved. -->\n <KeepAlive v-else-if=\"currentQuestion\">\n <QuestionCard\n :key=\"currentQuestion.id\"\n :question=\"currentQuestion\"\n :page-index=\"questionIndex\"\n :page-count=\"questionCount\"\n :others-answered=\"othersAnswered\"\n @back=\"goBack\"\n @skip=\"onQuestionSkip(currentQuestion!)\"\n @next=\"(answer) => onQuestionNext(currentQuestion!, answer)\"\n @submit=\"(answer) => onQuestionSubmit(currentQuestion!, answer)\"\n />\n </KeepAlive>\n </div>\n </AttachableSurface>\n</template>\n\n<style scoped>\n.inquiry-stack {\n padding: calc(\n var(--kds-spacing-container-1x) - var(--kds-core-border-width-xs)\n )\n calc(var(--kds-spacing-container-1x) - var(--kds-core-border-width-xs))\n var(--kds-spacing-container-1x);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { watch } from \"vue\";\n\nimport AttachableSurface from \"../AttachableSurface.vue\";\nimport type {\n KdsPromptInputInquiryResponse,\n KdsPromptInputPermission,\n KdsPromptInputPermissionDecision,\n KdsPromptInputQuestion,\n KdsPromptInputQuestionAnswer,\n} from \"../types\";\n\nimport PermissionCard from \"./PermissionCard.vue\";\nimport QuestionCard from \"./QuestionCard.vue\";\nimport { usePermissionPhase } from \"./usePermissionPhase\";\nimport { useQuestionPhase } from \"./useQuestionPhase\";\n\n/**\n * The pending permissions and questions, attached as a surface above the\n * input. The two are resolved in distinct, sequential phases, see\n * the corresponding composables:\n *\n * - {@link usePermissionPhase} is first. Deciding one permission pops it off and reveals the next.\n * - {@link useQuestionPhase} follows once every permission is addressed: a stack\n * the user moves through with \"Next\" / \"Skip\" and \"Back\".\n *\n * This component only sequences the two phases and, once both are complete,\n * emits a single `respond` carrying every decision (all-or-nothing per turn).\n * For the common single-inquiry case this degenerates to one card that responds\n * immediately.\n */\nconst { permissions, questions } = defineProps<{\n permissions: KdsPromptInputPermission[];\n questions: KdsPromptInputQuestion[];\n}>();\n\nconst emit = defineEmits<{\n respond: [response: KdsPromptInputInquiryResponse];\n}>();\n\nconst {\n current: currentPermission,\n isComplete: permissionsComplete,\n decide: decidePermission,\n toOrderedDecisions,\n reset: resetPermissions,\n} = usePermissionPhase(() => permissions);\n\nconst {\n current: currentQuestion,\n index: questionIndex,\n pageCount: questionCount,\n isComplete: questionsComplete,\n othersAnswered,\n advance: advanceQuestion,\n back: goBack,\n answer: recordAnswer,\n skip: skipQuestion,\n toOrderedAnswers,\n reset: resetQuestions,\n} = useQuestionPhase(() => questions);\n\n// A new set of pending inquiries invalidates both phases' buffered state.\nwatch(\n () => [...permissions, ...questions].map((inquiry) => inquiry.id).join(\",\"),\n () => {\n resetPermissions();\n resetQuestions();\n },\n);\n\n// Emit once both phases are resolved. Either the last permission decision (when there\n// are no questions) or the question \"Submit\".\nconst respondIfComplete = () => {\n if (!permissionsComplete.value || !questionsComplete.value) {\n return;\n }\n emit(\"respond\", {\n permissions: toOrderedDecisions(),\n questions: toOrderedAnswers(),\n });\n};\n\nconst onDecidePermission = (\n permission: KdsPromptInputPermission,\n decision: Omit<KdsPromptInputPermissionDecision, \"id\">,\n) => {\n // `decidePermission` is a no-op (returns false) for an already-resolved card,\n // so a double click cannot fire `respond` twice.\n if (decidePermission(permission, decision)) {\n respondIfComplete();\n }\n};\n\nconst onQuestionNext = (\n question: KdsPromptInputQuestion,\n answer: Omit<KdsPromptInputQuestionAnswer, \"id\">,\n) => {\n recordAnswer(question, answer);\n advanceQuestion();\n};\n\nconst onQuestionSkip = (question: KdsPromptInputQuestion) => {\n // Skip resolves the question as skipped and moves on.\n // On the last question `advanceQuestion` is a clamp\n // leaving \"Submit\" as the only way forward.\n skipQuestion(question);\n advanceQuestion();\n};\n\nconst onQuestionSubmit = (\n question: KdsPromptInputQuestion,\n answer: Omit<KdsPromptInputQuestionAnswer, \"id\">,\n) => {\n recordAnswer(question, answer);\n respondIfComplete();\n};\n</script>\n\n<template>\n <AttachableSurface v-if=\"currentPermission || currentQuestion\" tone=\"neutral\">\n <div class=\"inquiry-stack\">\n <PermissionCard\n v-if=\"currentPermission\"\n :key=\"currentPermission.id\"\n :permission=\"currentPermission\"\n @decide=\"(decision) => onDecidePermission(currentPermission!, decision)\"\n />\n\n <!-- KeepAlive preserves each question's in-progress state (selected\n options, freeform text) while the user pages back and forth before\n confirming. The cache lives only as long as the stack itself, which\n unmounts once the turn's inquiries are resolved. -->\n <KeepAlive v-else-if=\"currentQuestion\">\n <QuestionCard\n :key=\"currentQuestion.id\"\n :question=\"currentQuestion\"\n :page-index=\"questionIndex\"\n :page-count=\"questionCount\"\n :others-answered=\"othersAnswered\"\n @back=\"goBack\"\n @skip=\"onQuestionSkip(currentQuestion!)\"\n @next=\"(answer) => onQuestionNext(currentQuestion!, answer)\"\n @submit=\"(answer) => onQuestionSubmit(currentQuestion!, answer)\"\n />\n </KeepAlive>\n </div>\n </AttachableSurface>\n</template>\n\n<style scoped>\n.inquiry-stack {\n padding: calc(\n var(--kds-spacing-container-1x) - var(--kds-core-border-width-xs)\n )\n calc(var(--kds-spacing-container-1x) - var(--kds-core-border-width-xs))\n var(--kds-spacing-container-1x);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useTemplateRef, watch } from \"vue\";\nimport { useResizeObserver } from \"@vueuse/core\";\n\nimport { KdsButton, KdsLink, KdsProgressBar } from \"@knime/kds-components\";\n\nimport InlineStatusBanner from \"./InlineStatusBanner.vue\";\nimport { InquiryStack } from \"./inquiries\";\nimport type {\n KdsPromptInputInquiryResponse,\n KdsPromptInputProps,\n} from \"./types\";\n\nconst {\n placeholder = \"\",\n disabled = false,\n loading = false,\n status = undefined,\n ariaLabel = \"Prompt\",\n leftSubtext = undefined,\n rightSubtext = undefined,\n link = undefined,\n permissions = [],\n questions = [],\n} = defineProps<KdsPromptInputProps>();\n\nconst emit = defineEmits<{\n /** User submitted the prompt (Enter or send button). */\n submit: [value: string];\n /** User clicked the stop button while loading. */\n stop: [];\n /** User clicked the action button inside the inline status banner. */\n statusButtonClick: [];\n /** User decided every pending permission and answered every question. */\n respond: [response: KdsPromptInputInquiryResponse];\n}>();\n\nconst modelValue = defineModel<string>({ default: \"\" });\n\nconst textareaRef = useTemplateRef<HTMLTextAreaElement>(\"textareaRef\");\n\nconst hasInquiries = computed(\n () => permissions.length > 0 || questions.length > 0,\n);\n\n// While permissions or questions are shown, the input is disabled, so\n// responding to them is the only way to continue the conversation.\nconst isDisabled = computed(() => disabled || hasInquiries.value);\n\nconst canSubmit = computed(\n () => !isDisabled.value && !loading && modelValue.value.trim().length > 0,\n);\n\nfunction resizeTextarea() {\n const el = textareaRef.value;\n if (!el) {\n return;\n }\n // Defer style writes to the next animation frame so they don't run inside\n // the ResizeObserver callback (which would otherwise trigger the\n // \"ResizeObserver loop completed with undelivered notifications\" warning).\n requestAnimationFrame(() => {\n if (!textareaRef.value) {\n return;\n }\n el.style.height = \"auto\";\n const scrollHeight = el.scrollHeight;\n if (scrollHeight <= el.offsetHeight) {\n return;\n }\n el.style.height = `${scrollHeight}px`;\n });\n}\n\nuseResizeObserver(textareaRef, resizeTextarea);\nwatch(modelValue, resizeTextarea, { immediate: true });\n\nfunction onSubmit() {\n if (!canSubmit.value) {\n return;\n }\n emit(\"submit\", modelValue.value.trim());\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n // Enter submits, Shift+Enter inserts a newline. Ignore Enter while an IME\n // composition session is in progress.\n if (event.key === \"Enter\" && !event.shiftKey && !event.isComposing) {\n event.preventDefault();\n onSubmit();\n }\n}\n\ndefineExpose({\n focus: () => textareaRef.value?.focus(),\n});\n</script>\n\n<template>\n <div class=\"prompt-input\">\n <InlineStatusBanner\n v-if=\"status\"\n :status=\"status\"\n @button-click=\"emit('statusButtonClick')\"\n />\n\n <InquiryStack\n v-if=\"hasInquiries\"\n :permissions=\"permissions\"\n :questions=\"questions\"\n @respond=\"emit('respond', $event)\"\n />\n\n <div\n :class=\"['input-container', { disabled: isDisabled }]\"\n @click=\"textareaRef?.focus()\"\n >\n <div v-if=\"loading\" class=\"progress-bar\">\n <KdsProgressBar indeterminate />\n </div>\n\n <div class=\"text-input-wrapper\">\n <textarea\n ref=\"textareaRef\"\n v-model=\"modelValue\"\n class=\"text-input\"\n rows=\"1\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n :aria-label=\"ariaLabel\"\n @keydown=\"onKeydown\"\n />\n </div>\n\n <div class=\"action\">\n <KdsButton\n v-if=\"loading\"\n variant=\"outlined\"\n leading-icon=\"stop\"\n size=\"medium\"\n ariaLabel=\"Stop\"\n @click.stop=\"emit('stop')\"\n />\n <KdsButton\n v-else\n variant=\"filled\"\n leading-icon=\"send\"\n size=\"medium\"\n ariaLabel=\"Send\"\n :disabled=\"!canSubmit\"\n @click.stop=\"onSubmit\"\n />\n </div>\n </div>\n\n <div v-if=\"leftSubtext || rightSubtext || link\" class=\"subtext\">\n <div v-if=\"leftSubtext\" class=\"subtext-left\">{{ leftSubtext }}</div>\n <div v-if=\"rightSubtext || link\" class=\"subtext-right\">\n <span v-if=\"rightSubtext\">{{ rightSubtext }}</span>\n <KdsLink v-if=\"link\" :label=\"link.label\" :to=\"link.to\" size=\"small\" />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.prompt-input {\n display: flex;\n flex-direction: column;\n width: 100%;\n}\n\n.input-container {\n position: relative;\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n align-items: flex-end;\n padding: calc(\n var(--kds-spacing-container-0-37x) - var(--kds-core-border-width-xs)\n );\n overflow: hidden;\n cursor: text;\n background: var(--kds-color-background-input-initial);\n border: var(--kds-border-action-input);\n border-radius: var(--kds-border-radius-container-0-37x);\n\n &:has(textarea:focus:not(:disabled)) {\n outline: var(--kds-border-action-focused);\n outline-offset: var(--kds-spacing-offset-focus);\n }\n\n &:has(textarea:hover:not(:disabled, :focus)) {\n background: var(--kds-color-background-input-hover);\n }\n\n &.disabled {\n cursor: default;\n border: var(--kds-border-action-disabled);\n }\n}\n\n.text-input-wrapper {\n display: flex;\n flex: 1 0 0;\n align-items: center;\n min-width: 0;\n\n /*\n * Match the Send/Stop button height so a single-line textarea sits\n * vertically centered against the button.\n */\n min-height: var(--kds-dimension-component-height-1-75x);\n padding-left: var(--kds-spacing-container-0-37x);\n}\n\n.text-input {\n width: 100%;\n min-width: 0;\n\n /* Cap textarea growth at 4 lines, scroll beyond that. */\n max-height: calc(4 * 1lh);\n padding: 0;\n overflow-y: auto;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n resize: none;\n outline: none;\n scrollbar-width: none;\n background: transparent;\n border: none;\n\n &::placeholder {\n color: var(--kds-color-text-and-icon-subtle);\n }\n\n &:disabled {\n color: var(--kds-color-text-and-icon-disabled);\n cursor: default;\n\n &::placeholder {\n color: var(--kds-color-text-and-icon-disabled);\n }\n }\n\n &::-webkit-scrollbar {\n display: none;\n }\n}\n\n.action {\n display: flex;\n flex-shrink: 0;\n align-items: flex-end;\n}\n\n.progress-bar {\n position: absolute;\n inset: -1px 0 auto;\n}\n\n.subtext {\n display: flex;\n flex-wrap: wrap;\n gap: var(--kds-spacing-container-0-25x) var(--kds-spacing-container-0-5x);\n align-items: center;\n padding: var(--kds-spacing-container-0-25x) var(--kds-spacing-container-0-25x)\n 0;\n font: var(--kds-font-base-subtext-small);\n color: var(--kds-color-text-and-icon-neutral);\n}\n\n.subtext-left {\n /*\n * Take all remaining inline space so the right block is pushed to the\n * end. `min-width: 0` lets the ellipsis kick in when the right block\n * grows past the container width.\n */\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.subtext-right {\n /*\n * On row 1 (alongside .subtext-left) the right block stays at its\n * intrinsic width — line-breaking in flex layout is decided from\n * `flex-basis`, so it wraps onto its own row as soon as the container\n * is narrower than that intrinsic width. On row 2 (its own row) the\n * default `flex-shrink: 1` plus `min-width: 0` lets it shrink to the\n * container width and wrap its own content (text + link).\n */\n display: flex;\n flex-wrap: wrap;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-width: 0;\n margin-left: auto;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useTemplateRef, watch } from \"vue\";\nimport { useResizeObserver } from \"@vueuse/core\";\n\nimport { KdsButton, KdsLink, KdsProgressBar } from \"@knime/kds-components\";\n\nimport InlineStatusBanner from \"./InlineStatusBanner.vue\";\nimport { InquiryStack } from \"./inquiries\";\nimport type {\n KdsPromptInputInquiryResponse,\n KdsPromptInputProps,\n} from \"./types\";\n\nconst {\n placeholder = \"\",\n disabled = false,\n loading = false,\n status = undefined,\n ariaLabel = \"Prompt\",\n leftSubtext = undefined,\n rightSubtext = undefined,\n link = undefined,\n permissions = [],\n questions = [],\n} = defineProps<KdsPromptInputProps>();\n\nconst emit = defineEmits<{\n /** User submitted the prompt (Enter or send button). */\n submit: [value: string];\n /** User clicked the stop button while loading. */\n stop: [];\n /** User clicked the action button inside the inline status banner. */\n statusButtonClick: [];\n /** User decided every pending permission and answered every question. */\n respond: [response: KdsPromptInputInquiryResponse];\n}>();\n\nconst modelValue = defineModel<string>({ default: \"\" });\n\nconst textareaRef = useTemplateRef<HTMLTextAreaElement>(\"textareaRef\");\n\nconst hasInquiries = computed(\n () => permissions.length > 0 || questions.length > 0,\n);\n\n// While permissions or questions are shown, the input is disabled, so\n// responding to them is the only way to continue the conversation.\nconst isDisabled = computed(() => disabled || hasInquiries.value);\n\nconst canSubmit = computed(\n () => !isDisabled.value && !loading && modelValue.value.trim().length > 0,\n);\n\nfunction resizeTextarea() {\n const el = textareaRef.value;\n if (!el) {\n return;\n }\n // Defer style writes to the next animation frame so they don't run inside\n // the ResizeObserver callback (which would otherwise trigger the\n // \"ResizeObserver loop completed with undelivered notifications\" warning).\n requestAnimationFrame(() => {\n if (!textareaRef.value) {\n return;\n }\n el.style.height = \"auto\";\n const scrollHeight = el.scrollHeight;\n if (scrollHeight <= el.offsetHeight) {\n return;\n }\n el.style.height = `${scrollHeight}px`;\n });\n}\n\nuseResizeObserver(textareaRef, resizeTextarea);\nwatch(modelValue, resizeTextarea, { immediate: true });\n\nfunction onSubmit() {\n if (!canSubmit.value) {\n return;\n }\n emit(\"submit\", modelValue.value.trim());\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n // Enter submits, Shift+Enter inserts a newline. Ignore Enter while an IME\n // composition session is in progress.\n if (event.key === \"Enter\" && !event.shiftKey && !event.isComposing) {\n event.preventDefault();\n onSubmit();\n }\n}\n\ndefineExpose({\n focus: () => textareaRef.value?.focus(),\n});\n</script>\n\n<template>\n <div class=\"prompt-input\">\n <InlineStatusBanner\n v-if=\"status\"\n :status=\"status\"\n @button-click=\"emit('statusButtonClick')\"\n />\n\n <InquiryStack\n v-if=\"hasInquiries\"\n :permissions=\"permissions\"\n :questions=\"questions\"\n @respond=\"emit('respond', $event)\"\n />\n\n <div\n :class=\"['input-container', { disabled: isDisabled }]\"\n @click=\"textareaRef?.focus()\"\n >\n <div v-if=\"loading\" class=\"progress-bar\">\n <KdsProgressBar indeterminate />\n </div>\n\n <div class=\"text-input-wrapper\">\n <textarea\n ref=\"textareaRef\"\n v-model=\"modelValue\"\n class=\"text-input\"\n rows=\"1\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n :aria-label=\"ariaLabel\"\n @keydown=\"onKeydown\"\n />\n </div>\n\n <div class=\"action\">\n <KdsButton\n v-if=\"loading\"\n variant=\"outlined\"\n leading-icon=\"stop\"\n size=\"medium\"\n ariaLabel=\"Stop\"\n @click.stop=\"emit('stop')\"\n />\n <KdsButton\n v-else\n variant=\"filled\"\n leading-icon=\"send\"\n size=\"medium\"\n ariaLabel=\"Send\"\n :disabled=\"!canSubmit\"\n @click.stop=\"onSubmit\"\n />\n </div>\n </div>\n\n <div v-if=\"leftSubtext || rightSubtext || link\" class=\"subtext\">\n <div v-if=\"leftSubtext\" class=\"subtext-left\">{{ leftSubtext }}</div>\n <div v-if=\"rightSubtext || link\" class=\"subtext-right\">\n <span v-if=\"rightSubtext\">{{ rightSubtext }}</span>\n <KdsLink v-if=\"link\" :label=\"link.label\" :to=\"link.to\" size=\"small\" />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.prompt-input {\n display: flex;\n flex-direction: column;\n width: 100%;\n}\n\n.input-container {\n position: relative;\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n align-items: flex-end;\n padding: calc(\n var(--kds-spacing-container-0-37x) - var(--kds-core-border-width-xs)\n );\n overflow: hidden;\n cursor: text;\n background: var(--kds-color-background-input-initial);\n border: var(--kds-border-action-input);\n border-radius: var(--kds-border-radius-container-0-37x);\n\n &:has(textarea:focus:not(:disabled)) {\n outline: var(--kds-border-action-focused);\n outline-offset: var(--kds-spacing-offset-focus);\n }\n\n &:has(textarea:hover:not(:disabled, :focus)) {\n background: var(--kds-color-background-input-hover);\n }\n\n &.disabled {\n cursor: default;\n border: var(--kds-border-action-disabled);\n }\n}\n\n.text-input-wrapper {\n display: flex;\n flex: 1 0 0;\n align-items: center;\n min-width: 0;\n\n /*\n * Match the Send/Stop button height so a single-line textarea sits\n * vertically centered against the button.\n */\n min-height: var(--kds-dimension-component-height-1-75x);\n padding-left: var(--kds-spacing-container-0-37x);\n}\n\n.text-input {\n width: 100%;\n min-width: 0;\n\n /* Cap textarea growth at 4 lines, scroll beyond that. */\n max-height: calc(4 * 1lh);\n padding: 0;\n overflow-y: auto;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n resize: none;\n outline: none;\n scrollbar-width: none;\n background: transparent;\n border: none;\n\n &::placeholder {\n color: var(--kds-color-text-and-icon-subtle);\n }\n\n &:disabled {\n color: var(--kds-color-text-and-icon-disabled);\n cursor: default;\n\n &::placeholder {\n color: var(--kds-color-text-and-icon-disabled);\n }\n }\n\n &::-webkit-scrollbar {\n display: none;\n }\n}\n\n.action {\n display: flex;\n flex-shrink: 0;\n align-items: flex-end;\n}\n\n.progress-bar {\n position: absolute;\n inset: -1px 0 auto;\n}\n\n.subtext {\n display: flex;\n flex-wrap: wrap;\n gap: var(--kds-spacing-container-0-25x) var(--kds-spacing-container-0-5x);\n align-items: center;\n padding: var(--kds-spacing-container-0-25x) var(--kds-spacing-container-0-25x)\n 0;\n font: var(--kds-font-base-subtext-small);\n color: var(--kds-color-text-and-icon-neutral);\n}\n\n.subtext-left {\n /*\n * Take all remaining inline space so the right block is pushed to the\n * end. `min-width: 0` lets the ellipsis kick in when the right block\n * grows past the container width.\n */\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.subtext-right {\n /*\n * On row 1 (alongside .subtext-left) the right block stays at its\n * intrinsic width — line-breaking in flex layout is decided from\n * `flex-basis`, so it wraps onto its own row as soon as the container\n * is narrower than that intrinsic width. On row 2 (its own row) the\n * default `flex-shrink: 1` plus `min-width: 0` lets it shrink to the\n * container width and wrap its own content (text + link).\n */\n display: flex;\n flex-wrap: wrap;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-width: 0;\n margin-left: auto;\n}\n</style>\n","export const kdsPromptInputStatusType = {\n ERROR: \"error\",\n WARNING: \"warning\",\n NOTE: \"note\",\n} as const;\n\nexport const kdsPromptInputStatusTypes = Object.values(\n kdsPromptInputStatusType,\n);\n\nexport const kdsPromptInputPermissionDecision = {\n ALLOW: \"allow\",\n DENY: \"deny\",\n} as const;\n\nexport const kdsPromptInputPermissionDecisions = Object.values(\n kdsPromptInputPermissionDecision,\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ai.js","names":[],"sources":["../src/ai/KdsPromptInput/AttachableSurface.vue","../src/ai/KdsPromptInput/AttachableSurface.vue","../src/ai/KdsPromptInput/InlineStatusBanner.vue","../src/ai/KdsPromptInput/InlineStatusBanner.vue","../src/ai/KdsPromptInput/inquiries/PermissionCard.vue","../src/ai/KdsPromptInput/inquiries/PermissionCard.vue","../src/ai/KdsPromptInput/inquiries/QuestionCard.vue","../src/ai/KdsPromptInput/inquiries/QuestionCard.vue","../src/ai/KdsPromptInput/inquiries/usePermissionPhase.ts","../src/ai/KdsPromptInput/inquiries/useQuestionPhase.ts","../src/ai/KdsPromptInput/inquiries/InquiryStack.vue","../src/ai/KdsPromptInput/inquiries/InquiryStack.vue","../src/ai/KdsPromptInput/KdsPromptInput.vue","../src/ai/KdsPromptInput/KdsPromptInput.vue","../src/ai/KdsPromptInput/enums.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Wrapper for a surface that attaches directly above the prompt input\n * (or above another attachable surface).\n *\n * Used by the status banner and inquiry cards.\n */\nconst { tone = \"neutral\" } = defineProps<{\n /**\n * Palette of the surface:\n * - `neutral` — default surface with a neutral border (inquiry cards).\n * - `danger` — danger-muted background (error status).\n * - `warning` — warning-muted background (warning status).\n * - `info` — info-muted background (note status).\n */\n tone?: \"neutral\" | \"danger\" | \"warning\" | \"info\";\n}>();\n</script>\n\n<template>\n <div :class=\"['attachable-surface', tone]\">\n <slot />\n </div>\n</template>\n\n<style scoped>\n.attachable-surface {\n margin-bottom: calc(-1 * var(--kds-spacing-container-0-25x));\n background-color: var(--surface-background);\n border: var(--surface-border);\n border-bottom: none;\n border-top-left-radius: var(--kds-border-radius-container-0-50x);\n border-top-right-radius: var(--kds-border-radius-container-0-50x);\n\n &.neutral {\n --surface-background: var(--kds-color-background-input-initial);\n --surface-border: var(--kds-border-action-default);\n }\n\n &.danger {\n --surface-background: var(--kds-color-background-static-danger-muted);\n --surface-border: var(--kds-border-base-danger);\n }\n\n &.warning {\n --surface-background: var(--kds-color-background-static-warning-muted);\n --surface-border: var(--kds-border-base-warning);\n }\n\n &.info {\n --surface-background: var(--kds-color-background-static-info-muted);\n --surface-border: var(--kds-border-base-info);\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * Wrapper for a surface that attaches directly above the prompt input\n * (or above another attachable surface).\n *\n * Used by the status banner and inquiry cards.\n */\nconst { tone = \"neutral\" } = defineProps<{\n /**\n * Palette of the surface:\n * - `neutral` — default surface with a neutral border (inquiry cards).\n * - `danger` — danger-muted background (error status).\n * - `warning` — warning-muted background (warning status).\n * - `info` — info-muted background (note status).\n */\n tone?: \"neutral\" | \"danger\" | \"warning\" | \"info\";\n}>();\n</script>\n\n<template>\n <div :class=\"['attachable-surface', tone]\">\n <slot />\n </div>\n</template>\n\n<style scoped>\n.attachable-surface {\n margin-bottom: calc(-1 * var(--kds-spacing-container-0-25x));\n background-color: var(--surface-background);\n border: var(--surface-border);\n border-bottom: none;\n border-top-left-radius: var(--kds-border-radius-container-0-50x);\n border-top-right-radius: var(--kds-border-radius-container-0-50x);\n\n &.neutral {\n --surface-background: var(--kds-color-background-input-initial);\n --surface-border: var(--kds-border-action-default);\n }\n\n &.danger {\n --surface-background: var(--kds-color-background-static-danger-muted);\n --surface-border: var(--kds-border-base-danger);\n }\n\n &.warning {\n --surface-background: var(--kds-color-background-static-warning-muted);\n --surface-border: var(--kds-border-base-warning);\n }\n\n &.info {\n --surface-background: var(--kds-color-background-static-info-muted);\n --surface-border: var(--kds-border-base-info);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { type HTMLAttributes, computed } from \"vue\";\n\nimport { KdsButton, KdsIcon, type KdsIconName } from \"@knime/kds-components\";\n\nimport AttachableSurface from \"./AttachableSurface.vue\";\nimport type { KdsPromptInputStatus, KdsPromptInputStatusType } from \"./types\";\n\nconst { status } = defineProps<{\n status: KdsPromptInputStatus;\n}>();\n\nconst emit = defineEmits<{\n buttonClick: [];\n}>();\n\n// `error` and `warning` interrupt the user, so they are announced\n// assertively; a `note` is ambient and only announced politely.\nconst statusMapper = {\n error: { tone: \"danger\", icon: \"circle-error\", role: \"alert\" },\n warning: { tone: \"warning\", icon: \"warning\", role: \"alert\" },\n note: { tone: \"info\", icon: \"circle-info\", role: \"status\" },\n} as const satisfies Record<\n KdsPromptInputStatusType,\n { tone: string; icon: KdsIconName; role: HTMLAttributes[\"role\"] }\n>;\n\nconst appearance = computed(() => statusMapper[status.type]);\n</script>\n\n<template>\n <AttachableSurface :tone=\"appearance.tone\">\n <div :class=\"['inline-status-banner', status.type]\" :role=\"appearance.role\">\n <span class=\"icon-wrapper\">\n <KdsIcon :name=\"appearance.icon\" size=\"small\" class=\"icon\" />\n </span>\n <!--\n tabindex=\"0\" so keyboard users can scroll the message when it\n overflows the 2-line cap (axe rule: scrollable-region-focusable).\n -->\n <span class=\"text\" tabindex=\"0\">{{ status.message }}</span>\n <KdsButton\n v-if=\"status.buttonLabel\"\n class=\"action\"\n size=\"small\"\n variant=\"filled\"\n :label=\"status.buttonLabel\"\n @click=\"emit('buttonClick')\"\n />\n </div>\n </AttachableSurface>\n</template>\n\n<style scoped>\n.inline-status-banner {\n display: flex;\n gap: var(--kds-spacing-container-0-37x);\n align-items: center;\n\n /*\n * Horizontal / top padding subtract the surface's border width so the\n * content box matches the design (where the border is drawn outside the\n * layout box). The bottom adds an extra 0.25x to compensate for the 0.25x\n * the surface tucks behind whatever sits below it, keeping the content\n * vertically centered against the visible portion of the banner.\n */\n padding: calc(\n var(--kds-spacing-container-0-5x) - var(--kds-core-border-width-xs)\n )\n calc(var(--kds-spacing-container-0-5x) - var(--kds-core-border-width-xs))\n calc(var(--kds-spacing-container-0-5x) + var(--kds-spacing-container-0-25x));\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n\n .icon-wrapper,\n .action {\n flex-shrink: 0;\n }\n\n .icon-wrapper {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-1-5x);\n }\n\n &.error .icon {\n color: var(--kds-color-text-and-icon-danger);\n }\n\n &.warning .icon {\n color: var(--kds-color-text-and-icon-warning);\n }\n\n &.note .icon {\n color: var(--kds-color-text-and-icon-info);\n }\n}\n\n.text {\n flex: 1 0 0;\n min-width: 0;\n\n /* Allow up to 2 lines, scroll beyond that. */\n max-height: calc(2 * 1lh);\n overflow-y: auto;\n overflow-wrap: anywhere;\n scrollbar-width: thin;\n\n &:focus-visible {\n outline: var(--kds-border-action-focused);\n outline-offset: var(--kds-spacing-offset-focus);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { type HTMLAttributes, computed } from \"vue\";\n\nimport { KdsButton, KdsIcon, type KdsIconName } from \"@knime/kds-components\";\n\nimport AttachableSurface from \"./AttachableSurface.vue\";\nimport type { KdsPromptInputStatus, KdsPromptInputStatusType } from \"./types\";\n\nconst { status } = defineProps<{\n status: KdsPromptInputStatus;\n}>();\n\nconst emit = defineEmits<{\n buttonClick: [];\n}>();\n\n// `error` and `warning` interrupt the user, so they are announced\n// assertively; a `note` is ambient and only announced politely.\nconst statusMapper = {\n error: { tone: \"danger\", icon: \"circle-error\", role: \"alert\" },\n warning: { tone: \"warning\", icon: \"warning\", role: \"alert\" },\n note: { tone: \"info\", icon: \"circle-info\", role: \"status\" },\n} as const satisfies Record<\n KdsPromptInputStatusType,\n { tone: string; icon: KdsIconName; role: HTMLAttributes[\"role\"] }\n>;\n\nconst appearance = computed(() => statusMapper[status.type]);\n</script>\n\n<template>\n <AttachableSurface :tone=\"appearance.tone\">\n <div :class=\"['inline-status-banner', status.type]\" :role=\"appearance.role\">\n <span class=\"icon-wrapper\">\n <KdsIcon :name=\"appearance.icon\" size=\"small\" class=\"icon\" />\n </span>\n <!--\n tabindex=\"0\" so keyboard users can scroll the message when it\n overflows the 2-line cap (axe rule: scrollable-region-focusable).\n -->\n <span class=\"text\" tabindex=\"0\">{{ status.message }}</span>\n <KdsButton\n v-if=\"status.buttonLabel\"\n class=\"action\"\n size=\"small\"\n variant=\"filled\"\n :label=\"status.buttonLabel\"\n @click=\"emit('buttonClick')\"\n />\n </div>\n </AttachableSurface>\n</template>\n\n<style scoped>\n.inline-status-banner {\n display: flex;\n gap: var(--kds-spacing-container-0-37x);\n align-items: center;\n\n /*\n * Horizontal / top padding subtract the surface's border width so the\n * content box matches the design (where the border is drawn outside the\n * layout box). The bottom adds an extra 0.25x to compensate for the 0.25x\n * the surface tucks behind whatever sits below it, keeping the content\n * vertically centered against the visible portion of the banner.\n */\n padding: calc(\n var(--kds-spacing-container-0-5x) - var(--kds-core-border-width-xs)\n )\n calc(var(--kds-spacing-container-0-5x) - var(--kds-core-border-width-xs))\n calc(var(--kds-spacing-container-0-5x) + var(--kds-spacing-container-0-25x));\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n\n .icon-wrapper,\n .action {\n flex-shrink: 0;\n }\n\n .icon-wrapper {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-1-5x);\n }\n\n &.error .icon {\n color: var(--kds-color-text-and-icon-danger);\n }\n\n &.warning .icon {\n color: var(--kds-color-text-and-icon-warning);\n }\n\n &.note .icon {\n color: var(--kds-color-text-and-icon-info);\n }\n}\n\n.text {\n flex: 1 0 0;\n min-width: 0;\n\n /* Allow up to 2 lines, scroll beyond that. */\n max-height: calc(2 * 1lh);\n overflow-y: auto;\n overflow-wrap: anywhere;\n scrollbar-width: thin;\n\n &:focus-visible {\n outline: var(--kds-border-action-focused);\n outline-offset: var(--kds-spacing-offset-focus);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onMounted, ref, useTemplateRef } from \"vue\";\n\nimport { KdsButton, KdsCheckbox } from \"@knime/kds-components\";\n\nimport type {\n KdsPromptInputPermission,\n KdsPromptInputPermissionDecision,\n} from \"../types\";\n\nconst { permission } = defineProps<{\n permission: KdsPromptInputPermission;\n}>();\n\nconst emit = defineEmits<{\n decide: [decision: Omit<KdsPromptInputPermissionDecision, \"id\">];\n}>();\n\nconst isRememberChoiceChecked = ref(false);\n\nconst decide = (decision: \"allow\" | \"deny\") => {\n emit(\"decide\", {\n decision,\n ...(permission.remember && {\n remember: isRememberChoiceChecked.value,\n }),\n });\n};\n\n// Focus the \"Allow\" button on render so the user can proceed quickly by\n// pressing \"Enter\".\nconst allowButtonRef =\n useTemplateRef<InstanceType<typeof KdsButton>>(\"allowButton\");\nonMounted(() => {\n (allowButtonRef.value?.$el as HTMLElement | undefined)?.focus();\n});\n\nconst handleKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n decide(\"allow\");\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n decide(\"deny\");\n }\n};\n</script>\n\n<template>\n <div class=\"inquiry-card\" @keydown=\"handleKeydown\">\n <span class=\"title\">{{ permission.headline }}</span>\n\n <div class=\"content\">\n <p v-if=\"permission.description\" class=\"description\">\n {{ permission.description }}\n </p>\n\n <KdsCheckbox\n v-if=\"permission.remember\"\n v-model=\"isRememberChoiceChecked\"\n :label=\"permission.remember.label\"\n :sub-text=\"permission.remember.subtext\"\n />\n\n <div class=\"action-buttons\">\n <KdsButton\n variant=\"transparent\"\n size=\"small\"\n label=\"Deny\"\n @click=\"decide('deny')\"\n />\n <KdsButton\n ref=\"allowButton\"\n variant=\"outlined\"\n size=\"small\"\n label=\"Allow\"\n @click=\"decide('allow')\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.inquiry-card {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n\n & .title {\n font: var(--kds-font-base-title-medium-strong);\n color: var(--kds-color-text-and-icon-neutral);\n overflow-wrap: anywhere;\n }\n\n & .content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-5x);\n\n & .description {\n margin: 0;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-subtle);\n overflow-wrap: anywhere;\n }\n\n & .action-buttons {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n justify-content: end;\n }\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onMounted, ref, useTemplateRef } from \"vue\";\n\nimport { KdsButton, KdsCheckbox } from \"@knime/kds-components\";\n\nimport type {\n KdsPromptInputPermission,\n KdsPromptInputPermissionDecision,\n} from \"../types\";\n\nconst { permission } = defineProps<{\n permission: KdsPromptInputPermission;\n}>();\n\nconst emit = defineEmits<{\n decide: [decision: Omit<KdsPromptInputPermissionDecision, \"id\">];\n}>();\n\nconst isRememberChoiceChecked = ref(false);\n\nconst decide = (decision: \"allow\" | \"deny\") => {\n emit(\"decide\", {\n decision,\n ...(permission.remember && {\n remember: isRememberChoiceChecked.value,\n }),\n });\n};\n\n// Focus the \"Allow\" button on render so the user can proceed quickly by\n// pressing \"Enter\".\nconst allowButtonRef =\n useTemplateRef<InstanceType<typeof KdsButton>>(\"allowButton\");\nonMounted(() => {\n (allowButtonRef.value?.$el as HTMLElement | undefined)?.focus();\n});\n\nconst handleKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n decide(\"allow\");\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n decide(\"deny\");\n }\n};\n</script>\n\n<template>\n <div class=\"inquiry-card\" @keydown=\"handleKeydown\">\n <span class=\"title\">{{ permission.headline }}</span>\n\n <div class=\"content\">\n <p v-if=\"permission.description\" class=\"description\">\n {{ permission.description }}\n </p>\n\n <KdsCheckbox\n v-if=\"permission.remember\"\n v-model=\"isRememberChoiceChecked\"\n :label=\"permission.remember.label\"\n :sub-text=\"permission.remember.subtext\"\n />\n\n <div class=\"action-buttons\">\n <KdsButton\n variant=\"transparent\"\n size=\"small\"\n label=\"Deny\"\n @click=\"decide('deny')\"\n />\n <KdsButton\n ref=\"allowButton\"\n variant=\"outlined\"\n size=\"small\"\n label=\"Allow\"\n @click=\"decide('allow')\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.inquiry-card {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n\n & .title {\n font: var(--kds-font-base-title-medium-strong);\n color: var(--kds-color-text-and-icon-neutral);\n overflow-wrap: anywhere;\n }\n\n & .content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-5x);\n\n & .description {\n margin: 0;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-subtle);\n overflow-wrap: anywhere;\n }\n\n & .action-buttons {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n justify-content: end;\n }\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from \"vue\";\n\nimport {\n KdsButton,\n KdsCheckboxGroup,\n KdsRadioButtonGroup,\n KdsTextInput,\n} from \"@knime/kds-components\";\n\nimport type {\n KdsPromptInputQuestion,\n KdsPromptInputQuestionAnswer,\n} from \"../types\";\n\n/**\n * A single pending question, shown one at a time and paginated by the parent\n * stack.\n */\nconst props = defineProps<{\n question: KdsPromptInputQuestion;\n /** Zero-based position of this question within the stack. */\n pageIndex: number;\n /** Total number of questions in the stack. */\n pageCount: number;\n /**\n * True when every *other* question in the stack has already been resolved\n * (answered or skipped). Combined with this card's own selection it decides\n * whether \"Submit\" is enabled on the last page.\n */\n othersAnswered: boolean;\n}>();\n\nconst emit = defineEmits<{\n /** Move to the previous question without recording an answer. */\n back: [];\n /** Skip this question (records `skipped: true`) and move on. */\n skip: [];\n /** Record this answer and advance to the next question. */\n next: [answer: Omit<KdsPromptInputQuestionAnswer, \"id\">];\n /** Record this answer and finalize every inquiry. */\n submit: [answer: Omit<KdsPromptInputQuestionAnswer, \"id\">];\n}>();\n\n/**\n * Synthetic id of the trailing \"Type your own answer\" option. Prefixed so it\n * cannot collide with consumer-provided option ids.\n */\nconst FREEFORM_OPTION_ID = \"__kds-prompt-input-freeform__\";\n\nconst hasOptions = computed(() => (props.question.options ?? []).length > 0);\n\nconst optionValues = computed(() => [\n ...(props.question.options ?? []).map((option) => ({\n id: option.id,\n text: option.label,\n helperText: option.subText,\n })),\n { id: FREEFORM_OPTION_ID, text: \"Type your own answer\" },\n]);\n\nconst selectedSingleId = ref(\"\");\nconst selectedMultiIds = ref<string[]>([]);\nconst freeformText = ref(\"\");\n\n// Whether the user has skipped this question. Kept alongside the selection so\n// the parent's `KeepAlive` preserves it: paging back to a skipped question\n// still shows it as \"(Skipped)\".\nconst isSkipped = ref(false);\n\nconst selectedIds = computed(() => {\n if (props.question.multiple) {\n return selectedMultiIds.value;\n }\n return selectedSingleId.value ? [selectedSingleId.value] : [];\n});\n\n// The freeform input is always active for option-less questions; otherwise\n// it appears once the \"Type your own answer\" option is selected.\nconst isFreeformActive = computed(\n () => !hasOptions.value || selectedIds.value.includes(FREEFORM_OPTION_ID),\n);\n\nconst selectedOptionIds = computed(() =>\n selectedIds.value.filter((id) => id !== FREEFORM_OPTION_ID),\n);\n\nconst canAnswer = computed(\n () =>\n selectedOptionIds.value.length > 0 ||\n (isFreeformActive.value && freeformText.value.trim().length > 0),\n);\n\n// Making a real selection clears the skipped mark — the two are mutually\n// exclusive. (Skipping itself clears the selection, so this never fires on\n// skip.)\nwatch(canAnswer, (answerable) => {\n if (answerable) {\n isSkipped.value = false;\n }\n});\n\nconst isLast = computed(() => props.pageIndex === props.pageCount - 1);\n// The pager only makes sense across multiple questions. \"Back\" is shown only\n// once there is an earlier question to return to (i.e. past the first page);\n// its slot in the action row is always reserved so the layout stays stable.\nconst showPagination = computed(() => props.pageCount > 1);\nconst showBack = computed(() => props.pageIndex > 0);\n\nconst primaryLabel = computed(() => (isLast.value ? \"Submit\" : \"Next\"));\n// The current question counts as resolved once it is answered or skipped. A\n// skipped question stays resolved when the user pages back to it, so the\n// primary button must remain enabled there — not only after a fresh answer.\nconst currentResolved = computed(() => canAnswer.value || isSkipped.value);\nconst canProceed = computed(() =>\n // \"Next\" just needs the current question resolved. \"Submit\" additionally\n // needs every *other* question resolved (answered or skipped).\n isLast.value\n ? props.othersAnswered && currentResolved.value\n : currentResolved.value,\n);\n\nconst buildAnswer = (): Omit<KdsPromptInputQuestionAnswer, \"id\"> => {\n if (isSkipped.value) {\n return { selectedOptionIds: [], skipped: true };\n }\n const freeform = isFreeformActive.value ? freeformText.value.trim() : \"\";\n return {\n selectedOptionIds: selectedOptionIds.value,\n ...(freeform && { freeformInput: freeform }),\n };\n};\n\nconst proceed = () => {\n if (!canProceed.value) {\n return;\n }\n if (isLast.value) {\n emit(\"submit\", buildAnswer());\n } else {\n emit(\"next\", buildAnswer());\n }\n};\n\nconst skip = () => {\n selectedSingleId.value = \"\";\n selectedMultiIds.value = [];\n freeformText.value = \"\";\n isSkipped.value = true;\n emit(\"skip\");\n};\n\nconst handleKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n proceed();\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n skip();\n }\n};\n</script>\n\n<template>\n <div class=\"inquiry-card\" @keydown=\"handleKeydown\">\n <div class=\"title-row\">\n <span class=\"title\"\n >{{ question.headline\n }}<span v-if=\"isSkipped\" class=\"skipped-tag\">(Skipped)</span></span\n >\n <span v-if=\"showPagination\" class=\"pagination\">\n {{ pageIndex + 1 }} of {{ pageCount }}\n </span>\n </div>\n\n <div class=\"content\">\n <p v-if=\"question.description\" class=\"description\">\n {{ question.description }}\n </p>\n\n <KdsRadioButtonGroup\n v-if=\"!question.multiple && hasOptions\"\n v-model=\"selectedSingleId\"\n :possible-values=\"optionValues\"\n alignment=\"vertical\"\n />\n <KdsCheckboxGroup\n v-if=\"question.multiple && hasOptions\"\n v-model=\"selectedMultiIds\"\n :possible-values=\"optionValues\"\n alignment=\"vertical\"\n />\n\n <KdsTextInput\n v-if=\"isFreeformActive\"\n v-model=\"freeformText\"\n ariaLabel=\"Type your own answer\"\n placeholder=\"Type your own answer...\"\n />\n\n <div class=\"action-buttons\">\n <div class=\"nav-secondary\">\n <KdsButton\n v-if=\"showBack\"\n variant=\"transparent\"\n size=\"small\"\n label=\"Back\"\n @click=\"emit('back')\"\n />\n </div>\n <div class=\"nav-primary\">\n <KdsButton\n variant=\"transparent\"\n size=\"small\"\n label=\"Skip\"\n @click=\"skip\"\n />\n <KdsButton\n variant=\"outlined\"\n size=\"small\"\n :label=\"primaryLabel\"\n :disabled=\"!canProceed\"\n @click=\"proceed\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.inquiry-card {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n\n & .title-row {\n display: flex;\n gap: var(--kds-spacing-container-1x);\n align-items: center;\n\n & .title {\n flex: 1 1 0;\n min-width: 0;\n font: var(--kds-font-base-title-medium-strong);\n color: var(--kds-color-text-and-icon-neutral);\n overflow-wrap: anywhere;\n\n & .skipped-tag {\n margin-left: var(--kds-spacing-container-0-25x);\n font: var(--kds-font-base-title-medium);\n color: var(--kds-color-text-and-icon-subtle);\n }\n }\n\n & .pagination {\n flex-shrink: 0;\n font: var(--kds-font-base-title-medium);\n color: var(--kds-color-text-and-icon-neutral);\n white-space: nowrap;\n }\n }\n\n & .content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-5x);\n\n & .description {\n margin: 0;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-subtle);\n overflow-wrap: anywhere;\n }\n\n & .action-buttons {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n justify-content: space-between;\n\n & .nav-primary {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n }\n }\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from \"vue\";\n\nimport {\n KdsButton,\n KdsCheckboxGroup,\n KdsRadioButtonGroup,\n KdsTextInput,\n} from \"@knime/kds-components\";\n\nimport type {\n KdsPromptInputQuestion,\n KdsPromptInputQuestionAnswer,\n} from \"../types\";\n\n/**\n * A single pending question, shown one at a time and paginated by the parent\n * stack.\n */\nconst props = defineProps<{\n question: KdsPromptInputQuestion;\n /** Zero-based position of this question within the stack. */\n pageIndex: number;\n /** Total number of questions in the stack. */\n pageCount: number;\n /**\n * True when every *other* question in the stack has already been resolved\n * (answered or skipped). Combined with this card's own selection it decides\n * whether \"Submit\" is enabled on the last page.\n */\n othersAnswered: boolean;\n}>();\n\nconst emit = defineEmits<{\n /** Move to the previous question without recording an answer. */\n back: [];\n /** Skip this question (records `skipped: true`) and move on. */\n skip: [];\n /** Record this answer and advance to the next question. */\n next: [answer: Omit<KdsPromptInputQuestionAnswer, \"id\">];\n /** Record this answer and finalize every inquiry. */\n submit: [answer: Omit<KdsPromptInputQuestionAnswer, \"id\">];\n}>();\n\n/**\n * Synthetic id of the trailing \"Type your own answer\" option. Prefixed so it\n * cannot collide with consumer-provided option ids.\n */\nconst FREEFORM_OPTION_ID = \"__kds-prompt-input-freeform__\";\n\nconst hasOptions = computed(() => (props.question.options ?? []).length > 0);\n\nconst optionValues = computed(() => [\n ...(props.question.options ?? []).map((option) => ({\n id: option.id,\n text: option.label,\n helperText: option.subText,\n })),\n { id: FREEFORM_OPTION_ID, text: \"Type your own answer\" },\n]);\n\nconst selectedSingleId = ref(\"\");\nconst selectedMultiIds = ref<string[]>([]);\nconst freeformText = ref(\"\");\n\n// Whether the user has skipped this question. Kept alongside the selection so\n// the parent's `KeepAlive` preserves it: paging back to a skipped question\n// still shows it as \"(Skipped)\".\nconst isSkipped = ref(false);\n\nconst selectedIds = computed(() => {\n if (props.question.multiple) {\n return selectedMultiIds.value;\n }\n return selectedSingleId.value ? [selectedSingleId.value] : [];\n});\n\n// The freeform input is always active for option-less questions; otherwise\n// it appears once the \"Type your own answer\" option is selected.\nconst isFreeformActive = computed(\n () => !hasOptions.value || selectedIds.value.includes(FREEFORM_OPTION_ID),\n);\n\nconst selectedOptionIds = computed(() =>\n selectedIds.value.filter((id) => id !== FREEFORM_OPTION_ID),\n);\n\nconst canAnswer = computed(\n () =>\n selectedOptionIds.value.length > 0 ||\n (isFreeformActive.value && freeformText.value.trim().length > 0),\n);\n\n// Making a real selection clears the skipped mark — the two are mutually\n// exclusive. (Skipping itself clears the selection, so this never fires on\n// skip.)\nwatch(canAnswer, (answerable) => {\n if (answerable) {\n isSkipped.value = false;\n }\n});\n\nconst isLast = computed(() => props.pageIndex === props.pageCount - 1);\n// The pager only makes sense across multiple questions. \"Back\" is shown only\n// once there is an earlier question to return to (i.e. past the first page);\n// its slot in the action row is always reserved so the layout stays stable.\nconst showPagination = computed(() => props.pageCount > 1);\nconst showBack = computed(() => props.pageIndex > 0);\n\nconst primaryLabel = computed(() => (isLast.value ? \"Submit\" : \"Next\"));\n// The current question counts as resolved once it is answered or skipped. A\n// skipped question stays resolved when the user pages back to it, so the\n// primary button must remain enabled there — not only after a fresh answer.\nconst currentResolved = computed(() => canAnswer.value || isSkipped.value);\nconst canProceed = computed(() =>\n // \"Next\" just needs the current question resolved. \"Submit\" additionally\n // needs every *other* question resolved (answered or skipped).\n isLast.value\n ? props.othersAnswered && currentResolved.value\n : currentResolved.value,\n);\n\nconst buildAnswer = (): Omit<KdsPromptInputQuestionAnswer, \"id\"> => {\n if (isSkipped.value) {\n return { selectedOptionIds: [], skipped: true };\n }\n const freeform = isFreeformActive.value ? freeformText.value.trim() : \"\";\n return {\n selectedOptionIds: selectedOptionIds.value,\n ...(freeform && { freeformInput: freeform }),\n };\n};\n\nconst proceed = () => {\n if (!canProceed.value) {\n return;\n }\n if (isLast.value) {\n emit(\"submit\", buildAnswer());\n } else {\n emit(\"next\", buildAnswer());\n }\n};\n\nconst skip = () => {\n selectedSingleId.value = \"\";\n selectedMultiIds.value = [];\n freeformText.value = \"\";\n isSkipped.value = true;\n emit(\"skip\");\n};\n\nconst handleKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n proceed();\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n skip();\n }\n};\n</script>\n\n<template>\n <div class=\"inquiry-card\" @keydown=\"handleKeydown\">\n <div class=\"title-row\">\n <span class=\"title\"\n >{{ question.headline\n }}<span v-if=\"isSkipped\" class=\"skipped-tag\">(Skipped)</span></span\n >\n <span v-if=\"showPagination\" class=\"pagination\">\n {{ pageIndex + 1 }} of {{ pageCount }}\n </span>\n </div>\n\n <div class=\"content\">\n <p v-if=\"question.description\" class=\"description\">\n {{ question.description }}\n </p>\n\n <KdsRadioButtonGroup\n v-if=\"!question.multiple && hasOptions\"\n v-model=\"selectedSingleId\"\n :possible-values=\"optionValues\"\n alignment=\"vertical\"\n />\n <KdsCheckboxGroup\n v-if=\"question.multiple && hasOptions\"\n v-model=\"selectedMultiIds\"\n :possible-values=\"optionValues\"\n alignment=\"vertical\"\n />\n\n <KdsTextInput\n v-if=\"isFreeformActive\"\n v-model=\"freeformText\"\n ariaLabel=\"Type your own answer\"\n placeholder=\"Type your own answer...\"\n />\n\n <div class=\"action-buttons\">\n <div class=\"nav-secondary\">\n <KdsButton\n v-if=\"showBack\"\n variant=\"transparent\"\n size=\"small\"\n label=\"Back\"\n @click=\"emit('back')\"\n />\n </div>\n <div class=\"nav-primary\">\n <KdsButton\n variant=\"transparent\"\n size=\"small\"\n label=\"Skip\"\n @click=\"skip\"\n />\n <KdsButton\n variant=\"outlined\"\n size=\"small\"\n :label=\"primaryLabel\"\n :disabled=\"!canProceed\"\n @click=\"proceed\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.inquiry-card {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n\n & .title-row {\n display: flex;\n gap: var(--kds-spacing-container-1x);\n align-items: center;\n\n & .title {\n flex: 1 1 0;\n min-width: 0;\n font: var(--kds-font-base-title-medium-strong);\n color: var(--kds-color-text-and-icon-neutral);\n overflow-wrap: anywhere;\n\n & .skipped-tag {\n margin-left: var(--kds-spacing-container-0-25x);\n font: var(--kds-font-base-title-medium);\n color: var(--kds-color-text-and-icon-subtle);\n }\n }\n\n & .pagination {\n flex-shrink: 0;\n font: var(--kds-font-base-title-medium);\n color: var(--kds-color-text-and-icon-neutral);\n white-space: nowrap;\n }\n }\n\n & .content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-5x);\n\n & .description {\n margin: 0;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-subtle);\n overflow-wrap: anywhere;\n }\n\n & .action-buttons {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n justify-content: space-between;\n\n & .nav-primary {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n }\n }\n }\n}\n</style>\n","import { computed, reactive } from \"vue\";\n\nimport type {\n KdsPromptInputPermission,\n KdsPromptInputPermissionDecision,\n} from \"../types\";\n\ntype PermissionDecision = Omit<KdsPromptInputPermissionDecision, \"id\">;\n\n/**\n * The permission phase: pending permissions resolved like a stack, with no\n * pager. The current permission is always the first still-undecided one;\n * deciding it pops it off and reveals the next. Buffered decisions persist\n * until {@link reset} so the parent can gather them into a single `respond`\n * once every phase is complete.\n *\n * @param permissions Getter for the pending permissions (kept reactive).\n */\nexport function usePermissionPhase(\n permissions: () => KdsPromptInputPermission[],\n) {\n // Keyed by the permission's own id; never shared with the question phase.\n const decisions = reactive(new Map<string, PermissionDecision>());\n\n /** The first permission not yet decided, or `undefined` once none remain. */\n const current = computed(() =>\n permissions().find((permission) => !decisions.has(permission.id)),\n );\n\n const isComplete = computed(() =>\n permissions().every((permission) => decisions.has(permission.id)),\n );\n\n /**\n * Record a decision for `permission`. Returns `false` (a no-op) when the\n * permission was already decided — e.g. a double click landing before the\n * stack pops the card off — so the caller can avoid emitting `respond` twice.\n *\n * A remembered decision also resolves every other undecided permission of the\n * same `remember.group` (e.g. repeated calls of one tool), so the user is not\n * asked again for a choice they just saved. Those never surface — the group\n * is popped silently.\n */\n const decide = (\n permission: KdsPromptInputPermission,\n value: PermissionDecision,\n ): boolean => {\n if (decisions.has(permission.id)) {\n return false;\n }\n decisions.set(permission.id, value);\n\n const group = permission.remember?.group;\n if (group && value.remember) {\n for (const other of permissions()) {\n if (other.remember?.group === group && !decisions.has(other.id)) {\n decisions.set(other.id, value);\n }\n }\n }\n return true;\n };\n\n /** Buffered decisions in prop order, ready for the `respond` payload. */\n const toOrderedDecisions = (): KdsPromptInputPermissionDecision[] =>\n permissions().map((permission) => ({\n id: permission.id,\n ...(decisions.get(permission.id) as PermissionDecision),\n }));\n\n const reset = () => decisions.clear();\n\n return { current, isComplete, decide, toOrderedDecisions, reset };\n}\n","import { computed, reactive, ref } from \"vue\";\n\nimport type {\n KdsPromptInputQuestion,\n KdsPromptInputQuestionAnswer,\n} from \"../types\";\n\ntype QuestionAnswer = Omit<KdsPromptInputQuestionAnswer, \"id\">;\n\n/**\n * The question phase: pending questions paginated one at a time. The user moves\n * forward with {@link answer} + {@link advance} (or {@link skip}) and back with\n * {@link back}, changing earlier selections freely. Buffered answers persist\n * until {@link reset} so the parent can gather them into a single `respond`\n * once every phase is complete.\n *\n * @param questions Getter for the pending questions (kept reactive).\n */\nexport function useQuestionPhase(questions: () => KdsPromptInputQuestion[]) {\n // Keyed by the question's own id; never shared with the permission phase.\n const answers = reactive(new Map<string, QuestionAnswer>());\n const index = ref(0);\n\n const current = computed(() => questions()[index.value]);\n const pageCount = computed(() => questions().length);\n\n const isComplete = computed(() =>\n questions().every((question) => answers.has(question.id)),\n );\n\n /**\n * True when every question *other than the current one* has been resolved.\n * The current card supplies its own answer live, so together they decide\n * whether \"Submit\" is enabled on the last page.\n */\n const othersAnswered = computed(() =>\n questions().every(\n (question, i) => i === index.value || answers.has(question.id),\n ),\n );\n\n const advance = () => {\n index.value = Math.min(index.value + 1, questions().length - 1);\n };\n\n const back = () => {\n if (index.value > 0) {\n index.value -= 1;\n }\n };\n\n const answer = (question: KdsPromptInputQuestion, value: QuestionAnswer) => {\n answers.set(question.id, value);\n };\n\n const skip = (question: KdsPromptInputQuestion) => {\n answers.set(question.id, { selectedOptionIds: [], skipped: true });\n };\n\n /** Buffered answers in prop order, ready for the `respond` payload. */\n const toOrderedAnswers = (): KdsPromptInputQuestionAnswer[] =>\n questions().map((question) => ({\n id: question.id,\n ...(answers.get(question.id) as QuestionAnswer),\n }));\n\n const reset = () => {\n answers.clear();\n index.value = 0;\n };\n\n return {\n current,\n index,\n pageCount,\n isComplete,\n othersAnswered,\n advance,\n back,\n answer,\n skip,\n toOrderedAnswers,\n reset,\n };\n}\n","<script setup lang=\"ts\">\nimport { watch } from \"vue\";\n\nimport AttachableSurface from \"../AttachableSurface.vue\";\nimport type {\n KdsPromptInputInquiryResponse,\n KdsPromptInputPermission,\n KdsPromptInputPermissionDecision,\n KdsPromptInputQuestion,\n KdsPromptInputQuestionAnswer,\n} from \"../types\";\n\nimport PermissionCard from \"./PermissionCard.vue\";\nimport QuestionCard from \"./QuestionCard.vue\";\nimport { usePermissionPhase } from \"./usePermissionPhase\";\nimport { useQuestionPhase } from \"./useQuestionPhase\";\n\n/**\n * The pending permissions and questions, attached as a surface above the\n * input. The two are resolved in distinct, sequential phases, see\n * the corresponding composables:\n *\n * - {@link usePermissionPhase} is first. Deciding one permission pops it off and reveals the next.\n * - {@link useQuestionPhase} follows once every permission is addressed: a stack\n * the user moves through with \"Next\" / \"Skip\" and \"Back\".\n *\n * This component only sequences the two phases and, once both are complete,\n * emits a single `respond` carrying every decision (all-or-nothing per turn).\n * For the common single-inquiry case this degenerates to one card that responds\n * immediately.\n */\nconst { permissions, questions } = defineProps<{\n permissions: KdsPromptInputPermission[];\n questions: KdsPromptInputQuestion[];\n}>();\n\nconst emit = defineEmits<{\n respond: [response: KdsPromptInputInquiryResponse];\n}>();\n\nconst {\n current: currentPermission,\n isComplete: permissionsComplete,\n decide: decidePermission,\n toOrderedDecisions,\n reset: resetPermissions,\n} = usePermissionPhase(() => permissions);\n\nconst {\n current: currentQuestion,\n index: questionIndex,\n pageCount: questionCount,\n isComplete: questionsComplete,\n othersAnswered,\n advance: advanceQuestion,\n back: goBack,\n answer: recordAnswer,\n skip: skipQuestion,\n toOrderedAnswers,\n reset: resetQuestions,\n} = useQuestionPhase(() => questions);\n\n// A new set of pending inquiries invalidates both phases' buffered state.\nwatch(\n () => [...permissions, ...questions].map((inquiry) => inquiry.id).join(\",\"),\n () => {\n resetPermissions();\n resetQuestions();\n },\n);\n\n// Emit once both phases are resolved. Either the last permission decision (when there\n// are no questions) or the question \"Submit\".\nconst respondIfComplete = () => {\n if (!permissionsComplete.value || !questionsComplete.value) {\n return;\n }\n emit(\"respond\", {\n permissions: toOrderedDecisions(),\n questions: toOrderedAnswers(),\n });\n};\n\nconst onDecidePermission = (\n permission: KdsPromptInputPermission,\n decision: Omit<KdsPromptInputPermissionDecision, \"id\">,\n) => {\n // `decidePermission` is a no-op (returns false) for an already-resolved card,\n // so a double click cannot fire `respond` twice.\n if (decidePermission(permission, decision)) {\n respondIfComplete();\n }\n};\n\nconst onQuestionNext = (\n question: KdsPromptInputQuestion,\n answer: Omit<KdsPromptInputQuestionAnswer, \"id\">,\n) => {\n recordAnswer(question, answer);\n advanceQuestion();\n};\n\nconst onQuestionSkip = (question: KdsPromptInputQuestion) => {\n // Skip resolves the question as skipped and moves on.\n // On the last question `advanceQuestion` is a clamp\n // leaving \"Submit\" as the only way forward.\n skipQuestion(question);\n advanceQuestion();\n};\n\nconst onQuestionSubmit = (\n question: KdsPromptInputQuestion,\n answer: Omit<KdsPromptInputQuestionAnswer, \"id\">,\n) => {\n recordAnswer(question, answer);\n respondIfComplete();\n};\n</script>\n\n<template>\n <AttachableSurface v-if=\"currentPermission || currentQuestion\" tone=\"neutral\">\n <div class=\"inquiry-stack\">\n <PermissionCard\n v-if=\"currentPermission\"\n :key=\"currentPermission.id\"\n :permission=\"currentPermission\"\n @decide=\"(decision) => onDecidePermission(currentPermission!, decision)\"\n />\n\n <!-- KeepAlive preserves each question's in-progress state (selected\n options, freeform text) while the user pages back and forth before\n confirming. The cache lives only as long as the stack itself, which\n unmounts once the turn's inquiries are resolved. -->\n <KeepAlive v-else-if=\"currentQuestion\">\n <QuestionCard\n :key=\"currentQuestion.id\"\n :question=\"currentQuestion\"\n :page-index=\"questionIndex\"\n :page-count=\"questionCount\"\n :others-answered=\"othersAnswered\"\n @back=\"goBack\"\n @skip=\"onQuestionSkip(currentQuestion!)\"\n @next=\"(answer) => onQuestionNext(currentQuestion!, answer)\"\n @submit=\"(answer) => onQuestionSubmit(currentQuestion!, answer)\"\n />\n </KeepAlive>\n </div>\n </AttachableSurface>\n</template>\n\n<style scoped>\n.inquiry-stack {\n padding: calc(\n var(--kds-spacing-container-1x) - var(--kds-core-border-width-xs)\n )\n calc(var(--kds-spacing-container-1x) - var(--kds-core-border-width-xs))\n var(--kds-spacing-container-1x);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { watch } from \"vue\";\n\nimport AttachableSurface from \"../AttachableSurface.vue\";\nimport type {\n KdsPromptInputInquiryResponse,\n KdsPromptInputPermission,\n KdsPromptInputPermissionDecision,\n KdsPromptInputQuestion,\n KdsPromptInputQuestionAnswer,\n} from \"../types\";\n\nimport PermissionCard from \"./PermissionCard.vue\";\nimport QuestionCard from \"./QuestionCard.vue\";\nimport { usePermissionPhase } from \"./usePermissionPhase\";\nimport { useQuestionPhase } from \"./useQuestionPhase\";\n\n/**\n * The pending permissions and questions, attached as a surface above the\n * input. The two are resolved in distinct, sequential phases, see\n * the corresponding composables:\n *\n * - {@link usePermissionPhase} is first. Deciding one permission pops it off and reveals the next.\n * - {@link useQuestionPhase} follows once every permission is addressed: a stack\n * the user moves through with \"Next\" / \"Skip\" and \"Back\".\n *\n * This component only sequences the two phases and, once both are complete,\n * emits a single `respond` carrying every decision (all-or-nothing per turn).\n * For the common single-inquiry case this degenerates to one card that responds\n * immediately.\n */\nconst { permissions, questions } = defineProps<{\n permissions: KdsPromptInputPermission[];\n questions: KdsPromptInputQuestion[];\n}>();\n\nconst emit = defineEmits<{\n respond: [response: KdsPromptInputInquiryResponse];\n}>();\n\nconst {\n current: currentPermission,\n isComplete: permissionsComplete,\n decide: decidePermission,\n toOrderedDecisions,\n reset: resetPermissions,\n} = usePermissionPhase(() => permissions);\n\nconst {\n current: currentQuestion,\n index: questionIndex,\n pageCount: questionCount,\n isComplete: questionsComplete,\n othersAnswered,\n advance: advanceQuestion,\n back: goBack,\n answer: recordAnswer,\n skip: skipQuestion,\n toOrderedAnswers,\n reset: resetQuestions,\n} = useQuestionPhase(() => questions);\n\n// A new set of pending inquiries invalidates both phases' buffered state.\nwatch(\n () => [...permissions, ...questions].map((inquiry) => inquiry.id).join(\",\"),\n () => {\n resetPermissions();\n resetQuestions();\n },\n);\n\n// Emit once both phases are resolved. Either the last permission decision (when there\n// are no questions) or the question \"Submit\".\nconst respondIfComplete = () => {\n if (!permissionsComplete.value || !questionsComplete.value) {\n return;\n }\n emit(\"respond\", {\n permissions: toOrderedDecisions(),\n questions: toOrderedAnswers(),\n });\n};\n\nconst onDecidePermission = (\n permission: KdsPromptInputPermission,\n decision: Omit<KdsPromptInputPermissionDecision, \"id\">,\n) => {\n // `decidePermission` is a no-op (returns false) for an already-resolved card,\n // so a double click cannot fire `respond` twice.\n if (decidePermission(permission, decision)) {\n respondIfComplete();\n }\n};\n\nconst onQuestionNext = (\n question: KdsPromptInputQuestion,\n answer: Omit<KdsPromptInputQuestionAnswer, \"id\">,\n) => {\n recordAnswer(question, answer);\n advanceQuestion();\n};\n\nconst onQuestionSkip = (question: KdsPromptInputQuestion) => {\n // Skip resolves the question as skipped and moves on.\n // On the last question `advanceQuestion` is a clamp\n // leaving \"Submit\" as the only way forward.\n skipQuestion(question);\n advanceQuestion();\n};\n\nconst onQuestionSubmit = (\n question: KdsPromptInputQuestion,\n answer: Omit<KdsPromptInputQuestionAnswer, \"id\">,\n) => {\n recordAnswer(question, answer);\n respondIfComplete();\n};\n</script>\n\n<template>\n <AttachableSurface v-if=\"currentPermission || currentQuestion\" tone=\"neutral\">\n <div class=\"inquiry-stack\">\n <PermissionCard\n v-if=\"currentPermission\"\n :key=\"currentPermission.id\"\n :permission=\"currentPermission\"\n @decide=\"(decision) => onDecidePermission(currentPermission!, decision)\"\n />\n\n <!-- KeepAlive preserves each question's in-progress state (selected\n options, freeform text) while the user pages back and forth before\n confirming. The cache lives only as long as the stack itself, which\n unmounts once the turn's inquiries are resolved. -->\n <KeepAlive v-else-if=\"currentQuestion\">\n <QuestionCard\n :key=\"currentQuestion.id\"\n :question=\"currentQuestion\"\n :page-index=\"questionIndex\"\n :page-count=\"questionCount\"\n :others-answered=\"othersAnswered\"\n @back=\"goBack\"\n @skip=\"onQuestionSkip(currentQuestion!)\"\n @next=\"(answer) => onQuestionNext(currentQuestion!, answer)\"\n @submit=\"(answer) => onQuestionSubmit(currentQuestion!, answer)\"\n />\n </KeepAlive>\n </div>\n </AttachableSurface>\n</template>\n\n<style scoped>\n.inquiry-stack {\n padding: calc(\n var(--kds-spacing-container-1x) - var(--kds-core-border-width-xs)\n )\n calc(var(--kds-spacing-container-1x) - var(--kds-core-border-width-xs))\n var(--kds-spacing-container-1x);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useTemplateRef } from \"vue\";\n\nimport { KdsButton, KdsLink, KdsProgressBar } from \"@knime/kds-components\";\n\nimport InlineStatusBanner from \"./InlineStatusBanner.vue\";\nimport { InquiryStack } from \"./inquiries\";\nimport type {\n KdsPromptInputInquiryResponse,\n KdsPromptInputProps,\n} from \"./types\";\n\nconst {\n placeholder = \"\",\n disabled = false,\n loading = false,\n status = undefined,\n ariaLabel = \"Prompt\",\n leftSubtext = undefined,\n rightSubtext = undefined,\n link = undefined,\n permissions = [],\n questions = [],\n} = defineProps<KdsPromptInputProps>();\n\nconst emit = defineEmits<{\n /** User submitted the prompt (Enter or send button). */\n submit: [value: string];\n /** User clicked the stop button while loading. */\n stop: [];\n /** User clicked the action button inside the inline status banner. */\n statusButtonClick: [];\n /** User decided every pending permission and answered every question. */\n respond: [response: KdsPromptInputInquiryResponse];\n}>();\n\nconst modelValue = defineModel<string>({ default: \"\" });\n\nconst textareaRef = useTemplateRef<HTMLTextAreaElement>(\"textareaRef\");\n\nconst hasInquiries = computed(\n () => permissions.length > 0 || questions.length > 0,\n);\n\n// While permissions or questions are shown, the input is disabled, so\n// responding to them is the only way to continue the conversation.\nconst isDisabled = computed(() => disabled || hasInquiries.value);\n\nconst canSubmit = computed(\n () => !isDisabled.value && !loading && modelValue.value.trim().length > 0,\n);\n\nfunction onSubmit() {\n if (!canSubmit.value) {\n return;\n }\n emit(\"submit\", modelValue.value.trim());\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n // Enter submits, Shift+Enter inserts a newline. Ignore Enter while an IME\n // composition session is in progress.\n if (event.key === \"Enter\" && !event.shiftKey && !event.isComposing) {\n event.preventDefault();\n onSubmit();\n }\n}\n\ndefineExpose({\n focus: () => textareaRef.value?.focus(),\n});\n</script>\n\n<template>\n <div class=\"prompt-input\">\n <InlineStatusBanner\n v-if=\"status\"\n :status=\"status\"\n @button-click=\"emit('statusButtonClick')\"\n />\n\n <InquiryStack\n v-if=\"hasInquiries\"\n :permissions=\"permissions\"\n :questions=\"questions\"\n @respond=\"emit('respond', $event)\"\n />\n\n <div\n :class=\"['input-container', { disabled: isDisabled }]\"\n @click=\"textareaRef?.focus()\"\n >\n <div v-if=\"loading\" class=\"progress-bar\">\n <KdsProgressBar indeterminate />\n </div>\n\n <div class=\"text-input-wrapper\">\n <textarea\n ref=\"textareaRef\"\n v-model=\"modelValue\"\n class=\"text-input\"\n rows=\"1\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n :aria-label=\"ariaLabel\"\n @keydown=\"onKeydown\"\n />\n </div>\n\n <div class=\"action\">\n <KdsButton\n v-if=\"loading\"\n variant=\"outlined\"\n leading-icon=\"stop\"\n size=\"medium\"\n ariaLabel=\"Stop\"\n @click.stop=\"emit('stop')\"\n />\n <KdsButton\n v-else\n variant=\"filled\"\n leading-icon=\"send\"\n size=\"medium\"\n ariaLabel=\"Send\"\n :disabled=\"!canSubmit\"\n @click.stop=\"onSubmit\"\n />\n </div>\n </div>\n\n <div v-if=\"leftSubtext || rightSubtext || link\" class=\"subtext\">\n <div v-if=\"leftSubtext\" class=\"subtext-left\">{{ leftSubtext }}</div>\n <div v-if=\"rightSubtext || link\" class=\"subtext-right\">\n <span v-if=\"rightSubtext\">{{ rightSubtext }}</span>\n <KdsLink v-if=\"link\" :label=\"link.label\" :to=\"link.to\" size=\"small\" />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.prompt-input {\n display: flex;\n flex-direction: column;\n width: 100%;\n}\n\n.input-container {\n position: relative;\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n align-items: flex-end;\n padding: calc(\n var(--kds-spacing-container-0-37x) - var(--kds-core-border-width-xs)\n );\n overflow: hidden;\n cursor: text;\n background: var(--kds-color-background-input-initial);\n border: var(--kds-border-action-input);\n border-radius: var(--kds-border-radius-container-0-37x);\n\n &:has(textarea:focus:not(:disabled)) {\n outline: var(--kds-border-action-focused);\n outline-offset: var(--kds-spacing-offset-focus);\n }\n\n &:has(textarea:hover:not(:disabled, :focus)) {\n background: var(--kds-color-background-input-hover);\n }\n\n &.disabled {\n cursor: default;\n border: var(--kds-border-action-disabled);\n }\n}\n\n.text-input-wrapper {\n display: flex;\n flex: 1 0 0;\n align-items: center;\n min-width: 0;\n\n /*\n * Match the Send/Stop button height so a single-line textarea sits\n * vertically centered against the button.\n */\n min-height: var(--kds-dimension-component-height-1-75x);\n padding-left: var(--kds-spacing-container-0-37x);\n}\n\n.text-input {\n width: 100%;\n min-width: 0;\n\n /* Cap textarea growth at 4 lines, scroll beyond that. */\n field-sizing: content;\n max-height: calc(4 * 1lh);\n padding: 0;\n overflow-y: auto;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n resize: none;\n outline: none;\n scrollbar-width: none;\n background: transparent;\n border: none;\n\n &::placeholder {\n color: var(--kds-color-text-and-icon-subtle);\n }\n\n &:disabled {\n color: var(--kds-color-text-and-icon-disabled);\n cursor: default;\n\n &::placeholder {\n color: var(--kds-color-text-and-icon-disabled);\n }\n }\n\n &::-webkit-scrollbar {\n display: none;\n }\n}\n\n.action {\n display: flex;\n flex-shrink: 0;\n align-items: flex-end;\n}\n\n.progress-bar {\n position: absolute;\n inset: -1px 0 auto;\n}\n\n.subtext {\n display: flex;\n flex-wrap: wrap;\n gap: var(--kds-spacing-container-0-25x) var(--kds-spacing-container-0-5x);\n align-items: center;\n padding: var(--kds-spacing-container-0-25x) var(--kds-spacing-container-0-25x)\n 0;\n font: var(--kds-font-base-subtext-small);\n color: var(--kds-color-text-and-icon-neutral);\n}\n\n.subtext-left {\n /*\n * Take all remaining inline space so the right block is pushed to the\n * end. `min-width: 0` lets the ellipsis kick in when the right block\n * grows past the container width.\n */\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.subtext-right {\n /*\n * On row 1 (alongside .subtext-left) the right block stays at its\n * intrinsic width — line-breaking in flex layout is decided from\n * `flex-basis`, so it wraps onto its own row as soon as the container\n * is narrower than that intrinsic width. On row 2 (its own row) the\n * default `flex-shrink: 1` plus `min-width: 0` lets it shrink to the\n * container width and wrap its own content (text + link).\n */\n display: flex;\n flex-wrap: wrap;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-width: 0;\n margin-left: auto;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useTemplateRef } from \"vue\";\n\nimport { KdsButton, KdsLink, KdsProgressBar } from \"@knime/kds-components\";\n\nimport InlineStatusBanner from \"./InlineStatusBanner.vue\";\nimport { InquiryStack } from \"./inquiries\";\nimport type {\n KdsPromptInputInquiryResponse,\n KdsPromptInputProps,\n} from \"./types\";\n\nconst {\n placeholder = \"\",\n disabled = false,\n loading = false,\n status = undefined,\n ariaLabel = \"Prompt\",\n leftSubtext = undefined,\n rightSubtext = undefined,\n link = undefined,\n permissions = [],\n questions = [],\n} = defineProps<KdsPromptInputProps>();\n\nconst emit = defineEmits<{\n /** User submitted the prompt (Enter or send button). */\n submit: [value: string];\n /** User clicked the stop button while loading. */\n stop: [];\n /** User clicked the action button inside the inline status banner. */\n statusButtonClick: [];\n /** User decided every pending permission and answered every question. */\n respond: [response: KdsPromptInputInquiryResponse];\n}>();\n\nconst modelValue = defineModel<string>({ default: \"\" });\n\nconst textareaRef = useTemplateRef<HTMLTextAreaElement>(\"textareaRef\");\n\nconst hasInquiries = computed(\n () => permissions.length > 0 || questions.length > 0,\n);\n\n// While permissions or questions are shown, the input is disabled, so\n// responding to them is the only way to continue the conversation.\nconst isDisabled = computed(() => disabled || hasInquiries.value);\n\nconst canSubmit = computed(\n () => !isDisabled.value && !loading && modelValue.value.trim().length > 0,\n);\n\nfunction onSubmit() {\n if (!canSubmit.value) {\n return;\n }\n emit(\"submit\", modelValue.value.trim());\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n // Enter submits, Shift+Enter inserts a newline. Ignore Enter while an IME\n // composition session is in progress.\n if (event.key === \"Enter\" && !event.shiftKey && !event.isComposing) {\n event.preventDefault();\n onSubmit();\n }\n}\n\ndefineExpose({\n focus: () => textareaRef.value?.focus(),\n});\n</script>\n\n<template>\n <div class=\"prompt-input\">\n <InlineStatusBanner\n v-if=\"status\"\n :status=\"status\"\n @button-click=\"emit('statusButtonClick')\"\n />\n\n <InquiryStack\n v-if=\"hasInquiries\"\n :permissions=\"permissions\"\n :questions=\"questions\"\n @respond=\"emit('respond', $event)\"\n />\n\n <div\n :class=\"['input-container', { disabled: isDisabled }]\"\n @click=\"textareaRef?.focus()\"\n >\n <div v-if=\"loading\" class=\"progress-bar\">\n <KdsProgressBar indeterminate />\n </div>\n\n <div class=\"text-input-wrapper\">\n <textarea\n ref=\"textareaRef\"\n v-model=\"modelValue\"\n class=\"text-input\"\n rows=\"1\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n :aria-label=\"ariaLabel\"\n @keydown=\"onKeydown\"\n />\n </div>\n\n <div class=\"action\">\n <KdsButton\n v-if=\"loading\"\n variant=\"outlined\"\n leading-icon=\"stop\"\n size=\"medium\"\n ariaLabel=\"Stop\"\n @click.stop=\"emit('stop')\"\n />\n <KdsButton\n v-else\n variant=\"filled\"\n leading-icon=\"send\"\n size=\"medium\"\n ariaLabel=\"Send\"\n :disabled=\"!canSubmit\"\n @click.stop=\"onSubmit\"\n />\n </div>\n </div>\n\n <div v-if=\"leftSubtext || rightSubtext || link\" class=\"subtext\">\n <div v-if=\"leftSubtext\" class=\"subtext-left\">{{ leftSubtext }}</div>\n <div v-if=\"rightSubtext || link\" class=\"subtext-right\">\n <span v-if=\"rightSubtext\">{{ rightSubtext }}</span>\n <KdsLink v-if=\"link\" :label=\"link.label\" :to=\"link.to\" size=\"small\" />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.prompt-input {\n display: flex;\n flex-direction: column;\n width: 100%;\n}\n\n.input-container {\n position: relative;\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n align-items: flex-end;\n padding: calc(\n var(--kds-spacing-container-0-37x) - var(--kds-core-border-width-xs)\n );\n overflow: hidden;\n cursor: text;\n background: var(--kds-color-background-input-initial);\n border: var(--kds-border-action-input);\n border-radius: var(--kds-border-radius-container-0-37x);\n\n &:has(textarea:focus:not(:disabled)) {\n outline: var(--kds-border-action-focused);\n outline-offset: var(--kds-spacing-offset-focus);\n }\n\n &:has(textarea:hover:not(:disabled, :focus)) {\n background: var(--kds-color-background-input-hover);\n }\n\n &.disabled {\n cursor: default;\n border: var(--kds-border-action-disabled);\n }\n}\n\n.text-input-wrapper {\n display: flex;\n flex: 1 0 0;\n align-items: center;\n min-width: 0;\n\n /*\n * Match the Send/Stop button height so a single-line textarea sits\n * vertically centered against the button.\n */\n min-height: var(--kds-dimension-component-height-1-75x);\n padding-left: var(--kds-spacing-container-0-37x);\n}\n\n.text-input {\n width: 100%;\n min-width: 0;\n\n /* Cap textarea growth at 4 lines, scroll beyond that. */\n field-sizing: content;\n max-height: calc(4 * 1lh);\n padding: 0;\n overflow-y: auto;\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n resize: none;\n outline: none;\n scrollbar-width: none;\n background: transparent;\n border: none;\n\n &::placeholder {\n color: var(--kds-color-text-and-icon-subtle);\n }\n\n &:disabled {\n color: var(--kds-color-text-and-icon-disabled);\n cursor: default;\n\n &::placeholder {\n color: var(--kds-color-text-and-icon-disabled);\n }\n }\n\n &::-webkit-scrollbar {\n display: none;\n }\n}\n\n.action {\n display: flex;\n flex-shrink: 0;\n align-items: flex-end;\n}\n\n.progress-bar {\n position: absolute;\n inset: -1px 0 auto;\n}\n\n.subtext {\n display: flex;\n flex-wrap: wrap;\n gap: var(--kds-spacing-container-0-25x) var(--kds-spacing-container-0-5x);\n align-items: center;\n padding: var(--kds-spacing-container-0-25x) var(--kds-spacing-container-0-25x)\n 0;\n font: var(--kds-font-base-subtext-small);\n color: var(--kds-color-text-and-icon-neutral);\n}\n\n.subtext-left {\n /*\n * Take all remaining inline space so the right block is pushed to the\n * end. `min-width: 0` lets the ellipsis kick in when the right block\n * grows past the container width.\n */\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.subtext-right {\n /*\n * On row 1 (alongside .subtext-left) the right block stays at its\n * intrinsic width — line-breaking in flex layout is decided from\n * `flex-basis`, so it wraps onto its own row as soon as the container\n * is narrower than that intrinsic width. On row 2 (its own row) the\n * default `flex-shrink: 1` plus `min-width: 0` lets it shrink to the\n * container width and wrap its own content (text + link).\n */\n display: flex;\n flex-wrap: wrap;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-width: 0;\n margin-left: auto;\n}\n</style>\n","export const kdsPromptInputStatusType = {\n ERROR: \"error\",\n WARNING: \"warning\",\n NOTE: \"note\",\n} as const;\n\nexport const kdsPromptInputStatusTypes = Object.values(\n kdsPromptInputStatusType,\n);\n\nexport const kdsPromptInputPermissionDecision = {\n ALLOW: \"allow\",\n DENY: \"deny\",\n} as const;\n\nexport const kdsPromptInputPermissionDecisions = Object.values(\n kdsPromptInputPermissionDecision,\n);\n"],"mappings":"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"}
|
package/dist/dataTransfer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
|
|
2
2
|
import { a as getFileExtension, c as promise_exports, l as sleep, r as formatFileSize, s as knimeFileFormats } from "./dist-DvDWkkNg.js";
|
|
3
|
-
import { t as rfcErrors } from "./rfcErrors-
|
|
3
|
+
import { t as rfcErrors } from "./rfcErrors-CUq6KIxS.js";
|
|
4
4
|
import { a as createUploadManager, c as API_BASE_URL, n as createUploadClient, o as createHttpClient, s as defaultConfig, t as createUploadStatusPoller } from "./core-DznNUqnG.js";
|
|
5
5
|
import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeModels, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useId, useModel, vShow, watch, withCtx, withDirectives } from "vue";
|
|
6
6
|
import { KdsBadge, KdsButton, KdsIcon, KdsLoadingSkeleton, KdsProgressBar, vKdsTooltip } from "@knime/kds-components";
|
|
@@ -795,6 +795,34 @@ var DEFAULT_UPLOAD_SIZE_LIMIT_BYTES = 5 * 1024 * 1024 * 1024;
|
|
|
795
795
|
var DEFAULT_RETRY_DELAY_MS = 50;
|
|
796
796
|
var getUploadSizeLimitBytes = () => DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;
|
|
797
797
|
/**
|
|
798
|
+
* Renames any File whose name repeats an earlier one in the list, appending
|
|
799
|
+
* a " (n)" suffix (before the extension, if any) - including a name produced
|
|
800
|
+
* by this same disambiguation, so a literal "name (1).ext" already in the
|
|
801
|
+
* list can't collide with a generated one
|
|
802
|
+
*/
|
|
803
|
+
var disambiguateFiles = (files) => {
|
|
804
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
805
|
+
return files.map((file) => {
|
|
806
|
+
if (!usedNames.has(file.name)) {
|
|
807
|
+
usedNames.add(file.name);
|
|
808
|
+
return file;
|
|
809
|
+
}
|
|
810
|
+
const extension = getFileExtension(file.name);
|
|
811
|
+
const base = extension ? file.name.slice(0, -(extension.length + 1)) : file.name;
|
|
812
|
+
let counter = 1;
|
|
813
|
+
let candidate = extension ? `${base} (${counter}).${extension}` : `${base} (${counter})`;
|
|
814
|
+
while (usedNames.has(candidate)) {
|
|
815
|
+
counter += 1;
|
|
816
|
+
candidate = extension ? `${base} (${counter}).${extension}` : `${base} (${counter})`;
|
|
817
|
+
}
|
|
818
|
+
usedNames.add(candidate);
|
|
819
|
+
return new File([file], candidate, {
|
|
820
|
+
type: file.type,
|
|
821
|
+
lastModified: file.lastModified
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
};
|
|
825
|
+
/**
|
|
798
826
|
* This composable handles uploads of data to a hub repository item (space or workflow group).
|
|
799
827
|
* It is a multi-step process which is described as follows:
|
|
800
828
|
*
|
|
@@ -938,7 +966,8 @@ var useFileUpload = (options = {}) => {
|
|
|
938
966
|
try {
|
|
939
967
|
if (enqueueableFiles.length === 0) return [];
|
|
940
968
|
prepareQueueSize.value += enqueueableFiles.length;
|
|
941
|
-
const
|
|
969
|
+
const disambiguatedFiles = disambiguateFiles(enqueueableFiles);
|
|
970
|
+
const items = disambiguatedFiles.map((file) => ({
|
|
942
971
|
path: knimeFileFormats.KNWF.getNameOrDefault(file, file.name),
|
|
943
972
|
file
|
|
944
973
|
}));
|
|
@@ -947,7 +976,8 @@ var useFileUpload = (options = {}) => {
|
|
|
947
976
|
excludeError: (error) => Boolean(error.statusCode && error.statusCode < 500),
|
|
948
977
|
retryDelayMS: DEFAULT_RETRY_DELAY_MS
|
|
949
978
|
});
|
|
950
|
-
const
|
|
979
|
+
const uploadIdByFile = new Map(uploadPayload.map((upload) => [upload.file, upload.uploadId]));
|
|
980
|
+
const uploadIds = disambiguatedFiles.map((file) => uploadIdByFile.get(file));
|
|
951
981
|
useUploadManagerResult.start(parentId, uploadPayload);
|
|
952
982
|
return uploadIds;
|
|
953
983
|
} catch (error) {
|
package/dist/dataTransfer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataTransfer.js","names":[],"sources":["../src/dataTransfer/shared/ui/CollapsiblePanel/useIconFlipper.ts","../src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue","../src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue","../src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue","../src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue","../src/dataTransfer/shared/ui/ProgressList/ProgressList.vue","../src/dataTransfer/download/ui/DownloadProgressPanelItem.vue","../src/dataTransfer/download/ui/DownloadProgressPanelItem.vue","../src/dataTransfer/download/ui/DownloadProgressPanel.vue","../src/dataTransfer/download/ui/DownloadProgressPanel.vue","../src/dataTransfer/download/useDownloadArtifact.ts","../src/dataTransfer/upload/UploadProgressPanelItem.vue","../src/dataTransfer/upload/UploadProgressPanelItem.vue","../src/dataTransfer/upload/UploadProgressPanel.vue","../src/dataTransfer/upload/UploadProgressPanel.vue","../src/dataTransfer/upload/useUploadManager.ts","../src/dataTransfer/upload/useFileUpload.ts"],"sourcesContent":["import { type CSSProperties, type ComputedRef, computed } from \"vue\";\n\ntype UseIconFlipperOptions = {\n active: ComputedRef<boolean>;\n initialRotation?: `${string}deg`;\n};\n\nexport const useIconFlipper = (options: UseIconFlipperOptions) => {\n const iconFlipStyles = computed<CSSProperties>(() => {\n const declaration: Partial<CSSProperties> = {\n transition: \"transform 0.2s linear\",\n transform: \"scaleY(1)\",\n };\n\n if (options.initialRotation) {\n declaration.rotate = `${options.initialRotation}`;\n }\n\n const transform = options.active.value ? \"scaleY(-1)\" : undefined;\n\n return { ...declaration, transform };\n });\n\n return { iconFlipStyles };\n};\n","<script setup lang=\"ts\">\nimport { computed, useId } from \"vue\";\n\nimport { KdsButton } from \"@knime/kds-components\";\n\nimport type { CollapsiblePanelProps } from \"./types\";\nimport { useIconFlipper } from \"./useIconFlipper\";\n\nconst { headline, canBeDismissed = false } =\n defineProps<CollapsiblePanelProps>();\n\nconst emit = defineEmits<{\n close: [];\n}>();\n\nconst modelValue = defineModel<boolean>({ default: true });\n\nconst { iconFlipStyles } = useIconFlipper({\n active: computed(() => modelValue.value),\n initialRotation: \"180deg\",\n});\nconst panelContentId = useId();\n</script>\n\n<template>\n <div class=\"collapsible-panel\" :class=\"{ collapsed: !modelValue }\">\n <div class=\"title-bar\">\n {{ headline }}\n <div class=\"actions\">\n <KdsButton\n size=\"small\"\n variant=\"transparent\"\n leading-icon=\"chevron-up\"\n :aria-expanded=\"modelValue\"\n :aria-controls=\"panelContentId\"\n :ariaLabel=\"modelValue ? 'Collapse' : 'Expand'\"\n :style=\"iconFlipStyles\"\n @click=\"modelValue = !modelValue\"\n />\n\n <KdsButton\n v-if=\"canBeDismissed\"\n size=\"small\"\n variant=\"transparent\"\n ariaLabel=\"Close\"\n leading-icon=\"x-close\"\n @click=\"emit('close')\"\n />\n </div>\n </div>\n\n <Transition name=\"expand\">\n <div v-show=\"modelValue\" :id=\"panelContentId\" class=\"panel-content\">\n <slot />\n </div>\n </Transition>\n </div>\n</template>\n\n<style scoped>\n.collapsible-panel {\n background: var(--kds-color-surface-default);\n border-radius: var(--kds-spacing-container-0-25x);\n box-shadow: var(--kds-elevation-level-3);\n\n & .title-bar {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-2-5x);\n padding: var(--kds-spacing-container-0-5x)\n var(--kds-spacing-container-0-75x);\n font: var(--kds-font-base-title-small-strong);\n color: var(--kds-color-text-and-icon-neutral);\n\n & .actions {\n display: flex;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n margin-left: auto;\n }\n }\n\n &:not(.collapsed) {\n & .title-bar {\n border-bottom: var(--kds-border-base-subtle);\n }\n }\n}\n\n.panel-content {\n overflow: hidden;\n}\n\n.expand-enter-active,\n.expand-leave-active {\n display: grid;\n overflow: hidden;\n transition:\n grid-template-rows 0.2s ease-in-out,\n opacity 0.2s ease-in-out;\n}\n\n.expand-enter-from,\n.expand-leave-to {\n grid-template-rows: 0fr;\n opacity: 0;\n}\n\n.expand-enter-to,\n.expand-leave-from {\n grid-template-rows: 1fr;\n opacity: 1;\n}\n\n.panel-content > :deep(*) {\n min-height: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useId } from \"vue\";\n\nimport { KdsButton } from \"@knime/kds-components\";\n\nimport type { CollapsiblePanelProps } from \"./types\";\nimport { useIconFlipper } from \"./useIconFlipper\";\n\nconst { headline, canBeDismissed = false } =\n defineProps<CollapsiblePanelProps>();\n\nconst emit = defineEmits<{\n close: [];\n}>();\n\nconst modelValue = defineModel<boolean>({ default: true });\n\nconst { iconFlipStyles } = useIconFlipper({\n active: computed(() => modelValue.value),\n initialRotation: \"180deg\",\n});\nconst panelContentId = useId();\n</script>\n\n<template>\n <div class=\"collapsible-panel\" :class=\"{ collapsed: !modelValue }\">\n <div class=\"title-bar\">\n {{ headline }}\n <div class=\"actions\">\n <KdsButton\n size=\"small\"\n variant=\"transparent\"\n leading-icon=\"chevron-up\"\n :aria-expanded=\"modelValue\"\n :aria-controls=\"panelContentId\"\n :ariaLabel=\"modelValue ? 'Collapse' : 'Expand'\"\n :style=\"iconFlipStyles\"\n @click=\"modelValue = !modelValue\"\n />\n\n <KdsButton\n v-if=\"canBeDismissed\"\n size=\"small\"\n variant=\"transparent\"\n ariaLabel=\"Close\"\n leading-icon=\"x-close\"\n @click=\"emit('close')\"\n />\n </div>\n </div>\n\n <Transition name=\"expand\">\n <div v-show=\"modelValue\" :id=\"panelContentId\" class=\"panel-content\">\n <slot />\n </div>\n </Transition>\n </div>\n</template>\n\n<style scoped>\n.collapsible-panel {\n background: var(--kds-color-surface-default);\n border-radius: var(--kds-spacing-container-0-25x);\n box-shadow: var(--kds-elevation-level-3);\n\n & .title-bar {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-2-5x);\n padding: var(--kds-spacing-container-0-5x)\n var(--kds-spacing-container-0-75x);\n font: var(--kds-font-base-title-small-strong);\n color: var(--kds-color-text-and-icon-neutral);\n\n & .actions {\n display: flex;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n margin-left: auto;\n }\n }\n\n &:not(.collapsed) {\n & .title-bar {\n border-bottom: var(--kds-border-base-subtle);\n }\n }\n}\n\n.panel-content {\n overflow: hidden;\n}\n\n.expand-enter-active,\n.expand-leave-active {\n display: grid;\n overflow: hidden;\n transition:\n grid-template-rows 0.2s ease-in-out,\n opacity 0.2s ease-in-out;\n}\n\n.expand-enter-from,\n.expand-leave-to {\n grid-template-rows: 0fr;\n opacity: 0;\n}\n\n.expand-enter-to,\n.expand-leave-from {\n grid-template-rows: 1fr;\n opacity: 1;\n}\n\n.panel-content > :deep(*) {\n min-height: 0;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport {\n KdsBadge,\n KdsButton,\n KdsIcon,\n KdsProgressBar,\n vKdsTooltip,\n} from \"@knime/kds-components\";\n\nimport { type ProgressItemProps } from \"./types\";\n\ndefineProps<ProgressItemProps>();\n</script>\n\n<template>\n <div class=\"progress-wrapper\">\n <div :class=\"['progress-item', { padded: progress === undefined }]\">\n <div class=\"item-info\">\n <KdsIcon :name=\"icon\" />\n\n <div class=\"item-name\">\n <div v-kds-tooltip:truncated=\"headline\" class=\"title\">\n {{ headline }}\n </div>\n <span v-if=\"subtitle\" class=\"subtitle\">\n {{ subtitle }}\n </span>\n </div>\n </div>\n\n <div class=\"item-action\">\n <KdsBadge\n v-if=\"badge\"\n :variant=\"badge.variant\"\n :label=\"badge.text\"\n :leading-icon=\"badge.icon\"\n size=\"xxsmall\"\n />\n\n <template v-for=\"action in actions.slice(0, 2)\" :key=\"action.id\">\n <KdsButton\n :leading-icon=\"action.icon\"\n :ariaLabel=\"action.label\"\n variant=\"transparent\"\n @click=\"action.handler\"\n />\n </template>\n </div>\n </div>\n\n <div v-if=\"typeof progress === 'number'\" class=\"progress\">\n <KdsProgressBar :percentage=\"progress\" size=\"medium\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.progress-wrapper {\n --base-padding: var(--kds-spacing-container-1x);\n --progress-bar-height: var(--kds-dimension-component-height-0-25x);\n --progress-bar-distance: var(--kds-spacing-container-0-25x);\n\n display: flex;\n flex-direction: column;\n gap: var(--progress-bar-distance);\n height: var(--kds-dimension-component-height-4x);\n padding: var(--base-padding);\n}\n\n.progress-item {\n display: flex;\n align-items: center;\n width: 100%;\n height: 100%;\n\n /* add extra margin to prevent content jump when a progress bars appears/disappears */\n &.padded {\n margin-bottom: calc(\n var(--progress-bar-height) + var(--progress-bar-distance)\n );\n }\n}\n\n.progress {\n min-height: var(--progress-bar-height);\n}\n\n.item-info {\n display: flex;\n flex-grow: 1;\n gap: var(--kds-spacing-container-0-25x);\n overflow: hidden;\n\n & .item-name {\n display: flex;\n flex-grow: 1;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n justify-content: center;\n overflow: hidden;\n color: var(--kds-color-text-and-icon-neutral);\n\n & .title {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n font: var(--kds-font-base-interactive-small);\n white-space: nowrap;\n }\n\n & .subtitle {\n font: var(--kds-font-base-subtext-small);\n }\n }\n}\n\n.item-action {\n display: flex;\n flex-shrink: 0;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport {\n KdsBadge,\n KdsButton,\n KdsIcon,\n KdsProgressBar,\n vKdsTooltip,\n} from \"@knime/kds-components\";\n\nimport { type ProgressItemProps } from \"./types\";\n\ndefineProps<ProgressItemProps>();\n</script>\n\n<template>\n <div class=\"progress-wrapper\">\n <div :class=\"['progress-item', { padded: progress === undefined }]\">\n <div class=\"item-info\">\n <KdsIcon :name=\"icon\" />\n\n <div class=\"item-name\">\n <div v-kds-tooltip:truncated=\"headline\" class=\"title\">\n {{ headline }}\n </div>\n <span v-if=\"subtitle\" class=\"subtitle\">\n {{ subtitle }}\n </span>\n </div>\n </div>\n\n <div class=\"item-action\">\n <KdsBadge\n v-if=\"badge\"\n :variant=\"badge.variant\"\n :label=\"badge.text\"\n :leading-icon=\"badge.icon\"\n size=\"xxsmall\"\n />\n\n <template v-for=\"action in actions.slice(0, 2)\" :key=\"action.id\">\n <KdsButton\n :leading-icon=\"action.icon\"\n :ariaLabel=\"action.label\"\n variant=\"transparent\"\n @click=\"action.handler\"\n />\n </template>\n </div>\n </div>\n\n <div v-if=\"typeof progress === 'number'\" class=\"progress\">\n <KdsProgressBar :percentage=\"progress\" size=\"medium\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.progress-wrapper {\n --base-padding: var(--kds-spacing-container-1x);\n --progress-bar-height: var(--kds-dimension-component-height-0-25x);\n --progress-bar-distance: var(--kds-spacing-container-0-25x);\n\n display: flex;\n flex-direction: column;\n gap: var(--progress-bar-distance);\n height: var(--kds-dimension-component-height-4x);\n padding: var(--base-padding);\n}\n\n.progress-item {\n display: flex;\n align-items: center;\n width: 100%;\n height: 100%;\n\n /* add extra margin to prevent content jump when a progress bars appears/disappears */\n &.padded {\n margin-bottom: calc(\n var(--progress-bar-height) + var(--progress-bar-distance)\n );\n }\n}\n\n.progress {\n min-height: var(--progress-bar-height);\n}\n\n.item-info {\n display: flex;\n flex-grow: 1;\n gap: var(--kds-spacing-container-0-25x);\n overflow: hidden;\n\n & .item-name {\n display: flex;\n flex-grow: 1;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n justify-content: center;\n overflow: hidden;\n color: var(--kds-color-text-and-icon-neutral);\n\n & .title {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n font: var(--kds-font-base-interactive-small);\n white-space: nowrap;\n }\n\n & .subtitle {\n font: var(--kds-font-base-subtext-small);\n }\n }\n}\n\n.item-action {\n display: flex;\n flex-shrink: 0;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n}\n</style>\n","<template>\n <div class=\"progress-list\" tabindex=\"0\">\n <slot />\n </div>\n</template>\n\n<style scoped>\n.progress-list {\n max-height: var(--kds-dimension-component-height-20x);\n overflow-y: auto;\n\n &:deep(.progress-wrapper):not(:first-child) {\n border-top: var(--kds-border-base-subtle);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../../shared/ui\";\n\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n item: DownloadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n download: [];\n}>();\n\nconst statusMapper = {\n IN_PROGRESS: [\"Zipping\", \"info\", \"reload\"],\n READY: [\"Ready\", \"success\", \"circle-info\"],\n CANCELLED: [\"Cancelled\", \"error\", \"circle-error\"],\n FAILED: [\"Failed\", \"error\", \"circle-error\"],\n} satisfies Record<\n DownloadItem[\"status\"],\n [string, KdsBadgeVariant, KdsIconName]\n>;\n\nconst badge = computed<ProgressItemBadge>(() => {\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return {\n text,\n variant,\n icon,\n } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => props.item.status === \"IN_PROGRESS\");\nconst isReady = computed(() => props.item.status === \"READY\");\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const actions: ProgressItemAction[] = [];\n\n if (isCancellable.value) {\n actions.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (isReady.value) {\n actions.push({\n id: \"download\",\n icon: \"cloud-download\",\n label: \"Download again\",\n handler: () => emit(\"download\"),\n });\n }\n\n return actions;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.downloadId\"\n icon=\"file\"\n :headline=\"item.name\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../../shared/ui\";\n\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n item: DownloadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n download: [];\n}>();\n\nconst statusMapper = {\n IN_PROGRESS: [\"Zipping\", \"info\", \"reload\"],\n READY: [\"Ready\", \"success\", \"circle-info\"],\n CANCELLED: [\"Cancelled\", \"error\", \"circle-error\"],\n FAILED: [\"Failed\", \"error\", \"circle-error\"],\n} satisfies Record<\n DownloadItem[\"status\"],\n [string, KdsBadgeVariant, KdsIconName]\n>;\n\nconst badge = computed<ProgressItemBadge>(() => {\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return {\n text,\n variant,\n icon,\n } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => props.item.status === \"IN_PROGRESS\");\nconst isReady = computed(() => props.item.status === \"READY\");\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const actions: ProgressItemAction[] = [];\n\n if (isCancellable.value) {\n actions.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (isReady.value) {\n actions.push({\n id: \"download\",\n icon: \"cloud-download\",\n label: \"Download again\",\n handler: () => emit(\"download\"),\n });\n }\n\n return actions;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.downloadId\"\n icon=\"file\"\n :headline=\"item.name\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, watch } from \"vue\";\n\nimport { CollapsiblePanel, ProgressList } from \"../../shared/ui\";\n\nimport DownloadProgressPanelItem from \"./DownloadProgressPanelItem.vue\";\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n /**\n * List of current download items that should be displayed\n */\n items: DownloadItem[];\n};\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [item: DownloadItem];\n download: [item: DownloadItem];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => props.items.length);\nconst totalDownloadedItems = computed(\n () => props.items.filter(({ status }) => status === \"READY\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `file${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Downloaded ${totalDownloadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasItemsInProgress = computed(() =>\n props.items.some(({ status }) => status === \"IN_PROGRESS\"),\n);\n\nwatch(\n () => props.items?.length,\n (newValue, oldValue) => {\n if (oldValue > 0 && !newValue) {\n emit(\"close\");\n }\n },\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"download-panel\"\n :can-be-dismissed=\"!hasItemsInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"download-panel-content\">\n <ProgressList>\n <DownloadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.downloadId\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @download=\"emit('download', item)\"\n />\n </ProgressList>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.download-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.download-panel-content {\n max-height: var(--kds-dimension-component-height-20x);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, watch } from \"vue\";\n\nimport { CollapsiblePanel, ProgressList } from \"../../shared/ui\";\n\nimport DownloadProgressPanelItem from \"./DownloadProgressPanelItem.vue\";\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n /**\n * List of current download items that should be displayed\n */\n items: DownloadItem[];\n};\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [item: DownloadItem];\n download: [item: DownloadItem];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => props.items.length);\nconst totalDownloadedItems = computed(\n () => props.items.filter(({ status }) => status === \"READY\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `file${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Downloaded ${totalDownloadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasItemsInProgress = computed(() =>\n props.items.some(({ status }) => status === \"IN_PROGRESS\"),\n);\n\nwatch(\n () => props.items?.length,\n (newValue, oldValue) => {\n if (oldValue > 0 && !newValue) {\n emit(\"close\");\n }\n },\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"download-panel\"\n :can-be-dismissed=\"!hasItemsInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"download-panel-content\">\n <ProgressList>\n <DownloadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.downloadId\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @download=\"emit('download', item)\"\n />\n </ProgressList>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.download-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.download-panel-content {\n max-height: var(--kds-dimension-component-height-20x);\n}\n</style>\n","import { computed, ref } from \"vue\";\nimport { FetchError, type FetchOptions } from \"ofetch\";\n\nimport { sleep } from \"@knime/utils\";\n\nimport { rfcErrors } from \"../../rfcErrors\";\nimport { createHttpClient } from \"../shared/httpClient\";\n\nimport type { DownloadItem as DownloadItemExternal } from \"./ui/types\";\n\nconst DEFAULT_MAX_RETRIES = null;\nconst DEFAULT_POLLING_INTERVAL = 2000;\n\ntype ArtifactStatusResponse = {\n downloadId?: string;\n status?: string;\n statusMessage?: string;\n lastUpdated?: string;\n downloadUrl?: string;\n};\n\n// Initial request can have either download url (if it can be downloaded immediately) or download id (to poll for the status)\ntype RequestDownloadResponse =\n | {\n downloadId: string;\n }\n | {\n downloadUrl: string;\n };\n\n// type guard\nconst hasDownloadUrl = (\n response: RequestDownloadResponse,\n): response is { downloadUrl: string } => {\n return \"downloadUrl\" in response;\n};\n\n/**\n * The version of the artifact to download.\n */\ntype ItemVersion = number | \"current-state\" | \"most-recent\";\n\n/**\n * Tracks the state of each concurrent download.\n */\ntype DownloadItem = DownloadItemExternal & {\n itemId: string;\n version?: ItemVersion;\n};\n\nexport type UseDownloadArtifactOptions = {\n /**\n * The maximum number of times to poll for the download URL. Default is `null` (no limit).\n */\n maxRetries?: number;\n /**\n * The interval in milliseconds between each poll. Default is `2000` ms.\n */\n pollingInterval?: number;\n /**\n * Options to customize the fetch client used internally to make http requests.\n * e.g: headers, baseUrl, etc\n */\n customFetchClientOptions?: FetchOptions;\n};\n\nconst isAbortError = (error: unknown) =>\n error instanceof DOMException && error.name === \"AbortError\";\n\n/**\n * This composable supports multiple concurrent downloads. Each time you call ```start```, the following behavior applies:\n *\n * 1. **Immediate-ready artifact**: If the first status check for the artifact returns `READY`, then there's no polling and no need\n * to add it to `downloadItems`. The download URL is opened in the same tab immediately.\n *\n * 2. **Not-ready artifact**: If the artifact is not yet ready (e.g. needs zipping), then we add a record to `downloadItems` with status `IN_PROGRESS`\n * and repeatedly poll until it becomes `READY`, the polling times out, or the user aborts.\n *\n * **Usage**:\n * ```\n * const { start, cancel, downloadItems } = useDownloadArtifact({ ...options });\n *\n * // start a download\n * await start({ itemId: \"1234\", version: 1 });\n *\n * // if needed, cancel it:\n * cancel(\"1234\");\n *\n * // downloadItems (as a computed array) will reflect the current statuses.\n * ```\n */\nexport const useDownloadArtifact = (\n options: UseDownloadArtifactOptions = {},\n) => {\n const $ofetch = createHttpClient(options.customFetchClientOptions);\n\n const downloadItems = ref<Record<string, DownloadItem>>({});\n const abortControllers: Record<string, AbortController> = {};\n\n // Use fallback values if none are provided\n const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n const pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;\n\n const cancel = (downloadId: string) => {\n const itemController = abortControllers[downloadId];\n if (itemController) {\n itemController.abort();\n delete abortControllers[downloadId];\n }\n };\n\n const removeItem = (downloadId: string) => {\n cancel(downloadId);\n delete downloadItems.value[downloadId];\n };\n\n const fetchDownloadStatus = ({\n downloadId,\n signal,\n }: {\n downloadId: string;\n signal?: AbortSignal;\n }) => {\n return $ofetch<ArtifactStatusResponse>(`/downloads/${downloadId}/status`, {\n method: \"GET\",\n signal,\n });\n };\n\n const openDownload = (downloadUrl: string) => {\n const a = document.createElement(\"a\");\n a.target = \"_blank\";\n a.href = downloadUrl;\n a.download = \"\";\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n };\n\n /**\n * Makes a request to the download status endpoint for the given download item.\n * Returns a boolean that is true if a terminal state has been reached (download is ready,\n * download failed, or user aborted download)\n * @param downloadId the `downloadId`\n * @param signal the abort signal passed to the request to be able to abort it\n * @returns a boolean indicating whether a terminal state has been reached\n */\n const handlePollingResult = async ({\n downloadId,\n signal,\n }: {\n downloadId: string;\n signal: AbortSignal;\n }) => {\n try {\n const statusResponse = await fetchDownloadStatus({\n downloadId,\n signal,\n });\n\n if (statusResponse?.status === \"READY\" && statusResponse?.downloadUrl) {\n downloadItems.value[downloadId].status = \"READY\";\n downloadItems.value[downloadId].downloadUrl =\n statusResponse.downloadUrl;\n // open download in a new tab\n openDownload(statusResponse.downloadUrl);\n return true;\n } else if (statusResponse?.status === \"FAILED\") {\n let details = \"Download failed\";\n if (statusResponse?.statusMessage) {\n details += `: ${statusResponse.statusMessage}`;\n }\n throw new Error(details);\n }\n } catch (error: unknown) {\n consola.error(\"Error fetching status:\", error);\n // https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort\n const isAborted =\n isAbortError(error) ||\n (error instanceof FetchError && isAbortError(error.cause));\n\n if (!isAborted) {\n downloadItems.value[downloadId].status = \"FAILED\";\n downloadItems.value[downloadId].failureDetails =\n error instanceof Error ? error.message : (error as string);\n }\n return true;\n }\n return false;\n };\n\n const pollDownloadItem = async ({\n itemId,\n downloadId,\n version,\n name,\n }: {\n itemId: string;\n downloadId: string;\n version?: ItemVersion;\n name: string;\n }) => {\n const controller = new AbortController();\n abortControllers[downloadId] = controller;\n\n const signal = controller.signal;\n\n let itemName = `${name}`;\n if (version) {\n itemName += ` (${version})`;\n }\n\n downloadItems.value[downloadId] = {\n itemId,\n ...(version && { version }),\n downloadId,\n name: itemName,\n status: \"IN_PROGRESS\",\n };\n\n const abortListener = () => {\n controller.signal.removeEventListener(\"abort\", abortListener);\n if (!downloadItems.value[downloadId]) {\n return;\n }\n downloadItems.value[downloadId].status = \"CANCELLED\";\n downloadItems.value[downloadId].failureDetails =\n \"Download was cancelled manually\";\n };\n controller.signal.addEventListener(\"abort\", abortListener);\n\n let tries = 0;\n // eslint-disable-next-line no-unmodified-loop-condition\n while (maxRetries === null || tries < maxRetries) {\n tries++;\n await sleep(pollingInterval);\n\n const isTerminalState = await handlePollingResult({ downloadId, signal });\n\n if (isTerminalState) {\n break;\n }\n }\n\n delete abortControllers[downloadId];\n\n if (maxRetries !== null && tries >= maxRetries) {\n downloadItems.value[downloadId].status = \"FAILED\";\n downloadItems.value[downloadId].failureDetails = \"Download timed out\";\n }\n };\n\n const start = async ({\n itemId,\n version,\n name,\n }: {\n itemId: string;\n name: string;\n version?: ItemVersion;\n }) => {\n try {\n const response = await $ofetch<RequestDownloadResponse>(\n `/repository/${itemId}/artifact`,\n {\n method: \"GET\",\n query: { version },\n },\n );\n\n // if download is ready, open url immediately\n if (hasDownloadUrl(response)) {\n openDownload(response.downloadUrl);\n return;\n }\n\n // otherwise start polling download status\n await pollDownloadItem({\n itemId,\n version,\n downloadId: response.downloadId,\n name,\n });\n } catch (error: unknown) {\n // here only errors thrown by the initial requests are caught;\n // errors occurring while polling will be handled in pollDownloadItem\n throw rfcErrors.tryParse(error) ?? error;\n }\n };\n\n const totalItemsBeingZipped = computed(\n () =>\n Object.values(downloadItems.value).filter(\n ({ status }) => status === \"IN_PROGRESS\",\n ).length,\n );\n\n const resetState = () => {\n for (const [downloadId, controller] of Object.entries(abortControllers)) {\n controller?.abort();\n delete abortControllers[downloadId];\n }\n downloadItems.value = {};\n };\n\n const downloadItemsArray = computed(() => Object.values(downloadItems.value));\n\n return {\n /**\n * Initiates a download of the given itemId and version.\n */\n start,\n\n /**\n * Cancels the ongoing download for a given itemId (if any).\n * Sets the status in `downloadItems` to `CANCELLED`.\n */\n cancel,\n\n /**\n * Removes item from `downloadItems`.\n */\n removeItem,\n\n /**\n * A computed array of all current items that are being zipped and prepared for download.\n */\n downloadItems: downloadItemsArray,\n\n /**\n * The total number of zipping items in progress.\n */\n totalItemsBeingZipped,\n\n /**\n * Resets the state of the composable, clearing all downloads and controllers.\n */\n resetState,\n\n /**\n * Opens a download from the given url\n */\n openDownload,\n };\n};\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\nimport {\n formatFileSize,\n getFileExtension,\n knimeFileFormats,\n} from \"@knime/utils\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../shared/ui\";\n\nimport type { UploadItem, UploadItemStatus } from \"./types\";\n\ntype Props = {\n item: UploadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n viewDetails: [];\n}>();\n\nconst subtitle = computed(() => {\n if (props.item.type === \"file\") {\n const progressed = formatFileSize(\n (props.item.size / 100) * (props.item.progress ?? 0),\n );\n\n const total = formatFileSize(props.item.size);\n return `${progressed} of ${total}`;\n }\n\n if (props.item.type === \"directory\") {\n const total = props.item.totalEntries;\n const progressed = props.item.uploadedEntries;\n\n return `${progressed} of ${total} file${total > 1 ? \"s\" : \"\"}`;\n }\n\n return \"\";\n});\n\nconst progressPercentageValue = computed(() => {\n if (props.item.type === \"file\") {\n return props.item.progress;\n }\n\n if (props.item.type === \"directory\") {\n // The KdsProgressBar only accepts percentages, but for directory items\n // the consumer supplies different values so we must derive the percentage\n const progressPercentage =\n (props.item.uploadedEntries * 100) / props.item.totalEntries;\n\n return progressPercentage;\n }\n\n return undefined;\n});\n\nconst statusMapper = {\n inprogress: [\"Uploading\", \"info\", \"reload\"],\n failed: [\"Failed\", \"error\", \"circle-error\"],\n complete: [\"Uploaded\", \"success\", \"circle-success\"],\n cancelled: [\"Cancelled\", \"error\", \"circle-error\"],\n processing: [\"Processing\", \"info\", \"reload\"],\n \"partial-success\": [\"Partial success\", \"warning\", \"warning\"],\n} satisfies Record<UploadItemStatus, [string, KdsBadgeVariant, KdsIconName]>;\n\nconst itemIcon = computed<KdsIconName>(() => {\n if (props.item.type === \"directory\") {\n return \"folder\";\n }\n\n const extension = getFileExtension(props.item.name);\n return extension === knimeFileFormats.KNWF.extension ? \"workflow\" : \"file\";\n});\n\nconst badge = computed<ProgressItemBadge | undefined>(() => {\n if (!props.item.status) {\n return undefined;\n }\n\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return { text, variant, icon } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.cancellable === undefined\n ) {\n return true;\n }\n\n return props.item.actions.cancellable;\n});\n\nconst shouldShowCancelAction = computed(\n () => props.item.status === \"inprogress\" && isCancellable.value,\n);\n\nconst isInspectable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.inspectable === undefined\n ) {\n return false;\n }\n\n return props.item.actions.inspectable;\n});\n\nconst shouldShowViewDetailsAction = computed(\n () => props.item.status === \"partial-success\" && isInspectable.value,\n);\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const result: ProgressItemAction[] = [];\n\n if (shouldShowCancelAction.value) {\n result.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (shouldShowViewDetailsAction.value) {\n result.push({\n id: \"view-details\",\n icon: \"circle-info\",\n label: \"View details\",\n handler: () => emit(\"viewDetails\"),\n });\n }\n\n return result;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.id\"\n :headline=\"item.name\"\n :subtitle=\"subtitle\"\n :icon=\"itemIcon\"\n :progress=\"progressPercentageValue\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\nimport {\n formatFileSize,\n getFileExtension,\n knimeFileFormats,\n} from \"@knime/utils\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../shared/ui\";\n\nimport type { UploadItem, UploadItemStatus } from \"./types\";\n\ntype Props = {\n item: UploadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n viewDetails: [];\n}>();\n\nconst subtitle = computed(() => {\n if (props.item.type === \"file\") {\n const progressed = formatFileSize(\n (props.item.size / 100) * (props.item.progress ?? 0),\n );\n\n const total = formatFileSize(props.item.size);\n return `${progressed} of ${total}`;\n }\n\n if (props.item.type === \"directory\") {\n const total = props.item.totalEntries;\n const progressed = props.item.uploadedEntries;\n\n return `${progressed} of ${total} file${total > 1 ? \"s\" : \"\"}`;\n }\n\n return \"\";\n});\n\nconst progressPercentageValue = computed(() => {\n if (props.item.type === \"file\") {\n return props.item.progress;\n }\n\n if (props.item.type === \"directory\") {\n // The KdsProgressBar only accepts percentages, but for directory items\n // the consumer supplies different values so we must derive the percentage\n const progressPercentage =\n (props.item.uploadedEntries * 100) / props.item.totalEntries;\n\n return progressPercentage;\n }\n\n return undefined;\n});\n\nconst statusMapper = {\n inprogress: [\"Uploading\", \"info\", \"reload\"],\n failed: [\"Failed\", \"error\", \"circle-error\"],\n complete: [\"Uploaded\", \"success\", \"circle-success\"],\n cancelled: [\"Cancelled\", \"error\", \"circle-error\"],\n processing: [\"Processing\", \"info\", \"reload\"],\n \"partial-success\": [\"Partial success\", \"warning\", \"warning\"],\n} satisfies Record<UploadItemStatus, [string, KdsBadgeVariant, KdsIconName]>;\n\nconst itemIcon = computed<KdsIconName>(() => {\n if (props.item.type === \"directory\") {\n return \"folder\";\n }\n\n const extension = getFileExtension(props.item.name);\n return extension === knimeFileFormats.KNWF.extension ? \"workflow\" : \"file\";\n});\n\nconst badge = computed<ProgressItemBadge | undefined>(() => {\n if (!props.item.status) {\n return undefined;\n }\n\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return { text, variant, icon } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.cancellable === undefined\n ) {\n return true;\n }\n\n return props.item.actions.cancellable;\n});\n\nconst shouldShowCancelAction = computed(\n () => props.item.status === \"inprogress\" && isCancellable.value,\n);\n\nconst isInspectable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.inspectable === undefined\n ) {\n return false;\n }\n\n return props.item.actions.inspectable;\n});\n\nconst shouldShowViewDetailsAction = computed(\n () => props.item.status === \"partial-success\" && isInspectable.value,\n);\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const result: ProgressItemAction[] = [];\n\n if (shouldShowCancelAction.value) {\n result.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (shouldShowViewDetailsAction.value) {\n result.push({\n id: \"view-details\",\n icon: \"circle-info\",\n label: \"View details\",\n handler: () => emit(\"viewDetails\"),\n });\n }\n\n return result;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.id\"\n :headline=\"item.name\"\n :subtitle=\"subtitle\"\n :icon=\"itemIcon\"\n :progress=\"progressPercentageValue\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport { KdsButton, KdsLoadingSkeleton } from \"@knime/kds-components\";\n\nimport { CollapsiblePanel, ProgressList } from \"../shared/ui\";\n\nimport UploadProgressPanelItem from \"./UploadProgressPanelItem.vue\";\nimport type { UploadItem, UploadProgressPanelProps } from \"./types\";\n\nconst {\n items,\n placeholderItems = 0,\n canRetry = false,\n} = defineProps<UploadProgressPanelProps>();\n\nconst emit = defineEmits<{\n cancel: [item: UploadItem];\n viewDetails: [item: UploadItem];\n retry: [];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => items.length);\nconst uploadedItems = computed(\n () => items.filter(({ status }) => status === \"complete\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `item${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasSomeItemInProgress = computed(\n () =>\n items.some(\n ({ status }) => status === \"inprogress\" || status === \"processing\",\n ) || placeholderItems > 0,\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"upload-panel\"\n :can-be-dismissed=\"!hasSomeItemInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"upload-panel-content\">\n <ProgressList>\n <UploadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.id\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @view-details=\"emit('viewDetails', item)\"\n />\n </ProgressList>\n\n <template v-if=\"placeholderItems > 0\">\n <div\n v-for=\"skeleton in placeholderItems\"\n :key=\"skeleton\"\n class=\"skeleton-container\"\n >\n <KdsLoadingSkeleton variant=\"icon-large\" />\n <div class=\"skeleton-text-content\">\n <KdsLoadingSkeleton variant=\"label\" />\n <KdsLoadingSkeleton variant=\"text\" />\n </div>\n </div>\n </template>\n\n <div v-if=\"canRetry && !hasSomeItemInProgress\" class=\"retry-bar\">\n <KdsButton\n size=\"small\"\n variant=\"outlined\"\n label=\"Retry failed items\"\n leading-icon=\"reload\"\n @click=\"emit('retry')\"\n />\n </div>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.upload-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.upload-panel-content {\n display: flex;\n flex-direction: column;\n}\n\n.retry-bar {\n display: flex;\n justify-content: flex-end;\n padding: var(--kds-spacing-container-0-5x) var(--kds-spacing-container-0-75x);\n border-top: var(--kds-border-base-subtle);\n}\n\n.skeleton-container {\n display: grid;\n grid-template-columns: min-content 1fr;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-height: var(--kds-dimension-component-height-4x);\n padding: 0 var(--kds-spacing-container-1x);\n border-top: var(--kds-border-base-subtle);\n\n & .skeleton-text-content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport { KdsButton, KdsLoadingSkeleton } from \"@knime/kds-components\";\n\nimport { CollapsiblePanel, ProgressList } from \"../shared/ui\";\n\nimport UploadProgressPanelItem from \"./UploadProgressPanelItem.vue\";\nimport type { UploadItem, UploadProgressPanelProps } from \"./types\";\n\nconst {\n items,\n placeholderItems = 0,\n canRetry = false,\n} = defineProps<UploadProgressPanelProps>();\n\nconst emit = defineEmits<{\n cancel: [item: UploadItem];\n viewDetails: [item: UploadItem];\n retry: [];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => items.length);\nconst uploadedItems = computed(\n () => items.filter(({ status }) => status === \"complete\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `item${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasSomeItemInProgress = computed(\n () =>\n items.some(\n ({ status }) => status === \"inprogress\" || status === \"processing\",\n ) || placeholderItems > 0,\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"upload-panel\"\n :can-be-dismissed=\"!hasSomeItemInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"upload-panel-content\">\n <ProgressList>\n <UploadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.id\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @view-details=\"emit('viewDetails', item)\"\n />\n </ProgressList>\n\n <template v-if=\"placeholderItems > 0\">\n <div\n v-for=\"skeleton in placeholderItems\"\n :key=\"skeleton\"\n class=\"skeleton-container\"\n >\n <KdsLoadingSkeleton variant=\"icon-large\" />\n <div class=\"skeleton-text-content\">\n <KdsLoadingSkeleton variant=\"label\" />\n <KdsLoadingSkeleton variant=\"text\" />\n </div>\n </div>\n </template>\n\n <div v-if=\"canRetry && !hasSomeItemInProgress\" class=\"retry-bar\">\n <KdsButton\n size=\"small\"\n variant=\"outlined\"\n label=\"Retry failed items\"\n leading-icon=\"reload\"\n @click=\"emit('retry')\"\n />\n </div>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.upload-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.upload-panel-content {\n display: flex;\n flex-direction: column;\n}\n\n.retry-bar {\n display: flex;\n justify-content: flex-end;\n padding: var(--kds-spacing-container-0-5x) var(--kds-spacing-container-0-75x);\n border-top: var(--kds-border-base-subtle);\n}\n\n.skeleton-container {\n display: grid;\n grid-template-columns: min-content 1fr;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-height: var(--kds-dimension-component-height-4x);\n padding: 0 var(--kds-spacing-container-1x);\n border-top: var(--kds-border-base-subtle);\n\n & .skeleton-text-content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n }\n}\n</style>\n","import { computed, ref } from \"vue\";\n\nimport { type ChunkUploadManagerConfig, createUploadManager } from \"../core\";\n\nimport type { FileUploadItem, UploadItem } from \"./types\";\n\ntype OnCompletePayload = Parameters<\n NonNullable<ChunkUploadManagerConfig[\"onFileUploadComplete\"]>\n>[0] & { parentId: string; name: string };\n\ntype OnFailedPayload = Parameters<\n NonNullable<ChunkUploadManagerConfig[\"onFileUploadFailed\"]>\n>[0] & { parentId: string };\n\ntype UseUploadManagerOptions = Omit<\n ChunkUploadManagerConfig,\n \"onProgress\" | \"onFileUploadComplete\" | \"onFileUploadFailed\"\n> & {\n onFileUploadComplete?: (payload: OnCompletePayload) => void;\n onFileUploadFailed?: (payload: OnFailedPayload) => void;\n /**\n * Determines the status to assign a file once its raw upload finishes,\n * before calling `onFileUploadComplete`. This helps for files that need further\n * server-side processing\n *\n * Defaults to `complete`\n */\n resolveCompleteStatus?: (\n payload: OnCompletePayload,\n ) => \"complete\" | \"processing\";\n};\n\n/**\n * This composable wraps the `uploadManager` utility\n * in order to provide reactive state which will work tightly with the `UploadProgressPanel` component\n *\n * The reactive state returned (`uploadItems`) will update whenever the progress\n * or status of each item being uploaded changes\n */\nexport const useUploadManager = (options: UseUploadManagerOptions) => {\n const { resolveCompleteStatus, ...restOptions } = options;\n const uploadState = ref<Record<string, UploadItem>>({});\n\n const { uploadFiles, cancel, setFailed } = createUploadManager({\n ...restOptions,\n\n onFileUploadComplete: (params) => {\n const { parentId, name } = uploadState.value[params.uploadId];\n const payload = { ...params, parentId, name };\n uploadState.value[params.uploadId].status =\n resolveCompleteStatus?.(payload) ?? \"complete\";\n options.onFileUploadComplete?.(payload);\n },\n\n onFileUploadFailed: ({ uploadId, error }) => {\n const { parentId } = uploadState.value[uploadId];\n uploadState.value[uploadId] = {\n ...uploadState.value[uploadId],\n status: \"failed\",\n failureDetails: error.message,\n };\n options.onFileUploadFailed?.({ uploadId, error, parentId });\n },\n\n onProgress: (uploadId, progress) => {\n if (uploadState.value[uploadId].status === \"inprogress\") {\n (uploadState.value[uploadId] as FileUploadItem).progress = progress;\n }\n },\n });\n\n const start = async (\n parentId: string,\n uploadPayload: Array<{ uploadId: string; file: File }>,\n ) => {\n try {\n uploadPayload.forEach(({ uploadId, file }) => {\n uploadState.value[uploadId] = {\n id: uploadId,\n name: file.name,\n size: file.size,\n progress: 0,\n type: \"file\",\n status: \"inprogress\",\n parentId,\n };\n });\n\n return await uploadFiles(uploadPayload);\n } catch (error) {\n consola.error(\"Error uploading files\", error);\n return [];\n }\n };\n\n const canCancel = (uploadId: string) =>\n uploadState.value[uploadId]?.status === \"inprogress\" ||\n uploadState.value[uploadId]?.status === \"failed\";\n\n const removeItem = (uploadId: string) => {\n if (canCancel(uploadId)) {\n cancel(uploadId);\n }\n\n delete uploadState.value[uploadId];\n };\n\n const resetState = () => {\n uploadState.value = {};\n };\n\n const uploadItems = computed(() => Object.values(uploadState.value));\n\n const hasPendingUploads = computed(() =>\n uploadItems.value.some(({ status }) => status === \"inprogress\"),\n );\n\n const totalPendingUploads = computed(\n () =>\n uploadItems.value.filter(({ status }) => status === \"inprogress\").length,\n );\n\n return {\n uploadState,\n hasPendingUploads,\n totalPendingUploads,\n start,\n cancel: (uploadId: string) => {\n cancel(uploadId);\n uploadState.value[uploadId].status = \"cancelled\";\n },\n canCancel,\n removeItem,\n resetState,\n setFailed: (uploadId: string, error: Error) => {\n uploadState.value[uploadId] = {\n ...uploadState.value[uploadId],\n status: \"failed\",\n failureDetails: error.message,\n };\n setFailed(uploadId);\n const { parentId } = uploadState.value[uploadId];\n options.onFileUploadFailed?.({ parentId, uploadId, error });\n },\n };\n};\n","import { computed, ref } from \"vue\";\nimport type { FetchError, FetchOptions } from \"ofetch\";\n\nimport { formatFileSize, knimeFileFormats, promise } from \"@knime/utils\";\n\nimport { rfcErrors } from \"../../rfcErrors\";\nimport { createUploadClient, createUploadStatusPoller } from \"../core\";\n\nimport type { UploadItem } from \"./types\";\nimport { useUploadManager } from \"./useUploadManager\";\n\nconst DEFAULT_MAX_UPLOAD_QUEUE_SIZE = 10;\n// eslint-disable-next-line no-magic-numbers\nconst DEFAULT_UPLOAD_SIZE_LIMIT_BYTES = 5 * 1024 * 1024 * 1024;\n\nconst DEFAULT_RETRY_DELAY_MS = 50;\n\n// Catalog & artifacts services currently have a limit for 5 GiB for\n// upload and copy/move operations but it is not communicated to clients.\n// Repeat the constant here to be able to still make a pre-check. See NXT-4510.\nconst getUploadSizeLimitBytes = () => DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;\n\ntype UseFileUploadOptions = {\n /**\n * Max number of concurrent uploads allowed in the upload queue.\n * When an upload is started with a number of files that exceed the pending\n * uploads then only files up-to the max queue size will be processed and the rest\n * will be ignored. This will also call the `onUploadQueueSizeExceeded` callback\n */\n maxUploadQueueSize?: number;\n /**\n * Callback that triggers every time any file in the queue completes its upload\n * @returns\n */\n onFileUploadComplete?: (payload: {\n uploadId: string;\n filePartIds: Record<number, string>;\n parentId: string;\n name: string;\n }) => void;\n /**\n * Callback that triggers every time any file in the queue fails its upload\n * @returns\n */\n onFileUploadFailed?: (payload: {\n uploadId: string;\n error: Error;\n parentId: string;\n }) => void;\n /**\n * Callback that triggers when a upload batch is started (via the `start` function) but the amount\n * of items to upload exceed the max configured size of the queue.\n * @returns\n */\n onUploadQueueSizeExceeded?: (maxQueueSize: number) => void;\n /**\n * Options to customize the fetch client used internally to make http requests.\n * e.g: headers, baseUrl, etc\n */\n customFetchClientOptions?: FetchOptions;\n\n /**\n * Whether the manifest endpoint is flat (`/manifest`) rather than nested\n * under a repository item (`/repository/{parentId}/manifest`). Used for\n * targets whose upload endpoint isn't scoped by a repository parent,\n * e.g. environment extension DB driver uploads.\n */\n shouldUseFlatManifestPath?: boolean;\n\n isFileWithProcessing?: (fileName: string) => boolean;\n};\n\n/**\n * This composable handles uploads of data to a hub repository item (space or workflow group).\n * It is a multi-step process which is described as follows:\n *\n * Step 1:\n * First we need to prepare an upload. The items will be uploaded to a `parent` container\n * identified by its id. (When `shouldUseFlatManifestPath` is set, the manifest endpoint isn't\n * scoped by a parent, so the `parentId` passed to `start` is unused for that request.)\n * Then, for each file we want to upload, we must provide paths relative\n * to the parent that identify each file. Example:\n * ```\n * {\n * \"items\": {\n * \"my-file.txt\": { itemContentType: \"text/plain\", itemContentSize: 5000 },\n * \"folder/my-file.zip\": { itemContentType: \"application/zip\", itemContentSize: 5000 },\n * }\n * }\n * ```\n * This describes 2 items, one placed in the parent's root and another inside a folder.\n * The response for this would look like:\n * ```\n * {\n * \"items\": {\n * \"my-file.txt\": { uploadId: \"id1\" },\n * \"folder/my-file.zip\": { uploadId: \"id2\" },\n * }\n * }\n * ```\n * Now, each corresponding `uploadId` uniquely identifies the upload process for\n * their respective files.\n *\n * Step 2:\n * Next, we need to start uploading the file parts. Technically, you _can_ just upload\n * each file as one big file \"part\", but practically that is not recommended because a hiccup\n * in the connection means you would have to start the upload from scratch; instead, we split\n * the files in smaller parts.\n * The upload manager helper will take care of splitting the file. However, the responsibility\n * of this composable is to supply a function to resolve the upload URL. This upload URL is obtained\n * by POSTing to an endpoint with the `uploadId` and the `partNumber` and this will return\n * the data required to make an upload to a presigned URL; this will be called for each file part.\n *\n * Step 3:\n * Completion:\n * Once the upload of a single file is complete, the upload manager will trigger the `onFileUploadComplete`\n * callback, which then will supply the ids of all the different parts that got uploaded for a given `uploadId`\n * Then we need to \"complete\" the upload by sending a request which provides the `uploadId` and a list of\n * the ids of all the parts. Note that some backends don't return the ids of the file parts after a successful\n * upload of a part, in this case empty strings or null can be used instead of the part id.\n *\n * Cancellation:\n * Alternative to completion, you can also cancel an upload. For this, the composable makes a DELETE request\n * providing the corresponding `uploadId` which will effectively cancel it.\n *\n * For a usage example, refer to the Docs page in storybook.\n */\nexport const useFileUpload = (options: UseFileUploadOptions = {}) => {\n const uploadClient = createUploadClient({\n customFetchClientOptions: options.customFetchClientOptions,\n shouldUseFlatManifestPath: options.shouldUseFlatManifestPath,\n });\n\n let useUploadManagerResult: ReturnType<typeof useUploadManager> | null = null;\n\n const getItemFromQueue = (uploadId: string) => {\n const { uploadState } = useUploadManagerResult!;\n\n if (!uploadState.value[uploadId]) {\n consola.warn(\n `Cannot get item in queue. Upload id \"${uploadId}\" doesn't exist`,\n );\n return undefined;\n }\n\n return uploadState.value[uploadId];\n };\n\n const updateItemInQueue = (uploadId: string, data: Partial<UploadItem>) => {\n const { uploadState } = useUploadManagerResult!;\n\n if (!uploadState.value[uploadId]) {\n consola.warn(\n `Cannot update item in queue. Upload id \"${uploadId}\" doesn't exist`,\n );\n return;\n }\n\n uploadState.value = {\n ...uploadState.value,\n [uploadId]: {\n ...uploadState.value[uploadId],\n ...data,\n } as UploadItem,\n };\n };\n\n const statusPoller = createUploadStatusPoller(uploadClient, {\n onComplete: (uploadId) =>\n updateItemInQueue(uploadId, { status: \"complete\" }),\n\n onFail: (uploadId, statusMessage) =>\n updateItemInQueue(uploadId, {\n status: \"failed\",\n failureDetails: statusMessage,\n }),\n });\n\n useUploadManagerResult = useUploadManager({\n resolveFilePartUploadURL: uploadClient.resolveFilePartUploadURL,\n\n resolveCompleteStatus: ({ name }) =>\n options.isFileWithProcessing?.(name) ? \"processing\" : \"complete\",\n\n onFileUploadComplete: ({ uploadId, filePartIds, name, parentId }) => {\n promise\n .retryPromise({\n fn: () => uploadClient.completeUpload(uploadId, filePartIds),\n })\n .then(() => {\n if (options.isFileWithProcessing?.(name)) {\n statusPoller.track(uploadId);\n }\n\n options.onFileUploadComplete?.({\n uploadId,\n filePartIds,\n name,\n parentId,\n });\n })\n .catch((error) => {\n consola.error(\"Error attempting to complete upload\", { error });\n useUploadManagerResult!.setFailed(uploadId, error);\n });\n },\n\n onFileUploadFailed: ({ uploadId, error, parentId }) => {\n options?.onFileUploadFailed?.({ uploadId, error, parentId });\n },\n });\n\n const getEnqueueableFiles = (files: File[]): File[] => {\n const { totalPendingUploads } = useUploadManagerResult;\n\n const { maxUploadQueueSize = DEFAULT_MAX_UPLOAD_QUEUE_SIZE } = options;\n\n if (files.length + totalPendingUploads.value > maxUploadQueueSize) {\n options.onUploadQueueSizeExceeded?.(maxUploadQueueSize);\n }\n\n // only keep as many files as the queue allows\n const effectiveQueueSize = Math.max(\n 0,\n maxUploadQueueSize - totalPendingUploads.value,\n );\n return files.slice(0, effectiveQueueSize);\n };\n\n const { uploadState, ...uploadManagerResultRest } = useUploadManagerResult;\n\n const prepareQueueSize = ref(0);\n\n const uploadQueue = computed<UploadItem[]>(() =>\n Object.values(uploadState.value),\n );\n\n return {\n ...uploadManagerResultRest,\n\n uploadQueue,\n getItemFromQueue,\n updateItemInQueue,\n\n isPreparingUpload: computed(() => prepareQueueSize.value > 0),\n totalFilesBeingPrepared: computed(() => prepareQueueSize.value),\n\n start: async (parentId: string, files: File[]) => {\n const enqueueableFiles = getEnqueueableFiles(files);\n const uploadSizeLimitBytes = getUploadSizeLimitBytes();\n const oversizedFiles = enqueueableFiles.filter(\n (file) => file.size > uploadSizeLimitBytes,\n );\n\n if (oversizedFiles.length > 0) {\n throw new rfcErrors.RFCError({\n title: \"Upload size limit exceeded\",\n status: 413,\n details: oversizedFiles.map(\n (file) =>\n `${file.name} (${formatFileSize(file.size)}) exceeds the upload limit of ${formatFileSize(uploadSizeLimitBytes)}.`,\n ),\n });\n }\n\n try {\n if (enqueueableFiles.length === 0) {\n return [];\n }\n\n prepareQueueSize.value += enqueueableFiles.length;\n\n const items = enqueueableFiles.map((file) => ({\n // strip extension for workflows. otherwise, the extension gets baked into the name\n // of the uploaded file and the backend appends an extra extension.\n // unnecessary for other file types\n path: knimeFileFormats.KNWF.getNameOrDefault(file, file.name),\n file,\n }));\n\n const uploadPayload = await promise.retryPromise({\n fn: () => uploadClient.prepareUpload(parentId, items),\n excludeError: (error: FetchError) =>\n // eslint-disable-next-line no-magic-numbers\n Boolean(error.statusCode && error.statusCode < 500),\n retryDelayMS: DEFAULT_RETRY_DELAY_MS,\n });\n\n const uploadIds = uploadPayload.map((upload) => upload.uploadId);\n\n // accept floating promise, errors are handled inside the function\n useUploadManagerResult.start(parentId, uploadPayload);\n\n return uploadIds;\n } catch (error) {\n throw rfcErrors.tryParse(error) ?? error;\n } finally {\n // errors can only be thrown in the prepareUpload call, useUploadManagerResult.start does its own error handling\n prepareQueueSize.value -= enqueueableFiles.length;\n }\n },\n\n cancel: (uploadId: string) => {\n useUploadManagerResult.cancel(uploadId);\n uploadClient.cancelUpload(uploadId).catch((error: unknown) => {\n consola.error(\"There was a problem cancelling the upload\", { error });\n });\n },\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dataTransfer.js","names":[],"sources":["../src/dataTransfer/shared/ui/CollapsiblePanel/useIconFlipper.ts","../src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue","../src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue","../src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue","../src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue","../src/dataTransfer/shared/ui/ProgressList/ProgressList.vue","../src/dataTransfer/download/ui/DownloadProgressPanelItem.vue","../src/dataTransfer/download/ui/DownloadProgressPanelItem.vue","../src/dataTransfer/download/ui/DownloadProgressPanel.vue","../src/dataTransfer/download/ui/DownloadProgressPanel.vue","../src/dataTransfer/download/useDownloadArtifact.ts","../src/dataTransfer/upload/UploadProgressPanelItem.vue","../src/dataTransfer/upload/UploadProgressPanelItem.vue","../src/dataTransfer/upload/UploadProgressPanel.vue","../src/dataTransfer/upload/UploadProgressPanel.vue","../src/dataTransfer/upload/useUploadManager.ts","../src/dataTransfer/upload/useFileUpload.ts"],"sourcesContent":["import { type CSSProperties, type ComputedRef, computed } from \"vue\";\n\ntype UseIconFlipperOptions = {\n active: ComputedRef<boolean>;\n initialRotation?: `${string}deg`;\n};\n\nexport const useIconFlipper = (options: UseIconFlipperOptions) => {\n const iconFlipStyles = computed<CSSProperties>(() => {\n const declaration: Partial<CSSProperties> = {\n transition: \"transform 0.2s linear\",\n transform: \"scaleY(1)\",\n };\n\n if (options.initialRotation) {\n declaration.rotate = `${options.initialRotation}`;\n }\n\n const transform = options.active.value ? \"scaleY(-1)\" : undefined;\n\n return { ...declaration, transform };\n });\n\n return { iconFlipStyles };\n};\n","<script setup lang=\"ts\">\nimport { computed, useId } from \"vue\";\n\nimport { KdsButton } from \"@knime/kds-components\";\n\nimport type { CollapsiblePanelProps } from \"./types\";\nimport { useIconFlipper } from \"./useIconFlipper\";\n\nconst { headline, canBeDismissed = false } =\n defineProps<CollapsiblePanelProps>();\n\nconst emit = defineEmits<{\n close: [];\n}>();\n\nconst modelValue = defineModel<boolean>({ default: true });\n\nconst { iconFlipStyles } = useIconFlipper({\n active: computed(() => modelValue.value),\n initialRotation: \"180deg\",\n});\nconst panelContentId = useId();\n</script>\n\n<template>\n <div class=\"collapsible-panel\" :class=\"{ collapsed: !modelValue }\">\n <div class=\"title-bar\">\n {{ headline }}\n <div class=\"actions\">\n <KdsButton\n size=\"small\"\n variant=\"transparent\"\n leading-icon=\"chevron-up\"\n :aria-expanded=\"modelValue\"\n :aria-controls=\"panelContentId\"\n :ariaLabel=\"modelValue ? 'Collapse' : 'Expand'\"\n :style=\"iconFlipStyles\"\n @click=\"modelValue = !modelValue\"\n />\n\n <KdsButton\n v-if=\"canBeDismissed\"\n size=\"small\"\n variant=\"transparent\"\n ariaLabel=\"Close\"\n leading-icon=\"x-close\"\n @click=\"emit('close')\"\n />\n </div>\n </div>\n\n <Transition name=\"expand\">\n <div v-show=\"modelValue\" :id=\"panelContentId\" class=\"panel-content\">\n <slot />\n </div>\n </Transition>\n </div>\n</template>\n\n<style scoped>\n.collapsible-panel {\n background: var(--kds-color-surface-default);\n border-radius: var(--kds-spacing-container-0-25x);\n box-shadow: var(--kds-elevation-level-3);\n\n & .title-bar {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-2-5x);\n padding: var(--kds-spacing-container-0-5x)\n var(--kds-spacing-container-0-75x);\n font: var(--kds-font-base-title-small-strong);\n color: var(--kds-color-text-and-icon-neutral);\n\n & .actions {\n display: flex;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n margin-left: auto;\n }\n }\n\n &:not(.collapsed) {\n & .title-bar {\n border-bottom: var(--kds-border-base-subtle);\n }\n }\n}\n\n.panel-content {\n overflow: hidden;\n}\n\n.expand-enter-active,\n.expand-leave-active {\n display: grid;\n overflow: hidden;\n transition:\n grid-template-rows 0.2s ease-in-out,\n opacity 0.2s ease-in-out;\n}\n\n.expand-enter-from,\n.expand-leave-to {\n grid-template-rows: 0fr;\n opacity: 0;\n}\n\n.expand-enter-to,\n.expand-leave-from {\n grid-template-rows: 1fr;\n opacity: 1;\n}\n\n.panel-content > :deep(*) {\n min-height: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useId } from \"vue\";\n\nimport { KdsButton } from \"@knime/kds-components\";\n\nimport type { CollapsiblePanelProps } from \"./types\";\nimport { useIconFlipper } from \"./useIconFlipper\";\n\nconst { headline, canBeDismissed = false } =\n defineProps<CollapsiblePanelProps>();\n\nconst emit = defineEmits<{\n close: [];\n}>();\n\nconst modelValue = defineModel<boolean>({ default: true });\n\nconst { iconFlipStyles } = useIconFlipper({\n active: computed(() => modelValue.value),\n initialRotation: \"180deg\",\n});\nconst panelContentId = useId();\n</script>\n\n<template>\n <div class=\"collapsible-panel\" :class=\"{ collapsed: !modelValue }\">\n <div class=\"title-bar\">\n {{ headline }}\n <div class=\"actions\">\n <KdsButton\n size=\"small\"\n variant=\"transparent\"\n leading-icon=\"chevron-up\"\n :aria-expanded=\"modelValue\"\n :aria-controls=\"panelContentId\"\n :ariaLabel=\"modelValue ? 'Collapse' : 'Expand'\"\n :style=\"iconFlipStyles\"\n @click=\"modelValue = !modelValue\"\n />\n\n <KdsButton\n v-if=\"canBeDismissed\"\n size=\"small\"\n variant=\"transparent\"\n ariaLabel=\"Close\"\n leading-icon=\"x-close\"\n @click=\"emit('close')\"\n />\n </div>\n </div>\n\n <Transition name=\"expand\">\n <div v-show=\"modelValue\" :id=\"panelContentId\" class=\"panel-content\">\n <slot />\n </div>\n </Transition>\n </div>\n</template>\n\n<style scoped>\n.collapsible-panel {\n background: var(--kds-color-surface-default);\n border-radius: var(--kds-spacing-container-0-25x);\n box-shadow: var(--kds-elevation-level-3);\n\n & .title-bar {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-2-5x);\n padding: var(--kds-spacing-container-0-5x)\n var(--kds-spacing-container-0-75x);\n font: var(--kds-font-base-title-small-strong);\n color: var(--kds-color-text-and-icon-neutral);\n\n & .actions {\n display: flex;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n margin-left: auto;\n }\n }\n\n &:not(.collapsed) {\n & .title-bar {\n border-bottom: var(--kds-border-base-subtle);\n }\n }\n}\n\n.panel-content {\n overflow: hidden;\n}\n\n.expand-enter-active,\n.expand-leave-active {\n display: grid;\n overflow: hidden;\n transition:\n grid-template-rows 0.2s ease-in-out,\n opacity 0.2s ease-in-out;\n}\n\n.expand-enter-from,\n.expand-leave-to {\n grid-template-rows: 0fr;\n opacity: 0;\n}\n\n.expand-enter-to,\n.expand-leave-from {\n grid-template-rows: 1fr;\n opacity: 1;\n}\n\n.panel-content > :deep(*) {\n min-height: 0;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport {\n KdsBadge,\n KdsButton,\n KdsIcon,\n KdsProgressBar,\n vKdsTooltip,\n} from \"@knime/kds-components\";\n\nimport { type ProgressItemProps } from \"./types\";\n\ndefineProps<ProgressItemProps>();\n</script>\n\n<template>\n <div class=\"progress-wrapper\">\n <div :class=\"['progress-item', { padded: progress === undefined }]\">\n <div class=\"item-info\">\n <KdsIcon :name=\"icon\" />\n\n <div class=\"item-name\">\n <div v-kds-tooltip:truncated=\"headline\" class=\"title\">\n {{ headline }}\n </div>\n <span v-if=\"subtitle\" class=\"subtitle\">\n {{ subtitle }}\n </span>\n </div>\n </div>\n\n <div class=\"item-action\">\n <KdsBadge\n v-if=\"badge\"\n :variant=\"badge.variant\"\n :label=\"badge.text\"\n :leading-icon=\"badge.icon\"\n size=\"xxsmall\"\n />\n\n <template v-for=\"action in actions.slice(0, 2)\" :key=\"action.id\">\n <KdsButton\n :leading-icon=\"action.icon\"\n :ariaLabel=\"action.label\"\n variant=\"transparent\"\n @click=\"action.handler\"\n />\n </template>\n </div>\n </div>\n\n <div v-if=\"typeof progress === 'number'\" class=\"progress\">\n <KdsProgressBar :percentage=\"progress\" size=\"medium\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.progress-wrapper {\n --base-padding: var(--kds-spacing-container-1x);\n --progress-bar-height: var(--kds-dimension-component-height-0-25x);\n --progress-bar-distance: var(--kds-spacing-container-0-25x);\n\n display: flex;\n flex-direction: column;\n gap: var(--progress-bar-distance);\n height: var(--kds-dimension-component-height-4x);\n padding: var(--base-padding);\n}\n\n.progress-item {\n display: flex;\n align-items: center;\n width: 100%;\n height: 100%;\n\n /* add extra margin to prevent content jump when a progress bars appears/disappears */\n &.padded {\n margin-bottom: calc(\n var(--progress-bar-height) + var(--progress-bar-distance)\n );\n }\n}\n\n.progress {\n min-height: var(--progress-bar-height);\n}\n\n.item-info {\n display: flex;\n flex-grow: 1;\n gap: var(--kds-spacing-container-0-25x);\n overflow: hidden;\n\n & .item-name {\n display: flex;\n flex-grow: 1;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n justify-content: center;\n overflow: hidden;\n color: var(--kds-color-text-and-icon-neutral);\n\n & .title {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n font: var(--kds-font-base-interactive-small);\n white-space: nowrap;\n }\n\n & .subtitle {\n font: var(--kds-font-base-subtext-small);\n }\n }\n}\n\n.item-action {\n display: flex;\n flex-shrink: 0;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport {\n KdsBadge,\n KdsButton,\n KdsIcon,\n KdsProgressBar,\n vKdsTooltip,\n} from \"@knime/kds-components\";\n\nimport { type ProgressItemProps } from \"./types\";\n\ndefineProps<ProgressItemProps>();\n</script>\n\n<template>\n <div class=\"progress-wrapper\">\n <div :class=\"['progress-item', { padded: progress === undefined }]\">\n <div class=\"item-info\">\n <KdsIcon :name=\"icon\" />\n\n <div class=\"item-name\">\n <div v-kds-tooltip:truncated=\"headline\" class=\"title\">\n {{ headline }}\n </div>\n <span v-if=\"subtitle\" class=\"subtitle\">\n {{ subtitle }}\n </span>\n </div>\n </div>\n\n <div class=\"item-action\">\n <KdsBadge\n v-if=\"badge\"\n :variant=\"badge.variant\"\n :label=\"badge.text\"\n :leading-icon=\"badge.icon\"\n size=\"xxsmall\"\n />\n\n <template v-for=\"action in actions.slice(0, 2)\" :key=\"action.id\">\n <KdsButton\n :leading-icon=\"action.icon\"\n :ariaLabel=\"action.label\"\n variant=\"transparent\"\n @click=\"action.handler\"\n />\n </template>\n </div>\n </div>\n\n <div v-if=\"typeof progress === 'number'\" class=\"progress\">\n <KdsProgressBar :percentage=\"progress\" size=\"medium\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.progress-wrapper {\n --base-padding: var(--kds-spacing-container-1x);\n --progress-bar-height: var(--kds-dimension-component-height-0-25x);\n --progress-bar-distance: var(--kds-spacing-container-0-25x);\n\n display: flex;\n flex-direction: column;\n gap: var(--progress-bar-distance);\n height: var(--kds-dimension-component-height-4x);\n padding: var(--base-padding);\n}\n\n.progress-item {\n display: flex;\n align-items: center;\n width: 100%;\n height: 100%;\n\n /* add extra margin to prevent content jump when a progress bars appears/disappears */\n &.padded {\n margin-bottom: calc(\n var(--progress-bar-height) + var(--progress-bar-distance)\n );\n }\n}\n\n.progress {\n min-height: var(--progress-bar-height);\n}\n\n.item-info {\n display: flex;\n flex-grow: 1;\n gap: var(--kds-spacing-container-0-25x);\n overflow: hidden;\n\n & .item-name {\n display: flex;\n flex-grow: 1;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n justify-content: center;\n overflow: hidden;\n color: var(--kds-color-text-and-icon-neutral);\n\n & .title {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n font: var(--kds-font-base-interactive-small);\n white-space: nowrap;\n }\n\n & .subtitle {\n font: var(--kds-font-base-subtext-small);\n }\n }\n}\n\n.item-action {\n display: flex;\n flex-shrink: 0;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n}\n</style>\n","<template>\n <div class=\"progress-list\" tabindex=\"0\">\n <slot />\n </div>\n</template>\n\n<style scoped>\n.progress-list {\n max-height: var(--kds-dimension-component-height-20x);\n overflow-y: auto;\n\n &:deep(.progress-wrapper):not(:first-child) {\n border-top: var(--kds-border-base-subtle);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../../shared/ui\";\n\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n item: DownloadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n download: [];\n}>();\n\nconst statusMapper = {\n IN_PROGRESS: [\"Zipping\", \"info\", \"reload\"],\n READY: [\"Ready\", \"success\", \"circle-info\"],\n CANCELLED: [\"Cancelled\", \"error\", \"circle-error\"],\n FAILED: [\"Failed\", \"error\", \"circle-error\"],\n} satisfies Record<\n DownloadItem[\"status\"],\n [string, KdsBadgeVariant, KdsIconName]\n>;\n\nconst badge = computed<ProgressItemBadge>(() => {\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return {\n text,\n variant,\n icon,\n } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => props.item.status === \"IN_PROGRESS\");\nconst isReady = computed(() => props.item.status === \"READY\");\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const actions: ProgressItemAction[] = [];\n\n if (isCancellable.value) {\n actions.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (isReady.value) {\n actions.push({\n id: \"download\",\n icon: \"cloud-download\",\n label: \"Download again\",\n handler: () => emit(\"download\"),\n });\n }\n\n return actions;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.downloadId\"\n icon=\"file\"\n :headline=\"item.name\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../../shared/ui\";\n\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n item: DownloadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n download: [];\n}>();\n\nconst statusMapper = {\n IN_PROGRESS: [\"Zipping\", \"info\", \"reload\"],\n READY: [\"Ready\", \"success\", \"circle-info\"],\n CANCELLED: [\"Cancelled\", \"error\", \"circle-error\"],\n FAILED: [\"Failed\", \"error\", \"circle-error\"],\n} satisfies Record<\n DownloadItem[\"status\"],\n [string, KdsBadgeVariant, KdsIconName]\n>;\n\nconst badge = computed<ProgressItemBadge>(() => {\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return {\n text,\n variant,\n icon,\n } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => props.item.status === \"IN_PROGRESS\");\nconst isReady = computed(() => props.item.status === \"READY\");\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const actions: ProgressItemAction[] = [];\n\n if (isCancellable.value) {\n actions.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (isReady.value) {\n actions.push({\n id: \"download\",\n icon: \"cloud-download\",\n label: \"Download again\",\n handler: () => emit(\"download\"),\n });\n }\n\n return actions;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.downloadId\"\n icon=\"file\"\n :headline=\"item.name\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, watch } from \"vue\";\n\nimport { CollapsiblePanel, ProgressList } from \"../../shared/ui\";\n\nimport DownloadProgressPanelItem from \"./DownloadProgressPanelItem.vue\";\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n /**\n * List of current download items that should be displayed\n */\n items: DownloadItem[];\n};\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [item: DownloadItem];\n download: [item: DownloadItem];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => props.items.length);\nconst totalDownloadedItems = computed(\n () => props.items.filter(({ status }) => status === \"READY\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `file${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Downloaded ${totalDownloadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasItemsInProgress = computed(() =>\n props.items.some(({ status }) => status === \"IN_PROGRESS\"),\n);\n\nwatch(\n () => props.items?.length,\n (newValue, oldValue) => {\n if (oldValue > 0 && !newValue) {\n emit(\"close\");\n }\n },\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"download-panel\"\n :can-be-dismissed=\"!hasItemsInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"download-panel-content\">\n <ProgressList>\n <DownloadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.downloadId\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @download=\"emit('download', item)\"\n />\n </ProgressList>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.download-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.download-panel-content {\n max-height: var(--kds-dimension-component-height-20x);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, watch } from \"vue\";\n\nimport { CollapsiblePanel, ProgressList } from \"../../shared/ui\";\n\nimport DownloadProgressPanelItem from \"./DownloadProgressPanelItem.vue\";\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n /**\n * List of current download items that should be displayed\n */\n items: DownloadItem[];\n};\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [item: DownloadItem];\n download: [item: DownloadItem];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => props.items.length);\nconst totalDownloadedItems = computed(\n () => props.items.filter(({ status }) => status === \"READY\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `file${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Downloaded ${totalDownloadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasItemsInProgress = computed(() =>\n props.items.some(({ status }) => status === \"IN_PROGRESS\"),\n);\n\nwatch(\n () => props.items?.length,\n (newValue, oldValue) => {\n if (oldValue > 0 && !newValue) {\n emit(\"close\");\n }\n },\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"download-panel\"\n :can-be-dismissed=\"!hasItemsInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"download-panel-content\">\n <ProgressList>\n <DownloadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.downloadId\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @download=\"emit('download', item)\"\n />\n </ProgressList>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.download-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.download-panel-content {\n max-height: var(--kds-dimension-component-height-20x);\n}\n</style>\n","import { computed, ref } from \"vue\";\nimport { FetchError, type FetchOptions } from \"ofetch\";\n\nimport { sleep } from \"@knime/utils\";\n\nimport { rfcErrors } from \"../../rfcErrors\";\nimport { createHttpClient } from \"../shared/httpClient\";\n\nimport type { DownloadItem as DownloadItemExternal } from \"./ui/types\";\n\nconst DEFAULT_MAX_RETRIES = null;\nconst DEFAULT_POLLING_INTERVAL = 2000;\n\ntype ArtifactStatusResponse = {\n downloadId?: string;\n status?: string;\n statusMessage?: string;\n lastUpdated?: string;\n downloadUrl?: string;\n};\n\n// Initial request can have either download url (if it can be downloaded immediately) or download id (to poll for the status)\ntype RequestDownloadResponse =\n | {\n downloadId: string;\n }\n | {\n downloadUrl: string;\n };\n\n// type guard\nconst hasDownloadUrl = (\n response: RequestDownloadResponse,\n): response is { downloadUrl: string } => {\n return \"downloadUrl\" in response;\n};\n\n/**\n * The version of the artifact to download.\n */\ntype ItemVersion = number | \"current-state\" | \"most-recent\";\n\n/**\n * Tracks the state of each concurrent download.\n */\ntype DownloadItem = DownloadItemExternal & {\n itemId: string;\n version?: ItemVersion;\n};\n\nexport type UseDownloadArtifactOptions = {\n /**\n * The maximum number of times to poll for the download URL. Default is `null` (no limit).\n */\n maxRetries?: number;\n /**\n * The interval in milliseconds between each poll. Default is `2000` ms.\n */\n pollingInterval?: number;\n /**\n * Options to customize the fetch client used internally to make http requests.\n * e.g: headers, baseUrl, etc\n */\n customFetchClientOptions?: FetchOptions;\n};\n\nconst isAbortError = (error: unknown) =>\n error instanceof DOMException && error.name === \"AbortError\";\n\n/**\n * This composable supports multiple concurrent downloads. Each time you call ```start```, the following behavior applies:\n *\n * 1. **Immediate-ready artifact**: If the first status check for the artifact returns `READY`, then there's no polling and no need\n * to add it to `downloadItems`. The download URL is opened in the same tab immediately.\n *\n * 2. **Not-ready artifact**: If the artifact is not yet ready (e.g. needs zipping), then we add a record to `downloadItems` with status `IN_PROGRESS`\n * and repeatedly poll until it becomes `READY`, the polling times out, or the user aborts.\n *\n * **Usage**:\n * ```\n * const { start, cancel, downloadItems } = useDownloadArtifact({ ...options });\n *\n * // start a download\n * await start({ itemId: \"1234\", version: 1 });\n *\n * // if needed, cancel it:\n * cancel(\"1234\");\n *\n * // downloadItems (as a computed array) will reflect the current statuses.\n * ```\n */\nexport const useDownloadArtifact = (\n options: UseDownloadArtifactOptions = {},\n) => {\n const $ofetch = createHttpClient(options.customFetchClientOptions);\n\n const downloadItems = ref<Record<string, DownloadItem>>({});\n const abortControllers: Record<string, AbortController> = {};\n\n // Use fallback values if none are provided\n const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n const pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;\n\n const cancel = (downloadId: string) => {\n const itemController = abortControllers[downloadId];\n if (itemController) {\n itemController.abort();\n delete abortControllers[downloadId];\n }\n };\n\n const removeItem = (downloadId: string) => {\n cancel(downloadId);\n delete downloadItems.value[downloadId];\n };\n\n const fetchDownloadStatus = ({\n downloadId,\n signal,\n }: {\n downloadId: string;\n signal?: AbortSignal;\n }) => {\n return $ofetch<ArtifactStatusResponse>(`/downloads/${downloadId}/status`, {\n method: \"GET\",\n signal,\n });\n };\n\n const openDownload = (downloadUrl: string) => {\n const a = document.createElement(\"a\");\n a.target = \"_blank\";\n a.href = downloadUrl;\n a.download = \"\";\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n };\n\n /**\n * Makes a request to the download status endpoint for the given download item.\n * Returns a boolean that is true if a terminal state has been reached (download is ready,\n * download failed, or user aborted download)\n * @param downloadId the `downloadId`\n * @param signal the abort signal passed to the request to be able to abort it\n * @returns a boolean indicating whether a terminal state has been reached\n */\n const handlePollingResult = async ({\n downloadId,\n signal,\n }: {\n downloadId: string;\n signal: AbortSignal;\n }) => {\n try {\n const statusResponse = await fetchDownloadStatus({\n downloadId,\n signal,\n });\n\n if (statusResponse?.status === \"READY\" && statusResponse?.downloadUrl) {\n downloadItems.value[downloadId].status = \"READY\";\n downloadItems.value[downloadId].downloadUrl =\n statusResponse.downloadUrl;\n // open download in a new tab\n openDownload(statusResponse.downloadUrl);\n return true;\n } else if (statusResponse?.status === \"FAILED\") {\n let details = \"Download failed\";\n if (statusResponse?.statusMessage) {\n details += `: ${statusResponse.statusMessage}`;\n }\n throw new Error(details);\n }\n } catch (error: unknown) {\n consola.error(\"Error fetching status:\", error);\n // https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort\n const isAborted =\n isAbortError(error) ||\n (error instanceof FetchError && isAbortError(error.cause));\n\n if (!isAborted) {\n downloadItems.value[downloadId].status = \"FAILED\";\n downloadItems.value[downloadId].failureDetails =\n error instanceof Error ? error.message : (error as string);\n }\n return true;\n }\n return false;\n };\n\n const pollDownloadItem = async ({\n itemId,\n downloadId,\n version,\n name,\n }: {\n itemId: string;\n downloadId: string;\n version?: ItemVersion;\n name: string;\n }) => {\n const controller = new AbortController();\n abortControllers[downloadId] = controller;\n\n const signal = controller.signal;\n\n let itemName = `${name}`;\n if (version) {\n itemName += ` (${version})`;\n }\n\n downloadItems.value[downloadId] = {\n itemId,\n ...(version && { version }),\n downloadId,\n name: itemName,\n status: \"IN_PROGRESS\",\n };\n\n const abortListener = () => {\n controller.signal.removeEventListener(\"abort\", abortListener);\n if (!downloadItems.value[downloadId]) {\n return;\n }\n downloadItems.value[downloadId].status = \"CANCELLED\";\n downloadItems.value[downloadId].failureDetails =\n \"Download was cancelled manually\";\n };\n controller.signal.addEventListener(\"abort\", abortListener);\n\n let tries = 0;\n // eslint-disable-next-line no-unmodified-loop-condition\n while (maxRetries === null || tries < maxRetries) {\n tries++;\n await sleep(pollingInterval);\n\n const isTerminalState = await handlePollingResult({ downloadId, signal });\n\n if (isTerminalState) {\n break;\n }\n }\n\n delete abortControllers[downloadId];\n\n if (maxRetries !== null && tries >= maxRetries) {\n downloadItems.value[downloadId].status = \"FAILED\";\n downloadItems.value[downloadId].failureDetails = \"Download timed out\";\n }\n };\n\n const start = async ({\n itemId,\n version,\n name,\n }: {\n itemId: string;\n name: string;\n version?: ItemVersion;\n }) => {\n try {\n const response = await $ofetch<RequestDownloadResponse>(\n `/repository/${itemId}/artifact`,\n {\n method: \"GET\",\n query: { version },\n },\n );\n\n // if download is ready, open url immediately\n if (hasDownloadUrl(response)) {\n openDownload(response.downloadUrl);\n return;\n }\n\n // otherwise start polling download status\n await pollDownloadItem({\n itemId,\n version,\n downloadId: response.downloadId,\n name,\n });\n } catch (error: unknown) {\n // here only errors thrown by the initial requests are caught;\n // errors occurring while polling will be handled in pollDownloadItem\n throw rfcErrors.tryParse(error) ?? error;\n }\n };\n\n const totalItemsBeingZipped = computed(\n () =>\n Object.values(downloadItems.value).filter(\n ({ status }) => status === \"IN_PROGRESS\",\n ).length,\n );\n\n const resetState = () => {\n for (const [downloadId, controller] of Object.entries(abortControllers)) {\n controller?.abort();\n delete abortControllers[downloadId];\n }\n downloadItems.value = {};\n };\n\n const downloadItemsArray = computed(() => Object.values(downloadItems.value));\n\n return {\n /**\n * Initiates a download of the given itemId and version.\n */\n start,\n\n /**\n * Cancels the ongoing download for a given itemId (if any).\n * Sets the status in `downloadItems` to `CANCELLED`.\n */\n cancel,\n\n /**\n * Removes item from `downloadItems`.\n */\n removeItem,\n\n /**\n * A computed array of all current items that are being zipped and prepared for download.\n */\n downloadItems: downloadItemsArray,\n\n /**\n * The total number of zipping items in progress.\n */\n totalItemsBeingZipped,\n\n /**\n * Resets the state of the composable, clearing all downloads and controllers.\n */\n resetState,\n\n /**\n * Opens a download from the given url\n */\n openDownload,\n };\n};\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\nimport {\n formatFileSize,\n getFileExtension,\n knimeFileFormats,\n} from \"@knime/utils\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../shared/ui\";\n\nimport type { UploadItem, UploadItemStatus } from \"./types\";\n\ntype Props = {\n item: UploadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n viewDetails: [];\n}>();\n\nconst subtitle = computed(() => {\n if (props.item.type === \"file\") {\n const progressed = formatFileSize(\n (props.item.size / 100) * (props.item.progress ?? 0),\n );\n\n const total = formatFileSize(props.item.size);\n return `${progressed} of ${total}`;\n }\n\n if (props.item.type === \"directory\") {\n const total = props.item.totalEntries;\n const progressed = props.item.uploadedEntries;\n\n return `${progressed} of ${total} file${total > 1 ? \"s\" : \"\"}`;\n }\n\n return \"\";\n});\n\nconst progressPercentageValue = computed(() => {\n if (props.item.type === \"file\") {\n return props.item.progress;\n }\n\n if (props.item.type === \"directory\") {\n // The KdsProgressBar only accepts percentages, but for directory items\n // the consumer supplies different values so we must derive the percentage\n const progressPercentage =\n (props.item.uploadedEntries * 100) / props.item.totalEntries;\n\n return progressPercentage;\n }\n\n return undefined;\n});\n\nconst statusMapper = {\n inprogress: [\"Uploading\", \"info\", \"reload\"],\n failed: [\"Failed\", \"error\", \"circle-error\"],\n complete: [\"Uploaded\", \"success\", \"circle-success\"],\n cancelled: [\"Cancelled\", \"error\", \"circle-error\"],\n processing: [\"Processing\", \"info\", \"reload\"],\n \"partial-success\": [\"Partial success\", \"warning\", \"warning\"],\n} satisfies Record<UploadItemStatus, [string, KdsBadgeVariant, KdsIconName]>;\n\nconst itemIcon = computed<KdsIconName>(() => {\n if (props.item.type === \"directory\") {\n return \"folder\";\n }\n\n const extension = getFileExtension(props.item.name);\n return extension === knimeFileFormats.KNWF.extension ? \"workflow\" : \"file\";\n});\n\nconst badge = computed<ProgressItemBadge | undefined>(() => {\n if (!props.item.status) {\n return undefined;\n }\n\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return { text, variant, icon } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.cancellable === undefined\n ) {\n return true;\n }\n\n return props.item.actions.cancellable;\n});\n\nconst shouldShowCancelAction = computed(\n () => props.item.status === \"inprogress\" && isCancellable.value,\n);\n\nconst isInspectable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.inspectable === undefined\n ) {\n return false;\n }\n\n return props.item.actions.inspectable;\n});\n\nconst shouldShowViewDetailsAction = computed(\n () => props.item.status === \"partial-success\" && isInspectable.value,\n);\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const result: ProgressItemAction[] = [];\n\n if (shouldShowCancelAction.value) {\n result.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (shouldShowViewDetailsAction.value) {\n result.push({\n id: \"view-details\",\n icon: \"circle-info\",\n label: \"View details\",\n handler: () => emit(\"viewDetails\"),\n });\n }\n\n return result;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.id\"\n :headline=\"item.name\"\n :subtitle=\"subtitle\"\n :icon=\"itemIcon\"\n :progress=\"progressPercentageValue\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\nimport {\n formatFileSize,\n getFileExtension,\n knimeFileFormats,\n} from \"@knime/utils\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../shared/ui\";\n\nimport type { UploadItem, UploadItemStatus } from \"./types\";\n\ntype Props = {\n item: UploadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n viewDetails: [];\n}>();\n\nconst subtitle = computed(() => {\n if (props.item.type === \"file\") {\n const progressed = formatFileSize(\n (props.item.size / 100) * (props.item.progress ?? 0),\n );\n\n const total = formatFileSize(props.item.size);\n return `${progressed} of ${total}`;\n }\n\n if (props.item.type === \"directory\") {\n const total = props.item.totalEntries;\n const progressed = props.item.uploadedEntries;\n\n return `${progressed} of ${total} file${total > 1 ? \"s\" : \"\"}`;\n }\n\n return \"\";\n});\n\nconst progressPercentageValue = computed(() => {\n if (props.item.type === \"file\") {\n return props.item.progress;\n }\n\n if (props.item.type === \"directory\") {\n // The KdsProgressBar only accepts percentages, but for directory items\n // the consumer supplies different values so we must derive the percentage\n const progressPercentage =\n (props.item.uploadedEntries * 100) / props.item.totalEntries;\n\n return progressPercentage;\n }\n\n return undefined;\n});\n\nconst statusMapper = {\n inprogress: [\"Uploading\", \"info\", \"reload\"],\n failed: [\"Failed\", \"error\", \"circle-error\"],\n complete: [\"Uploaded\", \"success\", \"circle-success\"],\n cancelled: [\"Cancelled\", \"error\", \"circle-error\"],\n processing: [\"Processing\", \"info\", \"reload\"],\n \"partial-success\": [\"Partial success\", \"warning\", \"warning\"],\n} satisfies Record<UploadItemStatus, [string, KdsBadgeVariant, KdsIconName]>;\n\nconst itemIcon = computed<KdsIconName>(() => {\n if (props.item.type === \"directory\") {\n return \"folder\";\n }\n\n const extension = getFileExtension(props.item.name);\n return extension === knimeFileFormats.KNWF.extension ? \"workflow\" : \"file\";\n});\n\nconst badge = computed<ProgressItemBadge | undefined>(() => {\n if (!props.item.status) {\n return undefined;\n }\n\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return { text, variant, icon } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.cancellable === undefined\n ) {\n return true;\n }\n\n return props.item.actions.cancellable;\n});\n\nconst shouldShowCancelAction = computed(\n () => props.item.status === \"inprogress\" && isCancellable.value,\n);\n\nconst isInspectable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.inspectable === undefined\n ) {\n return false;\n }\n\n return props.item.actions.inspectable;\n});\n\nconst shouldShowViewDetailsAction = computed(\n () => props.item.status === \"partial-success\" && isInspectable.value,\n);\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const result: ProgressItemAction[] = [];\n\n if (shouldShowCancelAction.value) {\n result.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (shouldShowViewDetailsAction.value) {\n result.push({\n id: \"view-details\",\n icon: \"circle-info\",\n label: \"View details\",\n handler: () => emit(\"viewDetails\"),\n });\n }\n\n return result;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.id\"\n :headline=\"item.name\"\n :subtitle=\"subtitle\"\n :icon=\"itemIcon\"\n :progress=\"progressPercentageValue\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport { KdsButton, KdsLoadingSkeleton } from \"@knime/kds-components\";\n\nimport { CollapsiblePanel, ProgressList } from \"../shared/ui\";\n\nimport UploadProgressPanelItem from \"./UploadProgressPanelItem.vue\";\nimport type { UploadItem, UploadProgressPanelProps } from \"./types\";\n\nconst {\n items,\n placeholderItems = 0,\n canRetry = false,\n} = defineProps<UploadProgressPanelProps>();\n\nconst emit = defineEmits<{\n cancel: [item: UploadItem];\n viewDetails: [item: UploadItem];\n retry: [];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => items.length);\nconst uploadedItems = computed(\n () => items.filter(({ status }) => status === \"complete\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `item${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasSomeItemInProgress = computed(\n () =>\n items.some(\n ({ status }) => status === \"inprogress\" || status === \"processing\",\n ) || placeholderItems > 0,\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"upload-panel\"\n :can-be-dismissed=\"!hasSomeItemInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"upload-panel-content\">\n <ProgressList>\n <UploadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.id\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @view-details=\"emit('viewDetails', item)\"\n />\n </ProgressList>\n\n <template v-if=\"placeholderItems > 0\">\n <div\n v-for=\"skeleton in placeholderItems\"\n :key=\"skeleton\"\n class=\"skeleton-container\"\n >\n <KdsLoadingSkeleton variant=\"icon-large\" />\n <div class=\"skeleton-text-content\">\n <KdsLoadingSkeleton variant=\"label\" />\n <KdsLoadingSkeleton variant=\"text\" />\n </div>\n </div>\n </template>\n\n <div v-if=\"canRetry && !hasSomeItemInProgress\" class=\"retry-bar\">\n <KdsButton\n size=\"small\"\n variant=\"outlined\"\n label=\"Retry failed items\"\n leading-icon=\"reload\"\n @click=\"emit('retry')\"\n />\n </div>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.upload-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.upload-panel-content {\n display: flex;\n flex-direction: column;\n}\n\n.retry-bar {\n display: flex;\n justify-content: flex-end;\n padding: var(--kds-spacing-container-0-5x) var(--kds-spacing-container-0-75x);\n border-top: var(--kds-border-base-subtle);\n}\n\n.skeleton-container {\n display: grid;\n grid-template-columns: min-content 1fr;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-height: var(--kds-dimension-component-height-4x);\n padding: 0 var(--kds-spacing-container-1x);\n border-top: var(--kds-border-base-subtle);\n\n & .skeleton-text-content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport { KdsButton, KdsLoadingSkeleton } from \"@knime/kds-components\";\n\nimport { CollapsiblePanel, ProgressList } from \"../shared/ui\";\n\nimport UploadProgressPanelItem from \"./UploadProgressPanelItem.vue\";\nimport type { UploadItem, UploadProgressPanelProps } from \"./types\";\n\nconst {\n items,\n placeholderItems = 0,\n canRetry = false,\n} = defineProps<UploadProgressPanelProps>();\n\nconst emit = defineEmits<{\n cancel: [item: UploadItem];\n viewDetails: [item: UploadItem];\n retry: [];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => items.length);\nconst uploadedItems = computed(\n () => items.filter(({ status }) => status === \"complete\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `item${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasSomeItemInProgress = computed(\n () =>\n items.some(\n ({ status }) => status === \"inprogress\" || status === \"processing\",\n ) || placeholderItems > 0,\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"upload-panel\"\n :can-be-dismissed=\"!hasSomeItemInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"upload-panel-content\">\n <ProgressList>\n <UploadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.id\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @view-details=\"emit('viewDetails', item)\"\n />\n </ProgressList>\n\n <template v-if=\"placeholderItems > 0\">\n <div\n v-for=\"skeleton in placeholderItems\"\n :key=\"skeleton\"\n class=\"skeleton-container\"\n >\n <KdsLoadingSkeleton variant=\"icon-large\" />\n <div class=\"skeleton-text-content\">\n <KdsLoadingSkeleton variant=\"label\" />\n <KdsLoadingSkeleton variant=\"text\" />\n </div>\n </div>\n </template>\n\n <div v-if=\"canRetry && !hasSomeItemInProgress\" class=\"retry-bar\">\n <KdsButton\n size=\"small\"\n variant=\"outlined\"\n label=\"Retry failed items\"\n leading-icon=\"reload\"\n @click=\"emit('retry')\"\n />\n </div>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.upload-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.upload-panel-content {\n display: flex;\n flex-direction: column;\n}\n\n.retry-bar {\n display: flex;\n justify-content: flex-end;\n padding: var(--kds-spacing-container-0-5x) var(--kds-spacing-container-0-75x);\n border-top: var(--kds-border-base-subtle);\n}\n\n.skeleton-container {\n display: grid;\n grid-template-columns: min-content 1fr;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-height: var(--kds-dimension-component-height-4x);\n padding: 0 var(--kds-spacing-container-1x);\n border-top: var(--kds-border-base-subtle);\n\n & .skeleton-text-content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n }\n}\n</style>\n","import { computed, ref } from \"vue\";\n\nimport { type ChunkUploadManagerConfig, createUploadManager } from \"../core\";\n\nimport type { FileUploadItem, UploadItem } from \"./types\";\n\ntype OnCompletePayload = Parameters<\n NonNullable<ChunkUploadManagerConfig[\"onFileUploadComplete\"]>\n>[0] & { parentId: string; name: string };\n\ntype OnFailedPayload = Parameters<\n NonNullable<ChunkUploadManagerConfig[\"onFileUploadFailed\"]>\n>[0] & { parentId: string };\n\ntype UseUploadManagerOptions = Omit<\n ChunkUploadManagerConfig,\n \"onProgress\" | \"onFileUploadComplete\" | \"onFileUploadFailed\"\n> & {\n onFileUploadComplete?: (payload: OnCompletePayload) => void;\n onFileUploadFailed?: (payload: OnFailedPayload) => void;\n /**\n * Determines the status to assign a file once its raw upload finishes,\n * before calling `onFileUploadComplete`. This helps for files that need further\n * server-side processing\n *\n * Defaults to `complete`\n */\n resolveCompleteStatus?: (\n payload: OnCompletePayload,\n ) => \"complete\" | \"processing\";\n};\n\n/**\n * This composable wraps the `uploadManager` utility\n * in order to provide reactive state which will work tightly with the `UploadProgressPanel` component\n *\n * The reactive state returned (`uploadItems`) will update whenever the progress\n * or status of each item being uploaded changes\n */\nexport const useUploadManager = (options: UseUploadManagerOptions) => {\n const { resolveCompleteStatus, ...restOptions } = options;\n const uploadState = ref<Record<string, UploadItem>>({});\n\n const { uploadFiles, cancel, setFailed } = createUploadManager({\n ...restOptions,\n\n onFileUploadComplete: (params) => {\n const { parentId, name } = uploadState.value[params.uploadId];\n const payload = { ...params, parentId, name };\n uploadState.value[params.uploadId].status =\n resolveCompleteStatus?.(payload) ?? \"complete\";\n options.onFileUploadComplete?.(payload);\n },\n\n onFileUploadFailed: ({ uploadId, error }) => {\n const { parentId } = uploadState.value[uploadId];\n uploadState.value[uploadId] = {\n ...uploadState.value[uploadId],\n status: \"failed\",\n failureDetails: error.message,\n };\n options.onFileUploadFailed?.({ uploadId, error, parentId });\n },\n\n onProgress: (uploadId, progress) => {\n if (uploadState.value[uploadId].status === \"inprogress\") {\n (uploadState.value[uploadId] as FileUploadItem).progress = progress;\n }\n },\n });\n\n const start = async (\n parentId: string,\n uploadPayload: Array<{ uploadId: string; file: File }>,\n ) => {\n try {\n uploadPayload.forEach(({ uploadId, file }) => {\n uploadState.value[uploadId] = {\n id: uploadId,\n name: file.name,\n size: file.size,\n progress: 0,\n type: \"file\",\n status: \"inprogress\",\n parentId,\n };\n });\n\n return await uploadFiles(uploadPayload);\n } catch (error) {\n consola.error(\"Error uploading files\", error);\n return [];\n }\n };\n\n const canCancel = (uploadId: string) =>\n uploadState.value[uploadId]?.status === \"inprogress\" ||\n uploadState.value[uploadId]?.status === \"failed\";\n\n const removeItem = (uploadId: string) => {\n if (canCancel(uploadId)) {\n cancel(uploadId);\n }\n\n delete uploadState.value[uploadId];\n };\n\n const resetState = () => {\n uploadState.value = {};\n };\n\n const uploadItems = computed(() => Object.values(uploadState.value));\n\n const hasPendingUploads = computed(() =>\n uploadItems.value.some(({ status }) => status === \"inprogress\"),\n );\n\n const totalPendingUploads = computed(\n () =>\n uploadItems.value.filter(({ status }) => status === \"inprogress\").length,\n );\n\n return {\n uploadState,\n hasPendingUploads,\n totalPendingUploads,\n start,\n cancel: (uploadId: string) => {\n cancel(uploadId);\n uploadState.value[uploadId].status = \"cancelled\";\n },\n canCancel,\n removeItem,\n resetState,\n setFailed: (uploadId: string, error: Error) => {\n uploadState.value[uploadId] = {\n ...uploadState.value[uploadId],\n status: \"failed\",\n failureDetails: error.message,\n };\n setFailed(uploadId);\n const { parentId } = uploadState.value[uploadId];\n options.onFileUploadFailed?.({ parentId, uploadId, error });\n },\n };\n};\n","import { computed, ref } from \"vue\";\nimport type { FetchError, FetchOptions } from \"ofetch\";\n\nimport {\n formatFileSize,\n getFileExtension,\n knimeFileFormats,\n promise,\n} from \"@knime/utils\";\n\nimport { rfcErrors } from \"../../rfcErrors\";\nimport { createUploadClient, createUploadStatusPoller } from \"../core\";\n\nimport type { UploadItem } from \"./types\";\nimport { useUploadManager } from \"./useUploadManager\";\n\nconst DEFAULT_MAX_UPLOAD_QUEUE_SIZE = 10;\n// eslint-disable-next-line no-magic-numbers\nconst DEFAULT_UPLOAD_SIZE_LIMIT_BYTES = 5 * 1024 * 1024 * 1024;\n\nconst DEFAULT_RETRY_DELAY_MS = 50;\n\n// Catalog & artifacts services currently have a limit for 5 GiB for\n// upload and copy/move operations but it is not communicated to clients.\n// Repeat the constant here to be able to still make a pre-check. See NXT-4510.\nconst getUploadSizeLimitBytes = () => DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;\n\n/**\n * Renames any File whose name repeats an earlier one in the list, appending\n * a \" (n)\" suffix (before the extension, if any) - including a name produced\n * by this same disambiguation, so a literal \"name (1).ext\" already in the\n * list can't collide with a generated one\n */\nconst disambiguateFiles = (files: File[]): File[] => {\n const usedNames = new Set<string>();\n\n return files.map((file) => {\n if (!usedNames.has(file.name)) {\n usedNames.add(file.name);\n return file;\n }\n\n const extension = getFileExtension(file.name);\n const base = extension\n ? file.name.slice(0, -(extension.length + 1))\n : file.name;\n\n let counter = 1;\n let candidate = extension\n ? `${base} (${counter}).${extension}`\n : `${base} (${counter})`;\n\n while (usedNames.has(candidate)) {\n counter += 1;\n candidate = extension\n ? `${base} (${counter}).${extension}`\n : `${base} (${counter})`;\n }\n\n usedNames.add(candidate);\n return new File([file], candidate, {\n type: file.type,\n lastModified: file.lastModified,\n });\n });\n};\n\ntype UseFileUploadOptions = {\n /**\n * Max number of concurrent uploads allowed in the upload queue.\n * When an upload is started with a number of files that exceed the pending\n * uploads then only files up-to the max queue size will be processed and the rest\n * will be ignored. This will also call the `onUploadQueueSizeExceeded` callback\n */\n maxUploadQueueSize?: number;\n /**\n * Callback that triggers every time any file in the queue completes its upload\n * @returns\n */\n onFileUploadComplete?: (payload: {\n uploadId: string;\n filePartIds: Record<number, string>;\n parentId: string;\n name: string;\n }) => void;\n /**\n * Callback that triggers every time any file in the queue fails its upload\n * @returns\n */\n onFileUploadFailed?: (payload: {\n uploadId: string;\n error: Error;\n parentId: string;\n }) => void;\n /**\n * Callback that triggers when a upload batch is started (via the `start` function) but the amount\n * of items to upload exceed the max configured size of the queue.\n * @returns\n */\n onUploadQueueSizeExceeded?: (maxQueueSize: number) => void;\n /**\n * Options to customize the fetch client used internally to make http requests.\n * e.g: headers, baseUrl, etc\n */\n customFetchClientOptions?: FetchOptions;\n\n /**\n * Whether the manifest endpoint is flat (`/manifest`) rather than nested\n * under a repository item (`/repository/{parentId}/manifest`). Used for\n * targets whose upload endpoint isn't scoped by a repository parent,\n * e.g. environment extension DB driver uploads.\n */\n shouldUseFlatManifestPath?: boolean;\n\n isFileWithProcessing?: (fileName: string) => boolean;\n};\n\n/**\n * This composable handles uploads of data to a hub repository item (space or workflow group).\n * It is a multi-step process which is described as follows:\n *\n * Step 1:\n * First we need to prepare an upload. The items will be uploaded to a `parent` container\n * identified by its id. (When `shouldUseFlatManifestPath` is set, the manifest endpoint isn't\n * scoped by a parent, so the `parentId` passed to `start` is unused for that request.)\n * Then, for each file we want to upload, we must provide paths relative\n * to the parent that identify each file. Example:\n * ```\n * {\n * \"items\": {\n * \"my-file.txt\": { itemContentType: \"text/plain\", itemContentSize: 5000 },\n * \"folder/my-file.zip\": { itemContentType: \"application/zip\", itemContentSize: 5000 },\n * }\n * }\n * ```\n * This describes 2 items, one placed in the parent's root and another inside a folder.\n * The response for this would look like:\n * ```\n * {\n * \"items\": {\n * \"my-file.txt\": { uploadId: \"id1\" },\n * \"folder/my-file.zip\": { uploadId: \"id2\" },\n * }\n * }\n * ```\n * Now, each corresponding `uploadId` uniquely identifies the upload process for\n * their respective files.\n *\n * Step 2:\n * Next, we need to start uploading the file parts. Technically, you _can_ just upload\n * each file as one big file \"part\", but practically that is not recommended because a hiccup\n * in the connection means you would have to start the upload from scratch; instead, we split\n * the files in smaller parts.\n * The upload manager helper will take care of splitting the file. However, the responsibility\n * of this composable is to supply a function to resolve the upload URL. This upload URL is obtained\n * by POSTing to an endpoint with the `uploadId` and the `partNumber` and this will return\n * the data required to make an upload to a presigned URL; this will be called for each file part.\n *\n * Step 3:\n * Completion:\n * Once the upload of a single file is complete, the upload manager will trigger the `onFileUploadComplete`\n * callback, which then will supply the ids of all the different parts that got uploaded for a given `uploadId`\n * Then we need to \"complete\" the upload by sending a request which provides the `uploadId` and a list of\n * the ids of all the parts. Note that some backends don't return the ids of the file parts after a successful\n * upload of a part, in this case empty strings or null can be used instead of the part id.\n *\n * Cancellation:\n * Alternative to completion, you can also cancel an upload. For this, the composable makes a DELETE request\n * providing the corresponding `uploadId` which will effectively cancel it.\n *\n * For a usage example, refer to the Docs page in storybook.\n */\nexport const useFileUpload = (options: UseFileUploadOptions = {}) => {\n const uploadClient = createUploadClient({\n customFetchClientOptions: options.customFetchClientOptions,\n shouldUseFlatManifestPath: options.shouldUseFlatManifestPath,\n });\n\n let useUploadManagerResult: ReturnType<typeof useUploadManager> | null = null;\n\n const getItemFromQueue = (uploadId: string) => {\n const { uploadState } = useUploadManagerResult!;\n\n if (!uploadState.value[uploadId]) {\n consola.warn(\n `Cannot get item in queue. Upload id \"${uploadId}\" doesn't exist`,\n );\n return undefined;\n }\n\n return uploadState.value[uploadId];\n };\n\n const updateItemInQueue = (uploadId: string, data: Partial<UploadItem>) => {\n const { uploadState } = useUploadManagerResult!;\n\n if (!uploadState.value[uploadId]) {\n consola.warn(\n `Cannot update item in queue. Upload id \"${uploadId}\" doesn't exist`,\n );\n return;\n }\n\n uploadState.value = {\n ...uploadState.value,\n [uploadId]: {\n ...uploadState.value[uploadId],\n ...data,\n } as UploadItem,\n };\n };\n\n const statusPoller = createUploadStatusPoller(uploadClient, {\n onComplete: (uploadId) =>\n updateItemInQueue(uploadId, { status: \"complete\" }),\n\n onFail: (uploadId, statusMessage) =>\n updateItemInQueue(uploadId, {\n status: \"failed\",\n failureDetails: statusMessage,\n }),\n });\n\n useUploadManagerResult = useUploadManager({\n resolveFilePartUploadURL: uploadClient.resolveFilePartUploadURL,\n\n resolveCompleteStatus: ({ name }) =>\n options.isFileWithProcessing?.(name) ? \"processing\" : \"complete\",\n\n onFileUploadComplete: ({ uploadId, filePartIds, name, parentId }) => {\n promise\n .retryPromise({\n fn: () => uploadClient.completeUpload(uploadId, filePartIds),\n })\n .then(() => {\n if (options.isFileWithProcessing?.(name)) {\n statusPoller.track(uploadId);\n }\n\n options.onFileUploadComplete?.({\n uploadId,\n filePartIds,\n name,\n parentId,\n });\n })\n .catch((error) => {\n consola.error(\"Error attempting to complete upload\", { error });\n useUploadManagerResult!.setFailed(uploadId, error);\n });\n },\n\n onFileUploadFailed: ({ uploadId, error, parentId }) => {\n options?.onFileUploadFailed?.({ uploadId, error, parentId });\n },\n });\n\n const getEnqueueableFiles = (files: File[]): File[] => {\n const { totalPendingUploads } = useUploadManagerResult;\n\n const { maxUploadQueueSize = DEFAULT_MAX_UPLOAD_QUEUE_SIZE } = options;\n\n if (files.length + totalPendingUploads.value > maxUploadQueueSize) {\n options.onUploadQueueSizeExceeded?.(maxUploadQueueSize);\n }\n\n // only keep as many files as the queue allows\n const effectiveQueueSize = Math.max(\n 0,\n maxUploadQueueSize - totalPendingUploads.value,\n );\n return files.slice(0, effectiveQueueSize);\n };\n\n const { uploadState, ...uploadManagerResultRest } = useUploadManagerResult;\n\n const prepareQueueSize = ref(0);\n\n const uploadQueue = computed<UploadItem[]>(() =>\n Object.values(uploadState.value),\n );\n\n return {\n ...uploadManagerResultRest,\n\n uploadQueue,\n getItemFromQueue,\n updateItemInQueue,\n\n isPreparingUpload: computed(() => prepareQueueSize.value > 0),\n totalFilesBeingPrepared: computed(() => prepareQueueSize.value),\n\n start: async (parentId: string, files: File[]): Promise<string[]> => {\n const enqueueableFiles = getEnqueueableFiles(files);\n const uploadSizeLimitBytes = getUploadSizeLimitBytes();\n const oversizedFiles = enqueueableFiles.filter(\n (file) => file.size > uploadSizeLimitBytes,\n );\n\n if (oversizedFiles.length > 0) {\n throw new rfcErrors.RFCError({\n title: \"Upload size limit exceeded\",\n status: 413,\n details: oversizedFiles.map(\n (file) =>\n `${file.name} (${formatFileSize(file.size)}) exceeds the upload limit of ${formatFileSize(uploadSizeLimitBytes)}.`,\n ),\n });\n }\n\n try {\n if (enqueueableFiles.length === 0) {\n return [];\n }\n\n prepareQueueSize.value += enqueueableFiles.length;\n\n const disambiguatedFiles = disambiguateFiles(enqueueableFiles);\n\n const items = disambiguatedFiles.map((file) => ({\n // strip extension for workflows. otherwise, the extension gets baked into the name\n // of the uploaded file and the backend appends an extra extension.\n // unnecessary for other file types\n path: knimeFileFormats.KNWF.getNameOrDefault(file, file.name),\n file,\n }));\n\n const uploadPayload = await promise.retryPromise({\n fn: () => uploadClient.prepareUpload(parentId, items),\n excludeError: (error: FetchError) =>\n // eslint-disable-next-line no-magic-numbers\n Boolean(error.statusCode && error.statusCode < 500),\n retryDelayMS: DEFAULT_RETRY_DELAY_MS,\n });\n\n const uploadIdByFile = new Map(\n uploadPayload.map((upload) => [upload.file, upload.uploadId]),\n );\n const uploadIds = disambiguatedFiles.map((file) =>\n uploadIdByFile.get(file)!,\n );\n\n // accept floating promise, errors are handled inside the function\n useUploadManagerResult.start(parentId, uploadPayload);\n\n return uploadIds;\n } catch (error) {\n throw rfcErrors.tryParse(error) ?? error;\n } finally {\n // errors can only be thrown in the prepareUpload call, useUploadManagerResult.start does its own error handling\n prepareQueueSize.value -= enqueueableFiles.length;\n }\n },\n\n cancel: (uploadId: string) => {\n useUploadManagerResult.cancel(uploadId);\n uploadClient.cancelUpload(uploadId).catch((error: unknown) => {\n consola.error(\"There was a problem cancelling the upload\", { error });\n });\n },\n };\n};\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
|
|
2
2
|
import { n as formatDateTimeString } from "./dist-DvDWkkNg.js";
|
|
3
3
|
import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, createPropsRestProxy, createTextVNode, defineComponent, h, openBlock, ref, renderList, toDisplayString, unref } from "vue";
|
|
4
|
-
import { useClipboard } from "@vueuse/core";
|
|
5
4
|
import { KdsButton } from "@knime/kds-components";
|
|
5
|
+
import { useClipboard } from "@vueuse/core";
|
|
6
6
|
import './rfcErrors.css';//#region src/rfcErrors/serializeRFCErrorData.ts
|
|
7
7
|
var serializeErrorForClipboard = (error, headline, formatDate) => {
|
|
8
8
|
let details = "";
|
|
@@ -169,4 +169,4 @@ var rfcErrors = {
|
|
|
169
169
|
//#endregion
|
|
170
170
|
export { rfcErrors as t };
|
|
171
171
|
|
|
172
|
-
//# sourceMappingURL=rfcErrors-
|
|
172
|
+
//# sourceMappingURL=rfcErrors-CUq6KIxS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rfcErrors-B2gYd5aG.js","names":[],"sources":["../src/rfcErrors/serializeRFCErrorData.ts","../src/rfcErrors/RFCErrorToastTemplate.vue","../src/rfcErrors/RFCErrorToastTemplate.vue","../src/rfcErrors/types.ts","../src/rfcErrors/index.ts"],"sourcesContent":["import type { RFCErrorData } from \"./types\";\n\nexport const serializeErrorForClipboard = (\n error: RFCErrorData,\n headline: string,\n formatDate: (date: Date) => string,\n): string => {\n let details = \"\";\n if (error.details?.length) {\n if (error.details.length > 1) {\n const detailLines = error.details\n .map((item) => `\\u2022 ${item}`)\n .join(\"\\n\");\n details = `\\n${detailLines}`;\n } else {\n details = error.details[0];\n }\n }\n\n let errorText = `${headline}\\n\\n`;\n errorText += `${error.title}\\n\\n`;\n errorText += details ? `Details: ${details}\\n\\n` : \"\";\n\n if (error.status !== undefined) {\n errorText += `Status: ${error.status}\\n`;\n }\n\n if (error.date) {\n errorText += `Date: ${formatDate(error.date)}\\n`;\n }\n\n if (error.requestId) {\n errorText += `Request Id: ${error.requestId}\\n`;\n }\n\n if (error.errorId) {\n errorText += `Error Id: ${error.errorId}\\n`;\n }\n\n if (error.stacktrace) {\n errorText += \"------\\n\"; // separator\n errorText += `Stacktrace:\\n\\n${error.stacktrace}\\n`;\n }\n\n return errorText;\n};\n","<script setup lang=\"ts\">\nimport { ref } from \"vue\";\nimport { useClipboard } from \"@vueuse/core\";\n\nimport { KdsButton } from \"@knime/kds-components\";\nimport { formatDateTimeString } from \"@knime/utils\";\n\nimport { serializeErrorForClipboard as defaultSerializeRFCErrorData } from \"./serializeRFCErrorData\";\nimport type { RFCErrorData } from \"./types\";\n\ntype Props = {\n /**\n * Toast headline shown in the toast title bar. Not rendered inside this\n * template, but included when serializing the error to the clipboard.\n */\n headline: string;\n /** Whether to show the \"Copy error to clipboard\" button. */\n canCopyToClipboard?: boolean;\n /**\n * Custom serializer for the clipboard payload. Defaults to\n * `serializeRFCErrorData` which formats the full error as text.\n */\n serializeErrorForClipboard?: (\n error: RFCErrorData,\n headline: string,\n formatDate: (date: Date) => string,\n ) => string;\n} & RFCErrorData;\n\nconst {\n headline,\n canCopyToClipboard = true,\n serializeErrorForClipboard = defaultSerializeRFCErrorData,\n ...rfcError\n} = defineProps<Props>();\n\nconst emits = defineEmits([\"showMore\"]);\n\nconst { copy, copied } = useClipboard({\n copiedDuring: 3000,\n});\n\nconst hasDetails =\n rfcError.details?.length ||\n rfcError.status !== undefined ||\n rfcError.date !== undefined ||\n rfcError.requestId !== undefined ||\n rfcError.errorId !== undefined;\n// don't show 'Show details' button if there are no details to show\nconst showDetails = ref(!hasDetails);\n\nconst formatDate = (date: Date): string =>\n date ? formatDateTimeString(date.getTime()) : \"\";\n\nconst copyToClipboard = () => {\n if (copied.value) {\n return;\n }\n copy(serializeErrorForClipboard(rfcError, headline, formatDate)).catch(\n (error) => {\n consola.error(\"Failed to copy to clipboard\", { error });\n },\n );\n};\n\nconst onShowDetailsClicked = () => {\n showDetails.value = true;\n emits(\"showMore\");\n};\n</script>\n\n<template>\n <div class=\"wrapper\">\n <div class=\"title\">\n {{ title }}\n </div>\n <div v-if=\"showDetails\">\n <div v-if=\"details?.length\">\n <strong>Details: </strong>\n <template v-if=\"details.length === 1\">\n {{ details[0] }}\n </template>\n <template v-else>\n <ul class=\"details-list\">\n <li v-for=\"(item, index) in details\" :key=\"index\">{{ item }}</li>\n </ul>\n </template>\n </div>\n\n <div v-if=\"status !== undefined\">\n <strong>Status: </strong>{{ status }}\n </div>\n <div v-if=\"date\"><strong>Date: </strong>{{ formatDate(date) }}</div>\n <div v-if=\"requestId\"><strong>Request id: </strong>{{ requestId }}</div>\n <div v-if=\"errorId\"><strong>Error id: </strong>{{ errorId }}</div>\n <div v-if=\"stacktrace && canCopyToClipboard\">\n <strong>Stacktrace: </strong>Part of clipboard text\n </div>\n </div>\n <div class=\"actions-wrapper\">\n <KdsButton\n v-if=\"!showDetails\"\n data-testid=\"show-details\"\n variant=\"transparent\"\n label=\"Show details\"\n size=\"xsmall\"\n @click=\"onShowDetailsClicked\"\n />\n <KdsButton\n v-if=\"canCopyToClipboard\"\n data-testid=\"copy-to-clipboard\"\n :label=\"copied ? 'Error was copied' : 'Copy error to clipboard'\"\n :leading-icon=\"copied ? 'checkmark' : 'copy'\"\n variant=\"transparent\"\n size=\"xsmall\"\n @click=\"copyToClipboard\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n}\n\n.details-list {\n padding-left: var(--kds-spacing-container-1-5x);\n margin-top: var(--kds-spacing-container-0-37x);\n margin-bottom: var(--kds-spacing-container-0-37x);\n}\n\n.actions-wrapper {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n align-items: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref } from \"vue\";\nimport { useClipboard } from \"@vueuse/core\";\n\nimport { KdsButton } from \"@knime/kds-components\";\nimport { formatDateTimeString } from \"@knime/utils\";\n\nimport { serializeErrorForClipboard as defaultSerializeRFCErrorData } from \"./serializeRFCErrorData\";\nimport type { RFCErrorData } from \"./types\";\n\ntype Props = {\n /**\n * Toast headline shown in the toast title bar. Not rendered inside this\n * template, but included when serializing the error to the clipboard.\n */\n headline: string;\n /** Whether to show the \"Copy error to clipboard\" button. */\n canCopyToClipboard?: boolean;\n /**\n * Custom serializer for the clipboard payload. Defaults to\n * `serializeRFCErrorData` which formats the full error as text.\n */\n serializeErrorForClipboard?: (\n error: RFCErrorData,\n headline: string,\n formatDate: (date: Date) => string,\n ) => string;\n} & RFCErrorData;\n\nconst {\n headline,\n canCopyToClipboard = true,\n serializeErrorForClipboard = defaultSerializeRFCErrorData,\n ...rfcError\n} = defineProps<Props>();\n\nconst emits = defineEmits([\"showMore\"]);\n\nconst { copy, copied } = useClipboard({\n copiedDuring: 3000,\n});\n\nconst hasDetails =\n rfcError.details?.length ||\n rfcError.status !== undefined ||\n rfcError.date !== undefined ||\n rfcError.requestId !== undefined ||\n rfcError.errorId !== undefined;\n// don't show 'Show details' button if there are no details to show\nconst showDetails = ref(!hasDetails);\n\nconst formatDate = (date: Date): string =>\n date ? formatDateTimeString(date.getTime()) : \"\";\n\nconst copyToClipboard = () => {\n if (copied.value) {\n return;\n }\n copy(serializeErrorForClipboard(rfcError, headline, formatDate)).catch(\n (error) => {\n consola.error(\"Failed to copy to clipboard\", { error });\n },\n );\n};\n\nconst onShowDetailsClicked = () => {\n showDetails.value = true;\n emits(\"showMore\");\n};\n</script>\n\n<template>\n <div class=\"wrapper\">\n <div class=\"title\">\n {{ title }}\n </div>\n <div v-if=\"showDetails\">\n <div v-if=\"details?.length\">\n <strong>Details: </strong>\n <template v-if=\"details.length === 1\">\n {{ details[0] }}\n </template>\n <template v-else>\n <ul class=\"details-list\">\n <li v-for=\"(item, index) in details\" :key=\"index\">{{ item }}</li>\n </ul>\n </template>\n </div>\n\n <div v-if=\"status !== undefined\">\n <strong>Status: </strong>{{ status }}\n </div>\n <div v-if=\"date\"><strong>Date: </strong>{{ formatDate(date) }}</div>\n <div v-if=\"requestId\"><strong>Request id: </strong>{{ requestId }}</div>\n <div v-if=\"errorId\"><strong>Error id: </strong>{{ errorId }}</div>\n <div v-if=\"stacktrace && canCopyToClipboard\">\n <strong>Stacktrace: </strong>Part of clipboard text\n </div>\n </div>\n <div class=\"actions-wrapper\">\n <KdsButton\n v-if=\"!showDetails\"\n data-testid=\"show-details\"\n variant=\"transparent\"\n label=\"Show details\"\n size=\"xsmall\"\n @click=\"onShowDetailsClicked\"\n />\n <KdsButton\n v-if=\"canCopyToClipboard\"\n data-testid=\"copy-to-clipboard\"\n :label=\"copied ? 'Error was copied' : 'Copy error to clipboard'\"\n :leading-icon=\"copied ? 'checkmark' : 'copy'\"\n variant=\"transparent\"\n size=\"xsmall\"\n @click=\"copyToClipboard\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n}\n\n.details-list {\n padding-left: var(--kds-spacing-container-1-5x);\n margin-top: var(--kds-spacing-container-0-37x);\n margin-bottom: var(--kds-spacing-container-0-37x);\n}\n\n.actions-wrapper {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n align-items: center;\n}\n</style>\n","/**\n * Type based on an RFC-9457 error standard (https://www.rfc-editor.org/rfc/rfc9457)\n * Also see:\n * https://knime-com.atlassian.net/wiki/spaces/SPECS/pages/4126769212/API+Errors+Problem+Details\n */\nexport type RFCErrorData = {\n /** Short human-readable summary of the error. */\n title: string;\n /** HTTP status code associated with the error. */\n status?: number;\n /** Date/time when the error occurred. */\n date?: Date;\n /** Additional human-readable detail messages. */\n details?: string[];\n /** Correlation id for the request that triggered the error. */\n requestId?: string;\n /**\n * Id used to find an error in the Hub logs. Specially relevant\n * when details of the error are not wanted to be exposed to the user.\n */\n errorId?: string;\n /**\n * Relevant only for the Analytics Platform, since the Hub does not expose\n * stack traces.\n */\n stacktrace?: string;\n};\n\nexport class RFCError extends Error {\n data: RFCErrorData;\n\n constructor(data: RFCErrorData) {\n super(data.title);\n this.data = data;\n }\n}\n\nexport type FetchError = {\n response: {\n headers: Headers;\n };\n statusCode: number;\n data: {\n title: string;\n details: string[];\n };\n};\n","import { h } from \"vue\";\n\nimport type { Toast } from \"../toasts\";\n\nimport RFCErrorToastTemplate from \"./RFCErrorToastTemplate.vue\";\nimport { type FetchError, RFCError, type RFCErrorData } from \"./types\";\n\n/**\n * Map to a toast template component compatible with an RFCError format\n */\nconst toToast = ({\n headline,\n rfcError,\n canCopyToClipboard = true,\n serializeErrorForClipboard = undefined,\n}: {\n headline: string;\n rfcError: RFCError;\n canCopyToClipboard?: boolean;\n serializeErrorForClipboard?: (\n error: RFCErrorData,\n headline: string,\n formatDate: (date: Date) => string,\n ) => string;\n}): Toast => {\n const { data } = rfcError;\n\n const rfcErrorToastContent = h(RFCErrorToastTemplate, {\n headline,\n ...data,\n canCopyToClipboard,\n serializeErrorForClipboard,\n });\n\n return {\n type: \"error\",\n headline,\n component: rfcErrorToastContent,\n autoRemove: false,\n };\n};\n\nconst isFetchError = (error: unknown): error is FetchError => {\n return (\n error !== null &&\n typeof error === \"object\" &&\n \"response\" in error &&\n \"statusCode\" in error &&\n \"data\" in error\n );\n};\n\n/**\n * Parse a generic request error to an `RFCError`.\n * When parsing is not possible it returns undefined.\n */\nconst tryParse = (error: unknown): RFCError | undefined => {\n if (!isFetchError(error)) {\n return undefined;\n }\n\n if (!error.response) {\n return undefined;\n }\n\n const responseDate = error.response.headers.get(\"date\")\n ? new Date(error.response.headers.get(\"date\")!)\n : undefined;\n\n const title =\n typeof error.data === \"string\" ? error.data : (error.data?.title ?? \"\");\n\n const rfcErrorData: RFCErrorData = {\n title: title as string,\n details: error.data?.details as string[] | undefined,\n status: error.statusCode as number,\n date: responseDate,\n requestId: error.response.headers.get(\"x-request-id\") ?? undefined,\n errorId: error.response.headers.get(\"x-error-id\") ?? undefined,\n };\n\n return new RFCError(rfcErrorData);\n};\n\nexport const rfcErrors = {\n toToast,\n tryParse,\n RFCError,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rfcErrors-CUq6KIxS.js","names":[],"sources":["../src/rfcErrors/serializeRFCErrorData.ts","../src/rfcErrors/RFCErrorToastTemplate.vue","../src/rfcErrors/RFCErrorToastTemplate.vue","../src/rfcErrors/types.ts","../src/rfcErrors/index.ts"],"sourcesContent":["import type { RFCErrorData } from \"./types\";\n\nexport const serializeErrorForClipboard = (\n error: RFCErrorData,\n headline: string,\n formatDate: (date: Date) => string,\n): string => {\n let details = \"\";\n if (error.details?.length) {\n if (error.details.length > 1) {\n const detailLines = error.details\n .map((item) => `\\u2022 ${item}`)\n .join(\"\\n\");\n details = `\\n${detailLines}`;\n } else {\n details = error.details[0];\n }\n }\n\n let errorText = `${headline}\\n\\n`;\n errorText += `${error.title}\\n\\n`;\n errorText += details ? `Details: ${details}\\n\\n` : \"\";\n\n if (error.status !== undefined) {\n errorText += `Status: ${error.status}\\n`;\n }\n\n if (error.date) {\n errorText += `Date: ${formatDate(error.date)}\\n`;\n }\n\n if (error.requestId) {\n errorText += `Request Id: ${error.requestId}\\n`;\n }\n\n if (error.errorId) {\n errorText += `Error Id: ${error.errorId}\\n`;\n }\n\n if (error.stacktrace) {\n errorText += \"------\\n\"; // separator\n errorText += `Stacktrace:\\n\\n${error.stacktrace}\\n`;\n }\n\n return errorText;\n};\n","<script setup lang=\"ts\">\nimport { ref } from \"vue\";\nimport { useClipboard } from \"@vueuse/core\";\n\nimport { KdsButton } from \"@knime/kds-components\";\nimport { formatDateTimeString } from \"@knime/utils\";\n\nimport { serializeErrorForClipboard as defaultSerializeRFCErrorData } from \"./serializeRFCErrorData\";\nimport type { RFCErrorData } from \"./types\";\n\ntype Props = {\n /**\n * Toast headline shown in the toast title bar. Not rendered inside this\n * template, but included when serializing the error to the clipboard.\n */\n headline: string;\n /** Whether to show the \"Copy error to clipboard\" button. */\n canCopyToClipboard?: boolean;\n /**\n * Custom serializer for the clipboard payload. Defaults to\n * `serializeRFCErrorData` which formats the full error as text.\n */\n serializeErrorForClipboard?: (\n error: RFCErrorData,\n headline: string,\n formatDate: (date: Date) => string,\n ) => string;\n} & RFCErrorData;\n\nconst {\n headline,\n canCopyToClipboard = true,\n serializeErrorForClipboard = defaultSerializeRFCErrorData,\n ...rfcError\n} = defineProps<Props>();\n\nconst emits = defineEmits([\"showMore\"]);\n\nconst { copy, copied } = useClipboard({\n copiedDuring: 3000,\n});\n\nconst hasDetails =\n rfcError.details?.length ||\n rfcError.status !== undefined ||\n rfcError.date !== undefined ||\n rfcError.requestId !== undefined ||\n rfcError.errorId !== undefined;\n// don't show 'Show details' button if there are no details to show\nconst showDetails = ref(!hasDetails);\n\nconst formatDate = (date: Date): string =>\n date ? formatDateTimeString(date.getTime()) : \"\";\n\nconst copyToClipboard = () => {\n if (copied.value) {\n return;\n }\n copy(serializeErrorForClipboard(rfcError, headline, formatDate)).catch(\n (error) => {\n consola.error(\"Failed to copy to clipboard\", { error });\n },\n );\n};\n\nconst onShowDetailsClicked = () => {\n showDetails.value = true;\n emits(\"showMore\");\n};\n</script>\n\n<template>\n <div class=\"wrapper\">\n <div class=\"title\">\n {{ title }}\n </div>\n <div v-if=\"showDetails\">\n <div v-if=\"details?.length\">\n <strong>Details: </strong>\n <template v-if=\"details.length === 1\">\n {{ details[0] }}\n </template>\n <template v-else>\n <ul class=\"details-list\">\n <li v-for=\"(item, index) in details\" :key=\"index\">{{ item }}</li>\n </ul>\n </template>\n </div>\n\n <div v-if=\"status !== undefined\">\n <strong>Status: </strong>{{ status }}\n </div>\n <div v-if=\"date\"><strong>Date: </strong>{{ formatDate(date) }}</div>\n <div v-if=\"requestId\"><strong>Request id: </strong>{{ requestId }}</div>\n <div v-if=\"errorId\"><strong>Error id: </strong>{{ errorId }}</div>\n <div v-if=\"stacktrace && canCopyToClipboard\">\n <strong>Stacktrace: </strong>Part of clipboard text\n </div>\n </div>\n <div class=\"actions-wrapper\">\n <KdsButton\n v-if=\"!showDetails\"\n data-testid=\"show-details\"\n variant=\"transparent\"\n label=\"Show details\"\n size=\"xsmall\"\n @click=\"onShowDetailsClicked\"\n />\n <KdsButton\n v-if=\"canCopyToClipboard\"\n data-testid=\"copy-to-clipboard\"\n :label=\"copied ? 'Error was copied' : 'Copy error to clipboard'\"\n :leading-icon=\"copied ? 'checkmark' : 'copy'\"\n variant=\"transparent\"\n size=\"xsmall\"\n @click=\"copyToClipboard\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n}\n\n.details-list {\n padding-left: var(--kds-spacing-container-1-5x);\n margin-top: var(--kds-spacing-container-0-37x);\n margin-bottom: var(--kds-spacing-container-0-37x);\n}\n\n.actions-wrapper {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n align-items: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref } from \"vue\";\nimport { useClipboard } from \"@vueuse/core\";\n\nimport { KdsButton } from \"@knime/kds-components\";\nimport { formatDateTimeString } from \"@knime/utils\";\n\nimport { serializeErrorForClipboard as defaultSerializeRFCErrorData } from \"./serializeRFCErrorData\";\nimport type { RFCErrorData } from \"./types\";\n\ntype Props = {\n /**\n * Toast headline shown in the toast title bar. Not rendered inside this\n * template, but included when serializing the error to the clipboard.\n */\n headline: string;\n /** Whether to show the \"Copy error to clipboard\" button. */\n canCopyToClipboard?: boolean;\n /**\n * Custom serializer for the clipboard payload. Defaults to\n * `serializeRFCErrorData` which formats the full error as text.\n */\n serializeErrorForClipboard?: (\n error: RFCErrorData,\n headline: string,\n formatDate: (date: Date) => string,\n ) => string;\n} & RFCErrorData;\n\nconst {\n headline,\n canCopyToClipboard = true,\n serializeErrorForClipboard = defaultSerializeRFCErrorData,\n ...rfcError\n} = defineProps<Props>();\n\nconst emits = defineEmits([\"showMore\"]);\n\nconst { copy, copied } = useClipboard({\n copiedDuring: 3000,\n});\n\nconst hasDetails =\n rfcError.details?.length ||\n rfcError.status !== undefined ||\n rfcError.date !== undefined ||\n rfcError.requestId !== undefined ||\n rfcError.errorId !== undefined;\n// don't show 'Show details' button if there are no details to show\nconst showDetails = ref(!hasDetails);\n\nconst formatDate = (date: Date): string =>\n date ? formatDateTimeString(date.getTime()) : \"\";\n\nconst copyToClipboard = () => {\n if (copied.value) {\n return;\n }\n copy(serializeErrorForClipboard(rfcError, headline, formatDate)).catch(\n (error) => {\n consola.error(\"Failed to copy to clipboard\", { error });\n },\n );\n};\n\nconst onShowDetailsClicked = () => {\n showDetails.value = true;\n emits(\"showMore\");\n};\n</script>\n\n<template>\n <div class=\"wrapper\">\n <div class=\"title\">\n {{ title }}\n </div>\n <div v-if=\"showDetails\">\n <div v-if=\"details?.length\">\n <strong>Details: </strong>\n <template v-if=\"details.length === 1\">\n {{ details[0] }}\n </template>\n <template v-else>\n <ul class=\"details-list\">\n <li v-for=\"(item, index) in details\" :key=\"index\">{{ item }}</li>\n </ul>\n </template>\n </div>\n\n <div v-if=\"status !== undefined\">\n <strong>Status: </strong>{{ status }}\n </div>\n <div v-if=\"date\"><strong>Date: </strong>{{ formatDate(date) }}</div>\n <div v-if=\"requestId\"><strong>Request id: </strong>{{ requestId }}</div>\n <div v-if=\"errorId\"><strong>Error id: </strong>{{ errorId }}</div>\n <div v-if=\"stacktrace && canCopyToClipboard\">\n <strong>Stacktrace: </strong>Part of clipboard text\n </div>\n </div>\n <div class=\"actions-wrapper\">\n <KdsButton\n v-if=\"!showDetails\"\n data-testid=\"show-details\"\n variant=\"transparent\"\n label=\"Show details\"\n size=\"xsmall\"\n @click=\"onShowDetailsClicked\"\n />\n <KdsButton\n v-if=\"canCopyToClipboard\"\n data-testid=\"copy-to-clipboard\"\n :label=\"copied ? 'Error was copied' : 'Copy error to clipboard'\"\n :leading-icon=\"copied ? 'checkmark' : 'copy'\"\n variant=\"transparent\"\n size=\"xsmall\"\n @click=\"copyToClipboard\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n font: var(--kds-font-base-body-small);\n color: var(--kds-color-text-and-icon-neutral);\n}\n\n.details-list {\n padding-left: var(--kds-spacing-container-1-5x);\n margin-top: var(--kds-spacing-container-0-37x);\n margin-bottom: var(--kds-spacing-container-0-37x);\n}\n\n.actions-wrapper {\n display: flex;\n gap: var(--kds-spacing-container-0-5x);\n align-items: center;\n}\n</style>\n","/**\n * Type based on an RFC-9457 error standard (https://www.rfc-editor.org/rfc/rfc9457)\n * Also see:\n * https://knime-com.atlassian.net/wiki/spaces/SPECS/pages/4126769212/API+Errors+Problem+Details\n */\nexport type RFCErrorData = {\n /** Short human-readable summary of the error. */\n title: string;\n /** HTTP status code associated with the error. */\n status?: number;\n /** Date/time when the error occurred. */\n date?: Date;\n /** Additional human-readable detail messages. */\n details?: string[];\n /** Correlation id for the request that triggered the error. */\n requestId?: string;\n /**\n * Id used to find an error in the Hub logs. Specially relevant\n * when details of the error are not wanted to be exposed to the user.\n */\n errorId?: string;\n /**\n * Relevant only for the Analytics Platform, since the Hub does not expose\n * stack traces.\n */\n stacktrace?: string;\n};\n\nexport class RFCError extends Error {\n data: RFCErrorData;\n\n constructor(data: RFCErrorData) {\n super(data.title);\n this.data = data;\n }\n}\n\nexport type FetchError = {\n response: {\n headers: Headers;\n };\n statusCode: number;\n data: {\n title: string;\n details: string[];\n };\n};\n","import { h } from \"vue\";\n\nimport type { Toast } from \"../toasts\";\n\nimport RFCErrorToastTemplate from \"./RFCErrorToastTemplate.vue\";\nimport { type FetchError, RFCError, type RFCErrorData } from \"./types\";\n\n/**\n * Map to a toast template component compatible with an RFCError format\n */\nconst toToast = ({\n headline,\n rfcError,\n canCopyToClipboard = true,\n serializeErrorForClipboard = undefined,\n}: {\n headline: string;\n rfcError: RFCError;\n canCopyToClipboard?: boolean;\n serializeErrorForClipboard?: (\n error: RFCErrorData,\n headline: string,\n formatDate: (date: Date) => string,\n ) => string;\n}): Toast => {\n const { data } = rfcError;\n\n const rfcErrorToastContent = h(RFCErrorToastTemplate, {\n headline,\n ...data,\n canCopyToClipboard,\n serializeErrorForClipboard,\n });\n\n return {\n type: \"error\",\n headline,\n component: rfcErrorToastContent,\n autoRemove: false,\n };\n};\n\nconst isFetchError = (error: unknown): error is FetchError => {\n return (\n error !== null &&\n typeof error === \"object\" &&\n \"response\" in error &&\n \"statusCode\" in error &&\n \"data\" in error\n );\n};\n\n/**\n * Parse a generic request error to an `RFCError`.\n * When parsing is not possible it returns undefined.\n */\nconst tryParse = (error: unknown): RFCError | undefined => {\n if (!isFetchError(error)) {\n return undefined;\n }\n\n if (!error.response) {\n return undefined;\n }\n\n const responseDate = error.response.headers.get(\"date\")\n ? new Date(error.response.headers.get(\"date\")!)\n : undefined;\n\n const title =\n typeof error.data === \"string\" ? error.data : (error.data?.title ?? \"\");\n\n const rfcErrorData: RFCErrorData = {\n title: title as string,\n details: error.data?.details as string[] | undefined,\n status: error.statusCode as number,\n date: responseDate,\n requestId: error.response.headers.get(\"x-request-id\") ?? undefined,\n errorId: error.response.headers.get(\"x-error-id\") ?? undefined,\n };\n\n return new RFCError(rfcErrorData);\n};\n\nexport const rfcErrors = {\n toToast,\n tryParse,\n RFCError,\n};\n"],"mappings":"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"}
|
package/dist/rfcErrors.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as rfcErrors } from "./rfcErrors-
|
|
1
|
+
import { t as rfcErrors } from "./rfcErrors-CUq6KIxS.js";
|
|
2
2
|
export { rfcErrors };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KdsPromptInput.vue.d.ts","sourceRoot":"","sources":["../../../../src/ai/KdsPromptInput/KdsPromptInput.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KdsPromptInput.vue.d.ts","sourceRoot":"","sources":["../../../../src/ai/KdsPromptInput/KdsPromptInput.vue"],"names":[],"mappings":"AA8RA,OAAO,KAAK,EACV,6BAA6B,EAC7B,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAEjB,KAAK,WAAW,GAAG,mBAAmB,CAAC;AAoEvC,KAAK,gBAAgB,GAAG;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAKF,KAAK,iBAAiB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAwPxD,QAAA,MAAM,YAAY;;;;;;;;;;;;;;kFAIhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFileUpload.d.ts","sourceRoot":"","sources":["../../../../src/dataTransfer/upload/useFileUpload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"useFileUpload.d.ts","sourceRoot":"","sources":["../../../../src/dataTransfer/upload/useFileUpload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,QAAQ,CAAC;AAYvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAsD1C,KAAK,oBAAoB,GAAG;IAC1B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC/B,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACpC,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC7B,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,KAAK,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,IAAI,CAAC;IACX;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D;;;OAGG;IACH,wBAAwB,CAAC,EAAE,YAAY,CAAC;IAExC;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IAEpC,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACtD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,eAAO,MAAM,aAAa,GAAI,UAAS,oBAAyB;;iCAQ1B,MAAM;kCAaL,MAAM,QAAQ,OAAO,CAAC,UAAU,CAAC;;;sBAmG5C,MAAM,SAAS,IAAI,EAAE,KAAG,OAAO,CAAC,MAAM,EAAE,CAAC;uBA8D9C,MAAM;;;;;;;CAO5B,CAAC"}
|
package/dist/toasts.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
|
|
2
2
|
import { t as capitalize, u as truncateString_default } from "./dist-DvDWkkNg.js";
|
|
3
3
|
import { Fragment, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, getCurrentInstance, inject, mergeProps, normalizeClass, onMounted, openBlock, provide, ref, renderList, resolveDynamicComponent, toDisplayString, unref, useCssVars, vShow, watch, withCtx, withDirectives } from "vue";
|
|
4
|
-
import { useEventBus, useMouseInElement, useRafFn, useWindowFocus } from "@vueuse/core";
|
|
5
4
|
import { KdsButton, KdsIcon, KdsLinkButton, KdsProgressBar } from "@knime/kds-components";
|
|
5
|
+
import { useEventBus, useMouseInElement, useRafFn, useWindowFocus } from "@vueuse/core";
|
|
6
6
|
import { border, color } from "@knime/kds-styles/tokens";
|
|
7
7
|
import { cloneDeep } from "es-toolkit";
|
|
8
8
|
import { uniqueId } from "es-toolkit/compat";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knime/product-features",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Package containing composition patterns of KDS Components and KNIME business logic",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -63,14 +63,14 @@
|
|
|
63
63
|
"consola": "^3.4.2"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@knime/kds-components": "^8.2.
|
|
67
|
-
"@knime/kds-styles": "^8.2.
|
|
66
|
+
"@knime/kds-components": "^8.2.1",
|
|
67
|
+
"@knime/kds-styles": "^8.2.1",
|
|
68
68
|
"@storybook/addon-docs": "^10.4.6",
|
|
69
69
|
"@storybook/vue3-vite": "^10.4.6",
|
|
70
70
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
71
71
|
"@vue/test-utils": "^2.4.11",
|
|
72
72
|
"@vue/tsconfig": "^0.9.1",
|
|
73
|
-
"@vueuse/core": "~14.
|
|
73
|
+
"@vueuse/core": "~14.4.0",
|
|
74
74
|
"es-toolkit": "^1.49.0",
|
|
75
75
|
"flush-promises": "^1.0.2",
|
|
76
76
|
"mock-xmlhttprequest": "^8.4.1",
|