@epam/ai-dial-chat-hooks 1.1.0-dev.456 → 1.1.0-dev.465

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.
@@ -20,6 +20,9 @@ var t = (e) => {
20
20
  },
21
21
  markTaken: (e) => {
22
22
  o.add(e);
23
+ },
24
+ release: (e) => {
25
+ o.delete(e);
23
26
  }
24
27
  };
25
28
  };
@@ -1,38 +1,55 @@
1
- import { sanitizeFileName as e } from "../../files/file-name.js";
2
- import { AttachmentErrorReason as t } from "@epam/ai-dial-chat-shared";
3
- import { useCallback as n, useRef as r } from "react";
1
+ import { formatDateYM as e } from "../conversation-transfer/date.js";
2
+ import { sanitizeFileName as t } from "../../files/file-name.js";
3
+ import { createUploadPathAllocator as n } from "../conversation-transfer/build-upload-path.js";
4
+ import { AttachmentErrorReason as r } from "@epam/ai-dial-chat-shared";
5
+ import { useCallback as i, useRef as a } from "react";
4
6
  //#region src/conversation/useAttachmentUpload/useAttachmentUpload.ts
5
- var i = 700, a = (t) => e(t.split("/").filter(Boolean).pop() ?? "file").replace(/\.\.+/g, ".").replace(/^\.+/, "") || "file", o = (e, t = /* @__PURE__ */ new Date()) => `uploads/${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, "0")}/${encodeURIComponent(e)}`, s = ({ filesApi: e, bucket: s, onNetworkError: c, debounceMs: l = i }) => {
6
- let u = r([]), d = r(null);
7
- return { handleUploadAttachment: n(async (n) => {
8
- if (!s) throw Error("User bucket is not available");
9
- let r = a(n.name);
10
- try {
7
+ var o = 700, s = 5, c = (e) => t(e.split("/").filter(Boolean).pop() ?? "file").replace(/\.\.+/g, ".").replace(/^\.+/, "") || "file", l = (e) => typeof e == "object" && !!e && "response" in e && e.response?.status === 409, u = ({ filesApi: t, bucket: u, onNetworkError: d, debounceMs: f = o }) => {
8
+ let p = a([]), m = a(null), h = a(null), g = i((t, r) => {
9
+ let i = `${t}|${e(r)}`;
10
+ return h.current?.key !== i && (h.current = {
11
+ key: i,
12
+ allocator: n({ date: r })
13
+ }), h.current.allocator;
14
+ }, []);
15
+ return { handleUploadAttachment: i(async (e) => {
16
+ if (!u) throw Error("User bucket is not available");
17
+ let n = c(e.name), i = g(u, /* @__PURE__ */ new Date()), a = i.allocate(n);
18
+ for (let o = 0;; o += 1) try {
19
+ let n = a.isRenamed ? new File([e.file], a.fileName, { type: e.file.type }) : e.file;
11
20
  return {
12
- url: (await e.uploadFile({
13
- bucket: s,
14
- path: o(r),
15
- file: n.file
21
+ url: (await t.uploadFile({
22
+ bucket: u,
23
+ path: a.path,
24
+ file: n,
25
+ uploadMode: "create-only"
16
26
  })).url,
17
- name: r
27
+ name: a.fileName
18
28
  };
19
- } catch (e) {
29
+ } catch (t) {
30
+ if (l(t)) {
31
+ if (i.markTaken(a.fileName), o < s) {
32
+ a = i.allocate(n);
33
+ continue;
34
+ }
35
+ } else i.release(a.fileName);
20
36
  if (!navigator.onLine) {
21
- u.current.push(n.name), d.current != null && clearTimeout(d.current), d.current = setTimeout(() => {
22
- let e = u.current.splice(0);
23
- c?.(e), d.current = null;
24
- }, l);
25
- let r = e instanceof Error ? e : /* @__PURE__ */ Error("Network upload failed");
26
- throw r.errorReason = t.Network, r;
37
+ p.current.push(e.name), m.current != null && clearTimeout(m.current), m.current = setTimeout(() => {
38
+ let e = p.current.splice(0);
39
+ d?.(e), m.current = null;
40
+ }, f);
41
+ let n = t instanceof Error ? t : /* @__PURE__ */ Error("Network upload failed");
42
+ throw n.errorReason = r.Network, n;
27
43
  }
28
- throw e;
44
+ throw t;
29
45
  }
30
46
  }, [
31
- s,
32
- l,
33
- e,
34
- c
47
+ u,
48
+ f,
49
+ t,
50
+ g,
51
+ d
35
52
  ]) };
36
53
  };
37
54
  //#endregion
38
- export { s as useAttachmentUpload };
55
+ export { u as useAttachmentUpload };
@@ -1,10 +1,10 @@
1
1
  import { formatDateYM as e } from "../conversation-transfer/date.js";
2
2
  import { ConversationTransferWarningCode as t } from "../conversation-transfer/types.js";
3
3
  import { UnsupportedImportFormatError as n, getFolderBreadcrumb as r, parseImportEnvelope as i, planAttachmentUploads as a, rebaseConversationId as o, rewriteAttachmentUrls as s } from "../conversation-transfer/import-conversation.js";
4
- import { runWithConcurrency as c } from "../conversation-transfer/async.js";
5
- import { ConversationTransferKind as l, ConversationTransferPhase as u, buildTransferProgress as d } from "../conversation-transfer/progress.js";
6
- import { useConversationTransferQueue as f } from "../conversation-transfer/queue.js";
7
- import { createUploadPathAllocator as p } from "../conversation-transfer/build-upload-path.js";
4
+ import { createUploadPathAllocator as c } from "../conversation-transfer/build-upload-path.js";
5
+ import { runWithConcurrency as l } from "../conversation-transfer/async.js";
6
+ import { ConversationTransferKind as u, ConversationTransferPhase as d, buildTransferProgress as f } from "../conversation-transfer/progress.js";
7
+ import { useConversationTransferQueue as p } from "../conversation-transfer/queue.js";
8
8
  import { parseDialArchive as m } from "../conversation-transfer/zip-import.js";
9
9
  import { ConversationTransferErrorCode as h, ConversationTransferSubjectKind as g, ConversationTransferUnitKind as _, generateUUID as v } from "@epam/ai-dial-chat-shared";
10
10
  import { useCallback as y } from "react";
@@ -26,9 +26,9 @@ var b = 5, x = 5, S = (e) => /\.(dial|zip)$/i.test(e.name), C = (e) => new Promi
26
26
  attachments: /* @__PURE__ */ new Map(),
27
27
  isArchive: !1
28
28
  };
29
- }, T = async (e, t, n, r, i, o, s, l) => {
30
- let { plan: u, skippedNames: d } = a(e, t, r), f = !1, p = await c(u, b, async (e) => {
31
- if (l.aborted || f) return;
29
+ }, T = async (e, t, n, r, i, o, s, c) => {
30
+ let { plan: u, skippedNames: d } = a(e, t, r), f = !1, p = await l(u, b, async (e) => {
31
+ if (c.aborted || f) return;
32
32
  let t = e.allocated;
33
33
  for (let a = 0; a <= x; a += 1) try {
34
34
  let r = new File([new Uint8Array(e.bytes)], t.fileName), a = await i.uploadFile({
@@ -36,7 +36,7 @@ var b = 5, x = 5, S = (e) => /\.(dial|zip)$/i.test(e.name), C = (e) => new Promi
36
36
  path: t.path,
37
37
  file: r,
38
38
  uploadMode: "create-only"
39
- }, { signal: l });
39
+ }, { signal: c });
40
40
  return s(), {
41
41
  kind: "uploaded",
42
42
  oldFileId: e.fileId,
@@ -44,7 +44,7 @@ var b = 5, x = 5, S = (e) => /\.(dial|zip)$/i.test(e.name), C = (e) => new Promi
44
44
  newTitle: t.isRenamed ? t.fileName : void 0
45
45
  };
46
46
  } catch (n) {
47
- if (l.aborted) return;
47
+ if (c.aborted) return;
48
48
  if (o(n)) {
49
49
  f = !0;
50
50
  return;
@@ -66,22 +66,22 @@ var b = 5, x = 5, S = (e) => /\.(dial|zip)$/i.test(e.name), C = (e) => new Promi
66
66
  }) : h.push(e.name);
67
67
  return {
68
68
  targetMap: m,
69
- skippedNames: l.aborted || f ? [] : h,
70
- isUnauthorized: !l.aborted && f
69
+ skippedNames: c.aborted || f ? [] : h,
70
+ isUnauthorized: !c.aborted && f
71
71
  };
72
- }, E = ({ conversationsApi: i, filesApi: a, bucket: c, onImported: m, classifyTransferError: b = () => ({}), resolveErrorTraceId: x = async () => void 0, onSuccess: S, onWarning: C, onError: E }) => {
73
- let D = f(), O = y(async (n, r, f) => {
74
- if (!c) {
72
+ }, E = ({ conversationsApi: i, filesApi: a, bucket: l, onImported: m, classifyTransferError: b = () => ({}), resolveErrorTraceId: x = async () => void 0, onSuccess: S, onWarning: C, onError: E }) => {
73
+ let D = p(), O = y(async (n, r, p) => {
74
+ if (!l) {
75
75
  E?.({
76
76
  jobId: n,
77
77
  code: h.MissingBucket
78
78
  }), D.failJob(n, h.MissingBucket);
79
79
  return;
80
80
  }
81
- let g = l.Import, v = r.attachments.size, y = 0, w = () => {
82
- y += 1, D.setJobProgress(n, d({
81
+ let g = u.Import, v = r.attachments.size, y = 0, w = () => {
82
+ y += 1, D.setJobProgress(n, f({
83
83
  kind: g,
84
- phase: u.Transfer,
84
+ phase: d.Transfer,
85
85
  completed: y,
86
86
  total: v
87
87
  }, {
@@ -90,9 +90,9 @@ var b = 5, x = 5, S = (e) => /\.(dial|zip)$/i.test(e.name), C = (e) => new Promi
90
90
  kind: _.Attachment
91
91
  }));
92
92
  }, O = (e) => {
93
- D.setJobProgress(n, d({
93
+ D.setJobProgress(n, f({
94
94
  kind: g,
95
- phase: u.Finalize,
95
+ phase: d.Finalize,
96
96
  completed: e,
97
97
  total: r.history.length
98
98
  }, {
@@ -105,11 +105,11 @@ var b = 5, x = 5, S = (e) => /\.(dial|zip)$/i.test(e.name), C = (e) => new Promi
105
105
  let t = `uploads/${e(P)}`, r = [];
106
106
  try {
107
107
  r = (await a.listFiles({
108
- bucket: c,
108
+ bucket: l,
109
109
  path: t
110
- }, { signal: f })).items.map((e) => e.name);
110
+ }, { signal: p })).items.map((e) => e.name);
111
111
  } catch (e) {
112
- if (f.aborted) return;
112
+ if (p.aborted) return;
113
113
  if (F(e)) {
114
114
  E?.({
115
115
  jobId: n,
@@ -119,26 +119,26 @@ var b = 5, x = 5, S = (e) => /\.(dial|zip)$/i.test(e.name), C = (e) => new Promi
119
119
  }
120
120
  console.error("Failed to list the upload folder for attachment name disambiguation", e);
121
121
  }
122
- I = p({
122
+ I = c({
123
123
  date: P,
124
124
  existingNames: r
125
125
  });
126
126
  }
127
- D.setJobProgress(n, d({
127
+ D.setJobProgress(n, f({
128
128
  kind: g,
129
- phase: u.Prepare
130
- })), v === 0 && D.setJobProgress(n, d({
129
+ phase: d.Prepare
130
+ })), v === 0 && D.setJobProgress(n, f({
131
131
  kind: g,
132
- phase: u.Transfer
132
+ phase: d.Transfer
133
133
  }));
134
134
  for (let e of r.history) {
135
- if (f.aborted) return;
135
+ if (p.aborted) return;
136
136
  if (M) break;
137
137
  try {
138
138
  let t = /* @__PURE__ */ new Map();
139
139
  if (r.isArchive && I) {
140
- let n = await T(e, r.attachments, c, I, a, F, w, f);
141
- if (f.aborted) return;
140
+ let n = await T(e, r.attachments, l, I, a, F, w, p);
141
+ if (p.aborted) return;
142
142
  if (n.isUnauthorized) {
143
143
  M = !0;
144
144
  break;
@@ -146,17 +146,17 @@ var b = 5, x = 5, S = (e) => /\.(dial|zip)$/i.test(e.name), C = (e) => new Promi
146
146
  for (let e of n.skippedNames) j.add(e);
147
147
  t = n.targetMap;
148
148
  }
149
- let { conversation: n, subPath: l } = o(s(e, t), c);
149
+ let { conversation: n, subPath: c } = o(s(e, t), l);
150
150
  if (await i.saveConversation({
151
- path: l,
151
+ path: c,
152
152
  saveConversationBodyDto: { conversation: {
153
153
  ...n,
154
154
  llmNamingDone: !0
155
155
  } }
156
- }, { signal: f }), f.aborted) return;
156
+ }, { signal: p }), p.aborted) return;
157
157
  k.push(n.name);
158
158
  } catch (t) {
159
- if (f.aborted) return;
159
+ if (p.aborted) return;
160
160
  if (F(t)) {
161
161
  M = !0;
162
162
  break;
@@ -165,7 +165,7 @@ var b = 5, x = 5, S = (e) => /\.(dial|zip)$/i.test(e.name), C = (e) => new Promi
165
165
  }
166
166
  O(k.length + A.length);
167
167
  }
168
- if (!f.aborted) {
168
+ if (!p.aborted) {
169
169
  if (M) {
170
170
  E?.({
171
171
  jobId: n,
@@ -194,7 +194,7 @@ var b = 5, x = 5, S = (e) => /\.(dial|zip)$/i.test(e.name), C = (e) => new Promi
194
194
  }), A.length > 0 ? D.failJob(n, h.Unknown) : j.size > 0 ? D.warnJob(n, t.AttachmentSkipped) : D.succeedJob(n);
195
195
  }
196
196
  }, [
197
- c,
197
+ l,
198
198
  b,
199
199
  i,
200
200
  a,
package/conversation.d.ts CHANGED
@@ -690,7 +690,8 @@ export declare const useAsyncConfirmDialog: <T>() => AsyncConfirmDialogControls<
690
690
 
691
691
  /**
692
692
  * Uploads an attachment's file to DIAL Core storage against an
693
- * already-configured `FilesApi` instance, coalescing a burst of
693
+ * already-configured `FilesApi` instance, giving every upload a collision-free
694
+ * path so no attachment overwrites another, and coalescing a burst of
694
695
  * offline/network upload failures into a single debounced callback rather
695
696
  * than firing one notification per failed file.
696
697
  */
package/file-manager.d.ts CHANGED
@@ -85,6 +85,8 @@ export declare interface AttachmentCanvasUrlResolvers {
85
85
  resolveDialFileDownloadUrl: (fileId: string) => string | undefined;
86
86
  /** Resolves the best downloadable DIAL-file URL from an attachment's `url` or `referenceUrl`. */
87
87
  resolveDialUrl: (attachment: DisplayAttachment) => string | undefined;
88
+ /** Resolves a DIAL Core file id to a fetchable metadata URL (used for cache-freshness validation). */
89
+ resolveDialFileMetadataUrl: (fileId: string) => string | undefined;
88
90
  }
89
91
 
90
92
  /** The type of content the canvas can display. */
@@ -17,45 +17,80 @@ var g = (e, t) => URL.createObjectURL(s(e, t)), _ = (e) => {
17
17
  url: e
18
18
  }), b = new h({ max: 10 }), x = new h({ max: 50 }), S = () => {
19
19
  b.clear(), x.clear();
20
- }, C = (e) => {
21
- let t = b.get(e);
22
- return t ?? (t = fetch(e).then((e) => {
20
+ }, C = async (e, t) => {
21
+ let n = t.resolveDialFileMetadataUrl(e);
22
+ if (n != null) try {
23
+ let e = await fetch(n);
24
+ if (!e.ok) return;
25
+ let t = (await e.json())?.etag;
26
+ return typeof t == "string" ? t : void 0;
27
+ } catch {
28
+ return;
29
+ }
30
+ }, w = async (e, t, n) => {
31
+ let r = await C(t, n);
32
+ if (r == null) return fetch(e).then((e) => {
33
+ if (!e.ok) throw Object.assign(/* @__PURE__ */ Error(`HTTP ${e.status}`), { status: e.status });
34
+ return e.blob();
35
+ });
36
+ let i = b.get(e);
37
+ if (i != null && i.etag === r) return i.promise;
38
+ b.delete(e);
39
+ let a = fetch(e).then((e) => {
23
40
  if (!e.ok) throw Object.assign(/* @__PURE__ */ Error(`HTTP ${e.status}`), { status: e.status });
24
41
  return e.blob();
25
42
  }).catch((t) => {
26
43
  throw b.delete(e), t;
27
- }), b.set(e, t)), t;
28
- }, w = (e) => {
29
- let t = x.get(e);
30
- return t ?? (t = fetch(e).then((e) => {
44
+ });
45
+ return b.set(e, {
46
+ etag: r,
47
+ promise: a
48
+ }), a;
49
+ }, T = async (e, t, n) => {
50
+ let r = await C(t, n);
51
+ if (r == null) return fetch(e).then((e) => {
52
+ if (!e.ok) throw Object.assign(/* @__PURE__ */ Error(`HTTP ${e.status}`), { status: e.status });
53
+ return e.text();
54
+ });
55
+ let i = x.get(e);
56
+ if (i != null && i.etag === r) return i.promise;
57
+ x.delete(e);
58
+ let a = fetch(e).then((e) => {
31
59
  if (!e.ok) throw Object.assign(/* @__PURE__ */ Error(`HTTP ${e.status}`), { status: e.status });
32
60
  return e.text();
33
61
  }).catch((t) => {
34
62
  throw x.delete(e), t;
35
- }), x.set(e, t)), t;
36
- }, T = async (e, t) => {
63
+ });
64
+ return x.set(e, {
65
+ etag: r,
66
+ promise: a
67
+ }), a;
68
+ }, E = (e) => e.split("#")[0], D = (t) => {
69
+ if (t.url != null && e(t.url)) return E(t.url);
70
+ if (t.referenceUrl != null && e(t.referenceUrl)) return E(t.referenceUrl);
71
+ }, O = async (e, t) => {
37
72
  if ("file" in e) return URL.createObjectURL(e.file);
38
73
  let n = t.resolveDialUrl(e);
39
74
  if (n != null) try {
40
- let e = await C(n);
41
- return URL.createObjectURL(e);
75
+ let r = await w(n, D(e) ?? "", t);
76
+ return URL.createObjectURL(r);
42
77
  } catch (e) {
43
78
  let t = e.status;
44
79
  return t == null ? y(n) : v(t, n);
45
80
  }
46
81
  if (e.previewUrl != null) return e.previewUrl;
47
82
  if (e.data != null) return g(e.data, e.contentType);
48
- }, E = async (e, t) => {
83
+ }, k = async (e, t) => {
49
84
  if (e.data != null) return _(e.data);
50
85
  let n = t.resolveDialUrl(e);
51
86
  if (n != null) try {
52
- return await w(n);
87
+ return await T(n, D(e) ?? "", t);
53
88
  } catch (e) {
54
89
  let t = e.status;
55
90
  return t == null ? y(n) : v(t, n);
56
91
  }
57
92
  if ("file" in e) return e.file.text();
58
- }, D = (e, t) => e.data != null || t.resolveDialUrl(e) != null || "file" in e, O = (e, n) => {
93
+ }, A = (e, t) => e.data != null || t.resolveDialUrl(e) != null || "file" in e, j = (e, n) => {
59
94
  if ("file" in e) return {
60
95
  type: t.Image,
61
96
  url: URL.createObjectURL(e.file)
@@ -71,37 +106,37 @@ var g = (e, t) => URL.createObjectURL(s(e, t)), _ = (e) => {
71
106
  type: t.Image,
72
107
  url: r
73
108
  };
74
- }, k = async (e, n) => {
75
- let r = await E(e, n);
109
+ }, M = async (e, n) => {
110
+ let r = await k(e, n);
76
111
  return r == null ? null : typeof r == "string" ? {
77
112
  type: t.PlainText,
78
113
  text: r
79
114
  } : r;
80
- }, A = async (e, n) => {
81
- let r = await E(e, n);
115
+ }, N = async (e, n) => {
116
+ let r = await k(e, n);
82
117
  return r == null ? null : typeof r == "string" ? {
83
118
  type: t.Markdown,
84
119
  text: r
85
120
  } : r;
86
- }, j = 1048576, M = async (e, n, r) => {
87
- let i = await E(e, n);
121
+ }, P = 1048576, F = async (e, n, r) => {
122
+ let i = await k(e, n);
88
123
  return i == null ? null : typeof i == "string" ? {
89
124
  type: t.Code,
90
125
  text: i,
91
126
  language: r
92
127
  } : i;
93
- }, N = async (e, n) => {
94
- let r = await E(e, n);
128
+ }, I = async (e, n) => {
129
+ let r = await k(e, n);
95
130
  if (r == null) return null;
96
131
  if (typeof r != "string") return r;
97
- if (r.length > j) return null;
132
+ if (r.length > P) return null;
98
133
  let i = n.resolveDialUrl(e) ?? void 0;
99
134
  return {
100
135
  type: t.Html,
101
136
  srcdoc: r,
102
137
  url: i
103
138
  };
104
- }, P = (n, r, i) => {
139
+ }, L = (n, r, i) => {
105
140
  let a = n.body?.source?.attachment;
106
141
  if (a?.type !== o.PDF) return null;
107
142
  let s = e(a.url) ? i.resolveDialFileDownloadUrl(a.url) : a.url;
@@ -114,7 +149,7 @@ var g = (e, t) => URL.createObjectURL(s(e, t)), _ = (e) => {
114
149
  selectedHighlightId: f.some((e) => e.id === m) ? m : void 0,
115
150
  page: p(n)
116
151
  };
117
- }, F = (e) => {
152
+ }, R = (e) => {
118
153
  switch (e.type) {
119
154
  case "docx_text_range": return {
120
155
  kind: i.DocxTextRange,
@@ -139,7 +174,7 @@ var g = (e, t) => URL.createObjectURL(s(e, t)), _ = (e) => {
139
174
  end: e.end
140
175
  };
141
176
  }
142
- }, I = (n, i, o) => {
177
+ }, z = (n, i, o) => {
143
178
  let s = n.body?.source?.attachment;
144
179
  if (s?.url == null) return null;
145
180
  let c = a(s.title ?? s.url, s.type);
@@ -153,7 +188,7 @@ var g = (e, t) => URL.createObjectURL(s(e, t)), _ = (e) => {
153
188
  let i = l(e, t);
154
189
  m.push({
155
190
  id: i,
156
- locations: r.map(F)
191
+ locations: r.map(R)
157
192
  }), e === n && (h = i);
158
193
  }), {
159
194
  type: t.Ooxml,
@@ -164,7 +199,7 @@ var g = (e, t) => URL.createObjectURL(s(e, t)), _ = (e) => {
164
199
  selectedHighlightId: h
165
200
  } : {}
166
201
  };
167
- }, L = (n, r) => {
202
+ }, B = (n, r) => {
168
203
  let i = m(n.url);
169
204
  if (i == null) return null;
170
205
  let a = e(i.baseUrl) ? r.resolveDialFileDownloadUrl(i.baseUrl) : i.baseUrl;
@@ -194,39 +229,39 @@ var g = (e, t) => URL.createObjectURL(s(e, t)), _ = (e) => {
194
229
  selectedHighlightId: o,
195
230
  page: i.page
196
231
  };
197
- }, R = (e) => {
232
+ }, V = (e) => {
198
233
  try {
199
234
  let t = new URL(e).protocol;
200
235
  return t === "http:" || t === "https:" || t === "blob:";
201
236
  } catch {
202
237
  return !1;
203
238
  }
204
- }, z = (t) => t.url != null && !e(t.url) && R(t.url) ? t.url : void 0, B = async (e, n) => {
205
- let r = await T(e, n);
239
+ }, H = (t) => t.url != null && !e(t.url) && V(t.url) ? t.url : void 0, U = async (e, n) => {
240
+ let r = await O(e, n);
206
241
  if (r != null) return typeof r == "string" ? {
207
242
  type: t.Pdf,
208
243
  url: r
209
244
  } : r;
210
- let i = z(e);
245
+ let i = H(e);
211
246
  return i == null ? null : {
212
247
  type: t.Pdf,
213
248
  url: i
214
249
  };
215
- }, V = async (e, n, r) => {
216
- let i = await T(e, n);
250
+ }, W = async (e, n, r) => {
251
+ let i = await O(e, n);
217
252
  if (i != null) return typeof i == "string" ? {
218
253
  type: t.Ooxml,
219
254
  url: i,
220
255
  format: r
221
256
  } : i;
222
- let a = z(e);
257
+ let a = H(e);
223
258
  return a == null ? null : {
224
259
  type: t.Ooxml,
225
260
  url: a,
226
261
  format: r
227
262
  };
228
- }, H = async (e, n) => {
229
- let r = await E(e, n);
263
+ }, G = async (e, n) => {
264
+ let r = await k(e, n);
230
265
  if (r == null) return null;
231
266
  if (typeof r != "string") return r;
232
267
  try {
@@ -241,8 +276,8 @@ var g = (e, t) => URL.createObjectURL(s(e, t)), _ = (e) => {
241
276
  text: r
242
277
  };
243
278
  }
244
- }, U = async (e, n, r, i) => {
245
- let a = await E(e, n);
279
+ }, K = async (e, n, r, i) => {
280
+ let a = await k(e, n);
246
281
  if (a == null || typeof a != "string") return null;
247
282
  let o = {};
248
283
  try {
@@ -265,4 +300,4 @@ var g = (e, t) => URL.createObjectURL(s(e, t)), _ = (e) => {
265
300
  };
266
301
  };
267
302
  //#endregion
268
- export { I as annotationToOoxmlCanvasContent, P as annotationToPdfCanvasContent, S as clearAttachmentCache, D as hasAttachmentTextSource, L as referenceAttachmentToPdfCanvasContent, M as resolveCodeCanvasContent, N as resolveHtmlCanvasContent, O as resolveImageCanvasContent, H as resolveJsonCanvasContent, A as resolveMarkdownCanvasContent, V as resolveOoxmlCanvasContent, B as resolvePdfCanvasContent, k as resolveTextCanvasContent, U as resolveVisualizerCanvasContent };
303
+ export { z as annotationToOoxmlCanvasContent, L as annotationToPdfCanvasContent, S as clearAttachmentCache, A as hasAttachmentTextSource, B as referenceAttachmentToPdfCanvasContent, F as resolveCodeCanvasContent, I as resolveHtmlCanvasContent, j as resolveImageCanvasContent, G as resolveJsonCanvasContent, N as resolveMarkdownCanvasContent, W as resolveOoxmlCanvasContent, U as resolvePdfCanvasContent, M as resolveTextCanvasContent, K as resolveVisualizerCanvasContent };
package/index.d.ts CHANGED
@@ -34,8 +34,8 @@ import type { CustomVisualizerDataLayout } from '@epam/ai-dial-chat-shared';
34
34
  import { DeleteFilesResponseDto } from '@epam/ai-dial-chat-api-client';
35
35
  import { DeleteItemDto } from '@epam/ai-dial-chat-api-client';
36
36
  import { DeploymentConfigurationSchema } from '@epam/ai-dial-chat-shared';
37
- import { DeploymentCreationFormLocaleEntry } from '@epam/ai-dial-deployment-creation-form';
38
- import { DeploymentCreationFormLocaleOption } from '@epam/ai-dial-deployment-creation-form';
37
+ import { DeploymentCreationFormLocaleEntry } from '@epam/ai-dial-builder-form';
38
+ import { DeploymentCreationFormLocaleOption } from '@epam/ai-dial-builder-form';
39
39
  import { DeploymentDetailsDto } from '@epam/ai-dial-chat-api-client';
40
40
  import { DeploymentFeatures } from '@epam/ai-dial-chat-shared';
41
41
  import { DeploymentItemDto } from '@epam/ai-dial-chat-api-client';
@@ -277,6 +277,8 @@ export declare interface AttachmentCanvasUrlResolvers {
277
277
  resolveDialFileDownloadUrl: (fileId: string) => string | undefined;
278
278
  /** Resolves the best downloadable DIAL-file URL from an attachment's `url` or `referenceUrl`. */
279
279
  resolveDialUrl: (attachment: DisplayAttachment) => string | undefined;
280
+ /** Resolves a DIAL Core file id to a fetchable metadata URL (used for cache-freshness validation). */
281
+ resolveDialFileMetadataUrl: (fileId: string) => string | undefined;
280
282
  }
281
283
 
282
284
  /** The type of content the canvas can display. */
@@ -2184,6 +2186,9 @@ declare interface MarkdownCanvasContent {
2184
2186
  text: string;
2185
2187
  }
2186
2188
 
2189
+ /** Display mode an MCP App can ask the host to switch to via `ui/request-display-mode` — the MCP UI protocol's `inline`/`fullscreen`/`pip` union. */
2190
+ declare type McpAppDisplayMode = NonNullable<McpUiHostContext['displayMode']>;
2191
+
2187
2192
  /**
2188
2193
  * Dependencies the host must inject into `useMcpAppInlinePreview` /
2189
2194
  * `McpAppInlinePreview` — everything that requires host context (theming,
@@ -2211,6 +2216,12 @@ export declare interface McpAppHostContextParams {
2211
2216
  locale: string;
2212
2217
  /** Operator-configured MCP App user-agent override. Defaults to `'ai-dial-chat'`. */
2213
2218
  mcpAppUserAgent?: string;
2219
+ /**
2220
+ * Display modes the host can switch an app between via
2221
+ * `ui/request-display-mode`. Defaults to `['inline', 'fullscreen']` — the
2222
+ * compact inline preview and the full-width canvas.
2223
+ */
2224
+ availableDisplayModes?: McpAppDisplayMode[];
2214
2225
  }
2215
2226
 
2216
2227
  /** Thrown by `McpAppsApiClient.fetchResourceHtml` on a non-OK response, carrying the HTTP status so callers can distinguish `403` (forbidden) from any other failure. */
@@ -3851,7 +3862,8 @@ export declare interface UseAttachmentActionResult {
3851
3862
 
3852
3863
  /**
3853
3864
  * Uploads an attachment's file to DIAL Core storage against an
3854
- * already-configured `FilesApi` instance, coalescing a burst of
3865
+ * already-configured `FilesApi` instance, giving every upload a collision-free
3866
+ * path so no attachment overwrites another, and coalescing a burst of
3855
3867
  * offline/network upload failures into a single debounced callback rather
3856
3868
  * than firing one notification per failed file.
3857
3869
  */
@@ -4939,9 +4951,10 @@ export declare const useMcpAppHostAdapter: (displayMode: "inline" | "fullscreen"
4939
4951
  * `ui/initialize` handshake, shared by every place that mounts an app —
4940
4952
  * a full-width attachment canvas (`displayMode: 'fullscreen'`) and a compact
4941
4953
  * inline preview under a message (`displayMode: 'inline'`) — so the app can
4942
- * size/style itself differently for each.
4954
+ * size/style itself differently for each, and knows (via
4955
+ * `availableDisplayModes`) which surfaces it may ask the host to switch to.
4943
4956
  */
4944
- export declare const useMcpAppHostContext: (displayMode: "inline" | "fullscreen", { theme, mcpAppTheme, locale, mcpAppUserAgent }: McpAppHostContextParams) => McpUiHostContext;
4957
+ export declare const useMcpAppHostContext: (displayMode: "inline" | "fullscreen", { theme, mcpAppTheme, locale, mcpAppUserAgent, availableDisplayModes, }: McpAppHostContextParams) => McpUiHostContext;
4945
4958
 
4946
4959
  /**
4947
4960
  * Discovers MCP Apps-capable tools available to the current conversation, merging two
@@ -7,20 +7,22 @@ var t = /* @__PURE__ */ "--color-background-primary.--color-background-secondary
7
7
  t && (n[r] = t);
8
8
  }
9
9
  return n;
10
- }, r = (t, { theme: r, mcpAppTheme: i, locale: a, mcpAppUserAgent: o }) => e(() => ({
10
+ }, r = (t, { theme: r, mcpAppTheme: i, locale: a, mcpAppUserAgent: o, availableDisplayModes: s }) => e(() => ({
11
11
  theme: i ?? r,
12
12
  locale: a,
13
13
  timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
14
14
  userAgent: o ?? "ai-dial-chat",
15
15
  platform: "web",
16
16
  displayMode: t,
17
+ availableDisplayModes: s ?? ["inline", "fullscreen"],
17
18
  styles: { variables: n() }
18
19
  }), [
19
20
  i,
20
21
  r,
21
22
  a,
22
23
  o,
23
- t
24
+ t,
25
+ s
24
26
  ]);
25
27
  //#endregion
26
28
  export { r as useMcpAppHostContext };
@@ -4,12 +4,12 @@ import { useCallback as i, useEffect as a, useRef as o } from "react";
4
4
  import { computeMcpAppSeedKey as s, resolveMcpAppToolResult as c } from "@epam/ai-dial-mcp-apps";
5
5
  //#region src/mcp-apps/useOpenMcpAppCanvas/useOpenMcpAppCanvas.ts
6
6
  var l = (l, u, d, f) => {
7
- let { openCanvas: p, openCanvasLoading: m } = r(), { hostContext: h, sandboxUrl: g, fetchResourceHtml: _, callTool: v } = u, y = o(null), b = i(async (r, i, a, o = !1) => {
8
- if (g == null) return !1;
7
+ let { openCanvas: p, openCanvasLoading: m, closeCanvas: h } = r(), { hostContext: g, sandboxUrl: _, fetchResourceHtml: v, callTool: y } = u, b = i((e) => e === "inline" ? (h(), "inline") : "fullscreen", [h]), x = o(null), S = i(async (r, i, a, o = !1) => {
8
+ if (_ == null) return !1;
9
9
  f?.(), m(d.title, i);
10
10
  try {
11
11
  let e = s(a), n = async () => {
12
- let [e, t] = await Promise.all([_(r.toolsetId, r.resourceUri), c(r, a, v)]);
12
+ let [e, t] = await Promise.all([v(r.toolsetId, r.resourceUri), c(r, a, y)]);
13
13
  return {
14
14
  html: e,
15
15
  toolResult: t
@@ -21,14 +21,14 @@ var l = (l, u, d, f) => {
21
21
  }, e)), p({
22
22
  type: t.McpApp,
23
23
  html: u,
24
- sandboxUrl: g,
25
- toolName: r.mcpToolName,
24
+ sandboxUrl: _,
26
25
  toolInput: a?.toolInput,
27
26
  toolResult: f,
28
- hostContext: h,
29
- onToolCall: (e, t) => v(r.toolsetId, e, t, r.kind),
27
+ hostContext: g,
28
+ onToolCall: (e, t) => y(r.toolsetId, e, t, r.kind),
29
+ onRequestDisplayMode: b,
30
30
  onReload: () => {
31
- i != null && l.invalidate(i), y.current?.(r, i, a, !0);
31
+ i != null && l.invalidate(i), x.current?.(r, i, a, !0);
32
32
  }
33
33
  }, d.title, i), !0;
34
34
  } catch (r) {
@@ -43,16 +43,17 @@ var l = (l, u, d, f) => {
43
43
  p,
44
44
  m,
45
45
  f,
46
- g,
47
- h,
48
46
  _,
47
+ g,
49
48
  v,
49
+ y,
50
50
  l,
51
- d
51
+ d,
52
+ b
52
53
  ]);
53
54
  return a(() => {
54
- y.current = b;
55
- }, [b]), { openMcpAppCanvas: b };
55
+ x.current = S;
56
+ }, [S]), { openMcpAppCanvas: S };
56
57
  };
57
58
  //#endregion
58
59
  export { l as useOpenMcpAppCanvas };
package/mcp-apps.d.ts CHANGED
@@ -36,6 +36,9 @@ toolsetId: string,
36
36
  resourceUri: string,
37
37
  ) => Promise<string>;
38
38
 
39
+ /** Display mode an MCP App can ask the host to switch to via `ui/request-display-mode` — the MCP UI protocol's `inline`/`fullscreen`/`pip` union. */
40
+ declare type McpAppDisplayMode = NonNullable<McpUiHostContext['displayMode']>;
41
+
39
42
  /**
40
43
  * Dependencies the host must inject into `useMcpAppInlinePreview` /
41
44
  * `McpAppInlinePreview` — everything that requires host context (theming,
@@ -63,6 +66,12 @@ export declare interface McpAppHostContextParams {
63
66
  locale: string;
64
67
  /** Operator-configured MCP App user-agent override. Defaults to `'ai-dial-chat'`. */
65
68
  mcpAppUserAgent?: string;
69
+ /**
70
+ * Display modes the host can switch an app between via
71
+ * `ui/request-display-mode`. Defaults to `['inline', 'fullscreen']` — the
72
+ * compact inline preview and the full-width canvas.
73
+ */
74
+ availableDisplayModes?: McpAppDisplayMode[];
66
75
  }
67
76
 
68
77
  /** Thrown by `McpAppsApiClient.fetchResourceHtml` on a non-OK response, carrying the HTTP status so callers can distinguish `403` (forbidden) from any other failure. */
@@ -181,9 +190,10 @@ export declare const useMcpAppHostAdapter: (displayMode: "inline" | "fullscreen"
181
190
  * `ui/initialize` handshake, shared by every place that mounts an app —
182
191
  * a full-width attachment canvas (`displayMode: 'fullscreen'`) and a compact
183
192
  * inline preview under a message (`displayMode: 'inline'`) — so the app can
184
- * size/style itself differently for each.
193
+ * size/style itself differently for each, and knows (via
194
+ * `availableDisplayModes`) which surfaces it may ask the host to switch to.
185
195
  */
186
- export declare const useMcpAppHostContext: (displayMode: "inline" | "fullscreen", { theme, mcpAppTheme, locale, mcpAppUserAgent }: McpAppHostContextParams) => McpUiHostContext;
196
+ export declare const useMcpAppHostContext: (displayMode: "inline" | "fullscreen", { theme, mcpAppTheme, locale, mcpAppUserAgent, availableDisplayModes, }: McpAppHostContextParams) => McpUiHostContext;
187
197
 
188
198
  /**
189
199
  * Discovers MCP Apps-capable tools available to the current conversation, merging two
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@epam/ai-dial-chat-hooks",
3
3
  "description": "Framework-level React hooks extracted from AI DIAL Chat for building custom chat interfaces",
4
- "version": "1.1.0-dev.456",
4
+ "version": "1.1.0-dev.465",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "main": "./index.js",
@@ -96,7 +96,7 @@
96
96
  }
97
97
  },
98
98
  "dependencies": {
99
- "dompurify": "3.4.13",
99
+ "dompurify": "3.4.15",
100
100
  "fflate": "^0.8.3",
101
101
  "lru-cache": "^10.4.3",
102
102
  "mime-types": "^3.0.2",
@@ -104,21 +104,21 @@
104
104
  },
105
105
  "peerDependencies": {
106
106
  "react": "^19.2.6",
107
- "@epam/ai-dial-attachment-canvas": "1.1.0-dev.456",
108
- "@epam/ai-dial-attachment-input": "1.1.0-dev.456",
109
- "@epam/ai-dial-catalog": "1.1.0-dev.456",
110
- "@epam/ai-dial-chat-api-client": "1.1.0-dev.456",
111
- "@epam/ai-dial-chat-overlay": "1.1.0-dev.456",
112
- "@epam/ai-dial-chat-shared": "1.1.0-dev.456",
113
- "@epam/ai-dial-deployment-creation-form": "1.1.0-dev.456",
114
- "@epam/ai-dial-mcp-apps": "1.1.0-dev.456",
115
- "@epam/ai-dial-publish-panel": "1.1.0-dev.456",
116
- "@epam/ai-dial-quotations": "1.1.0-dev.456",
107
+ "@epam/ai-dial-attachment-canvas": "1.1.0-dev.465",
108
+ "@epam/ai-dial-attachment-input": "1.1.0-dev.465",
109
+ "@epam/ai-dial-builder-form": "1.1.0-dev.465",
110
+ "@epam/ai-dial-catalog": "1.1.0-dev.465",
111
+ "@epam/ai-dial-chat-api-client": "1.1.0-dev.465",
112
+ "@epam/ai-dial-chat-overlay": "1.1.0-dev.465",
113
+ "@epam/ai-dial-chat-shared": "1.1.0-dev.465",
114
+ "@epam/ai-dial-mcp-apps": "1.1.0-dev.465",
115
+ "@epam/ai-dial-publish-panel": "1.1.0-dev.465",
116
+ "@epam/ai-dial-quotations": "1.1.0-dev.465",
117
117
  "@epam/ai-dial-react-file-manager": "*",
118
- "@epam/ai-dial-scheduled-tasks": "1.1.0-dev.456",
119
- "@epam/ai-dial-share": "1.1.0-dev.456",
120
- "@epam/ai-dial-skill-editor": "1.1.0-dev.456",
121
- "@epam/ai-dial-source-panel": "1.1.0-dev.456",
118
+ "@epam/ai-dial-scheduled-tasks": "1.1.0-dev.465",
119
+ "@epam/ai-dial-share": "1.1.0-dev.465",
120
+ "@epam/ai-dial-skill-editor": "1.1.0-dev.465",
121
+ "@epam/ai-dial-source-panel": "1.1.0-dev.465",
122
122
  "@epam/ai-dial-ui-kit": "*",
123
123
  "@modelcontextprotocol/sdk": "^1.27.1",
124
124
  "@epam/pdf-highlighter-kit": ">=0.0.14",
@@ -131,6 +131,9 @@
131
131
  "@epam/ai-dial-attachment-input": {
132
132
  "optional": true
133
133
  },
134
+ "@epam/ai-dial-builder-form": {
135
+ "optional": true
136
+ },
134
137
  "@epam/ai-dial-catalog": {
135
138
  "optional": true
136
139
  },
@@ -143,9 +146,6 @@
143
146
  "@epam/ai-dial-chat-shared": {
144
147
  "optional": true
145
148
  },
146
- "@epam/ai-dial-deployment-creation-form": {
147
- "optional": true
148
- },
149
149
  "@epam/ai-dial-mcp-apps": {
150
150
  "optional": true
151
151
  },
package/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { DeploymentCreationFormLocaleEntry } from '@epam/ai-dial-deployment-creation-form';
2
- import { DeploymentCreationFormLocaleOption } from '@epam/ai-dial-deployment-creation-form';
1
+ import { DeploymentCreationFormLocaleEntry } from '@epam/ai-dial-builder-form';
2
+ import { DeploymentCreationFormLocaleOption } from '@epam/ai-dial-builder-form';
3
3
  import { ExportFolder } from '@epam/ai-dial-chat-shared';
4
4
  import { LocaleTextEntryDto } from '@epam/ai-dial-chat-api-client';
5
5
  import { Middleware } from '@epam/ai-dial-chat-api-client';