arcy.js 0.0.2 → 0.1.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/README.md +94 -3
- package/dist/arcy.chat.3968644db103.js +1271 -0
- package/dist/arcy.flow.fef131718e42.js +3 -0
- package/dist/arcy.legacy.b4005932a6cc.js +568 -0
- package/dist/arcy.loader.js +2 -0
- package/dist/arcy.modern.a51b9a32dd28.js +568 -0
- package/dist/arcy.picker.73f47221b73c.js +59 -0
- package/dist/chat-BK57WIAA.js +3867 -0
- package/dist/chunk-6HBSGVRN.js +219 -0
- package/dist/chunk-7VVKR3AO.js +122 -0
- package/dist/chunk-CQ2DESAJ.js +263 -0
- package/dist/chunk-DKYMIAYF.js +7 -0
- package/dist/chunk-FR6SJSDU.js +220 -0
- package/dist/chunk-LGWYJSYX.js +271 -0
- package/dist/chunk-NIHUDSWK.js +155 -0
- package/dist/chunk-NY3NXM2V.js +213 -0
- package/dist/design-mode.cjs +135 -0
- package/dist/design-mode.d.ts +197 -0
- package/dist/design-mode.js +2 -0
- package/dist/flow-M7W3H3C5.js +1422 -0
- package/dist/fonts/dm-sans-latin-ext.woff2 +0 -0
- package/dist/fonts/dm-sans-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-latin-ext.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-latin.woff2 +0 -0
- package/dist/fonts/inter-latin-ext.woff2 +0 -0
- package/dist/fonts/inter-latin.woff2 +0 -0
- package/dist/fonts/lato-latin-ext.woff2 +0 -0
- package/dist/fonts/lato-latin.woff2 +0 -0
- package/dist/fonts/manrope-latin-ext.woff2 +0 -0
- package/dist/fonts/manrope-latin.woff2 +0 -0
- package/dist/fonts/montserrat-latin-ext.woff2 +0 -0
- package/dist/fonts/montserrat-latin.woff2 +0 -0
- package/dist/fonts/nunito-latin-ext.woff2 +0 -0
- package/dist/fonts/nunito-latin.woff2 +0 -0
- package/dist/fonts/open-sans-latin-ext.woff2 +0 -0
- package/dist/fonts/open-sans-latin.woff2 +0 -0
- package/dist/fonts/playfair-display-latin-ext.woff2 +0 -0
- package/dist/fonts/playfair-display-latin.woff2 +0 -0
- package/dist/fonts/poppins-latin-ext.woff2 +0 -0
- package/dist/fonts/poppins-latin.woff2 +0 -0
- package/dist/fonts/roboto-latin-ext.woff2 +0 -0
- package/dist/fonts/roboto-latin.woff2 +0 -0
- package/dist/fonts/source-sans-3-latin-ext.woff2 +0 -0
- package/dist/fonts/source-sans-3-latin.woff2 +0 -0
- package/dist/fonts/work-sans-latin-ext.woff2 +0 -0
- package/dist/fonts/work-sans-latin.woff2 +0 -0
- package/dist/index.cjs +13848 -0
- package/dist/index.d.ts +169 -0
- package/dist/index.js +4743 -0
- package/dist/picker-JPESE6JJ.js +2134 -0
- package/dist/snippet.cjs +46 -0
- package/dist/snippet.d.ts +39 -0
- package/dist/snippet.js +43 -0
- package/package.json +69 -5
- package/index.js +0 -5
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/* arcy.js — https://arcyai.com */
|
|
2
|
+
|
|
3
|
+
// src/shell/chat-contract.ts
|
|
4
|
+
var CHAT_GLOBAL = "__arcyChat";
|
|
5
|
+
var CHAT_CONTRACT = 4;
|
|
6
|
+
var ATTACHABLE_TYPES = [
|
|
7
|
+
"image/png",
|
|
8
|
+
"image/jpeg",
|
|
9
|
+
"image/webp"
|
|
10
|
+
];
|
|
11
|
+
var FONT_KEY_PROPERTY = "--_arcy-font-key";
|
|
12
|
+
var POLICY_LINK_CLASS = "arcy-chat-policy";
|
|
13
|
+
var BAR_TRAY_CLASS = "arcy-bar-tray";
|
|
14
|
+
var FALLBACK_CHROME = {
|
|
15
|
+
historyTitle: "Recent chats",
|
|
16
|
+
historyEmpty: "No conversations yet.",
|
|
17
|
+
newChat: "New chat",
|
|
18
|
+
flowsTitle: "What I can do",
|
|
19
|
+
flowsEmpty: "Nothing to run yet.",
|
|
20
|
+
collapse: "Collapse",
|
|
21
|
+
back: "Back",
|
|
22
|
+
closeSheet: "Close",
|
|
23
|
+
settingsTitle: "Settings",
|
|
24
|
+
dockChat: "Dock to the corner",
|
|
25
|
+
dockChatHint: "Shrink ARCY to a button in the bottom-right corner.",
|
|
26
|
+
language: "Language",
|
|
27
|
+
openChat: "Open chat",
|
|
28
|
+
moveBar: "Move the chat bar",
|
|
29
|
+
send: "Send",
|
|
30
|
+
flowRunning: "Running",
|
|
31
|
+
flowStep: "Step {step} of {total}",
|
|
32
|
+
flowStop: "Stop",
|
|
33
|
+
flowFailed: "Sorry, I could not finish that for you this time. You can ask me here and I will help you do it.",
|
|
34
|
+
attachMenu: "Add",
|
|
35
|
+
attachImage: "Attach an image",
|
|
36
|
+
removeImage: "Remove image",
|
|
37
|
+
imageTooLarge: "That image is too large. Each image must be under 5 MB.",
|
|
38
|
+
imageWrongType: "That file type is not supported. Use PNG, JPEG or WEBP.",
|
|
39
|
+
imageTooMany: "You can attach up to 3 images.",
|
|
40
|
+
imageFailed: "That image could not be uploaded. Please try again.",
|
|
41
|
+
likeAnswer: "Good answer",
|
|
42
|
+
dislikeAnswer: "Bad answer",
|
|
43
|
+
answering: "Answering",
|
|
44
|
+
aiDisclaimer: "{app} is AI and can make mistakes",
|
|
45
|
+
consentNotice: "Your messages are processed to answer your question.",
|
|
46
|
+
consentWithPolicy: "{policy}",
|
|
47
|
+
disclaimerJoin: " \xB7 ",
|
|
48
|
+
privacyPolicy: "Privacy Policy",
|
|
49
|
+
poweredBy: "Powered by ARCY",
|
|
50
|
+
genericError: "Something went wrong. Please try again.",
|
|
51
|
+
userCapReached: "You've reached today's limit for questions. Please try again tomorrow.",
|
|
52
|
+
imageConversation: "Image",
|
|
53
|
+
searchedSourcesLabel: "Searched {count} sources",
|
|
54
|
+
sourcesLabel: "Sources:",
|
|
55
|
+
flowOfferHint: "You can do this in one click with the Quick Flow below.",
|
|
56
|
+
fillConfirm: "Confirm",
|
|
57
|
+
fillYes: "Yes",
|
|
58
|
+
fillNo: "No",
|
|
59
|
+
fillRequired: "This field needs an answer.",
|
|
60
|
+
fillInvalidNumber: "Please enter a number.",
|
|
61
|
+
fillNumberTooSmall: "Please enter {min} or more.",
|
|
62
|
+
fillNumberTooLarge: "Please enter {max} or less.",
|
|
63
|
+
fillInvalidEmail: "Please enter a valid email address.",
|
|
64
|
+
fillInvalidUrl: "Please enter a valid link.",
|
|
65
|
+
fillInvalidDate: "Please enter a valid date.",
|
|
66
|
+
fillInvalidTime: "Please enter a valid time.",
|
|
67
|
+
fillDateTooEarly: "Please pick {min} or later.",
|
|
68
|
+
fillDateTooLate: "Please pick {max} or earlier.",
|
|
69
|
+
fillTooLong: "Please keep it under {max} characters.",
|
|
70
|
+
fillInvalidOption: "Please pick one of the options.",
|
|
71
|
+
fillInvalid: "That does not look right. Please try again."
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/shell/drag.ts
|
|
75
|
+
function carriesFiles(event) {
|
|
76
|
+
const transfer = event.dataTransfer;
|
|
77
|
+
if (!transfer) return false;
|
|
78
|
+
const types = transfer.types;
|
|
79
|
+
if (!types) return false;
|
|
80
|
+
for (let index = 0; index < types.length; index += 1) {
|
|
81
|
+
if (types[index] === "Files") return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
function droppedFiles(event) {
|
|
86
|
+
const transfer = event.dataTransfer;
|
|
87
|
+
return Array.from(transfer?.files ?? []);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/shell/tooltip.ts
|
|
91
|
+
var TOOLTIP_CSS = `
|
|
92
|
+
[data-tip] {
|
|
93
|
+
position: relative;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
[data-tip]::after {
|
|
97
|
+
content: attr(data-tip);
|
|
98
|
+
position: absolute;
|
|
99
|
+
left: 50%;
|
|
100
|
+
bottom: calc(100% + 6px);
|
|
101
|
+
transform: translateX(-50%);
|
|
102
|
+
z-index: 2;
|
|
103
|
+
padding: 4px 8px;
|
|
104
|
+
border-radius: 6px;
|
|
105
|
+
background: var(--_arcy-main-text, #101828);
|
|
106
|
+
color: var(--_arcy-main-bg, #ffffff);
|
|
107
|
+
font-size: 11px;
|
|
108
|
+
line-height: 1.3;
|
|
109
|
+
white-space: nowrap;
|
|
110
|
+
pointer-events: none;
|
|
111
|
+
opacity: 0;
|
|
112
|
+
visibility: hidden;
|
|
113
|
+
transition: opacity 120ms ease;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
[data-tip][data-tip-placement="below"]::after {
|
|
117
|
+
bottom: auto;
|
|
118
|
+
top: calc(100% + 6px);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Alignment, for a control near an edge the panel clips (D895). Centring a
|
|
122
|
+
bubble on the leftmost header icon puts half of it outside the panel, and
|
|
123
|
+
the panel clips its overflow, so the half outside is simply gone. No
|
|
124
|
+
z-index can fix that: the bubble is not behind anything, it is cut off.
|
|
125
|
+
These pin the bubble to the control's own edge instead. */
|
|
126
|
+
[data-tip][data-tip-align="start"]::after {
|
|
127
|
+
left: 0;
|
|
128
|
+
transform: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
[data-tip][data-tip-align="end"]::after {
|
|
132
|
+
left: auto;
|
|
133
|
+
right: 0;
|
|
134
|
+
transform: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
[data-tip]:hover::after,
|
|
138
|
+
[data-tip]:focus-visible::after {
|
|
139
|
+
opacity: 1;
|
|
140
|
+
visibility: visible;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* A coarse pointer has no hover: the tooltip would latch open on tap and
|
|
144
|
+
sit over the thing the visitor just pressed. */
|
|
145
|
+
@media (hover: none) {
|
|
146
|
+
[data-tip]::after {
|
|
147
|
+
display: none;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@media (prefers-reduced-motion: reduce) {
|
|
152
|
+
[data-tip]::after {
|
|
153
|
+
transition: none;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
`;
|
|
157
|
+
|
|
158
|
+
// src/shell/viewport.ts
|
|
159
|
+
var MOBILE_BREAKPOINT = 640;
|
|
160
|
+
var MOBILE_MARGIN = 12;
|
|
161
|
+
var PANEL_MARGIN = 16;
|
|
162
|
+
var PANEL_GAP = 8;
|
|
163
|
+
var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
164
|
+
function readSize(win) {
|
|
165
|
+
const el = win?.document?.documentElement;
|
|
166
|
+
return {
|
|
167
|
+
width: positive(el?.clientWidth) || positive(win?.innerWidth),
|
|
168
|
+
height: positive(el?.clientHeight) || positive(win?.innerHeight)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function keyboardOffset(win) {
|
|
172
|
+
const vv = win?.visualViewport;
|
|
173
|
+
if (!vv) return 0;
|
|
174
|
+
const layoutHeight = readSize(win).height;
|
|
175
|
+
const visualHeight = positive(vv.height);
|
|
176
|
+
if (!layoutHeight || !visualHeight) return 0;
|
|
177
|
+
const top = typeof vv.offsetTop === "number" && Number.isFinite(vv.offsetTop) ? vv.offsetTop : 0;
|
|
178
|
+
const covered = layoutHeight - (visualHeight + top);
|
|
179
|
+
if (!Number.isFinite(covered) || covered < 24) return 0;
|
|
180
|
+
return Math.min(covered, layoutHeight);
|
|
181
|
+
}
|
|
182
|
+
function readViewport(win) {
|
|
183
|
+
const size = readSize(win);
|
|
184
|
+
return {
|
|
185
|
+
...size,
|
|
186
|
+
isMobile: size.width > 0 && size.width < MOBILE_BREAKPOINT,
|
|
187
|
+
keyboardOffset: keyboardOffset(win)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
var FALLBACK_WIDTH_PX = 560;
|
|
191
|
+
var FALLBACK_MAX_HEIGHT_PX = 640;
|
|
192
|
+
function surfaceWidth(widthPercent, viewport) {
|
|
193
|
+
const available = positive(viewport.width);
|
|
194
|
+
if (!available) return FALLBACK_WIDTH_PX;
|
|
195
|
+
if (available < MOBILE_BREAKPOINT) {
|
|
196
|
+
return Math.max(0, available - MOBILE_MARGIN * 2);
|
|
197
|
+
}
|
|
198
|
+
const target = Math.round(widthPercent / 100 * available);
|
|
199
|
+
return Math.min(target, available - PANEL_MARGIN * 2);
|
|
200
|
+
}
|
|
201
|
+
function panelHeight(maxHeightPercent, viewportHeight, barBottom) {
|
|
202
|
+
const height = positive(viewportHeight);
|
|
203
|
+
if (!height) return FALLBACK_MAX_HEIGHT_PX;
|
|
204
|
+
const target = Math.round(maxHeightPercent / 100 * height);
|
|
205
|
+
const available = height - barBottom - PANEL_MARGIN;
|
|
206
|
+
return Math.max(0, Math.min(target, available));
|
|
207
|
+
}
|
|
208
|
+
function dragTransform(offset) {
|
|
209
|
+
if (offset.x === 0 && offset.y === 0) return "none";
|
|
210
|
+
return `translate(${offset.x}px, ${offset.y}px)`;
|
|
211
|
+
}
|
|
212
|
+
function clampOffsetX(x, width, viewportWidth) {
|
|
213
|
+
const available = positive(viewportWidth);
|
|
214
|
+
if (!available) return x;
|
|
215
|
+
const maxX = Math.max(0, (available - positive(width)) / 2);
|
|
216
|
+
return Math.min(maxX, Math.max(-maxX, x));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export { ATTACHABLE_TYPES, BAR_TRAY_CLASS, CHAT_CONTRACT, CHAT_GLOBAL, FALLBACK_CHROME, FONT_KEY_PROPERTY, MOBILE_MARGIN, PANEL_GAP, PANEL_MARGIN, POLICY_LINK_CLASS, TOOLTIP_CSS, carriesFiles, clampOffsetX, dragTransform, droppedFiles, panelHeight, readViewport, surfaceWidth };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { SELECTOR_CANDIDATES_MAX, FINGERPRINT_TEXT_MAX } from './chunk-NY3NXM2V.js';
|
|
2
|
+
|
|
3
|
+
/* arcy.js — https://arcyai.com */
|
|
4
|
+
|
|
5
|
+
// src/picker/message.ts
|
|
6
|
+
var DESIGN_MODE_PROTOCOL = 2;
|
|
7
|
+
var DESIGN_MODE_READY = "arcy:design-mode:ready";
|
|
8
|
+
var DESIGN_MODE_PICK = "arcy:design-mode:pick";
|
|
9
|
+
var DESIGN_MODE_EXIT = "arcy:design-mode:exit";
|
|
10
|
+
var DESIGN_MODE_SCREENSHOT = "arcy:design-mode:screenshot";
|
|
11
|
+
var TAG_MAX = 64;
|
|
12
|
+
var ROUTE_MAX = 2048;
|
|
13
|
+
var SELECTOR_MAX = 512;
|
|
14
|
+
var ORDINAL_MAX = 1e4;
|
|
15
|
+
var SIBLING_INDEX_MAX = 1e5;
|
|
16
|
+
var SCREENSHOT_DATA_URL_MAX = 4e6;
|
|
17
|
+
var SCREENSHOT_PREFIX = "data:image/png;base64,";
|
|
18
|
+
var MATCH_COUNT_MAX = 1e6;
|
|
19
|
+
function isRecord(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
function isBoundedString(value, max) {
|
|
23
|
+
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
24
|
+
}
|
|
25
|
+
function isOptionalBoundedString(value, max) {
|
|
26
|
+
return value === void 0 || isBoundedString(value, max);
|
|
27
|
+
}
|
|
28
|
+
function isBoundedInteger(value, min, max) {
|
|
29
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
30
|
+
}
|
|
31
|
+
function readCore(value) {
|
|
32
|
+
if (!isRecord(value)) return null;
|
|
33
|
+
if (!isBoundedString(value.tag, TAG_MAX)) return null;
|
|
34
|
+
if (!isBoundedInteger(value.siblingIndex, 0, SIBLING_INDEX_MAX)) return null;
|
|
35
|
+
const optional = [
|
|
36
|
+
"text",
|
|
37
|
+
"ariaLabel",
|
|
38
|
+
"placeholder",
|
|
39
|
+
"id",
|
|
40
|
+
"role",
|
|
41
|
+
"testId",
|
|
42
|
+
"ancestorTag",
|
|
43
|
+
"ancestorAttr",
|
|
44
|
+
"ancestorValue"
|
|
45
|
+
];
|
|
46
|
+
for (const key of optional) {
|
|
47
|
+
if (!isOptionalBoundedString(value[key], FINGERPRINT_TEXT_MAX)) return null;
|
|
48
|
+
}
|
|
49
|
+
const core = {
|
|
50
|
+
tag: value.tag,
|
|
51
|
+
siblingIndex: value.siblingIndex
|
|
52
|
+
};
|
|
53
|
+
for (const key of optional) {
|
|
54
|
+
const field = value[key];
|
|
55
|
+
if (typeof field === "string") core[key] = field;
|
|
56
|
+
}
|
|
57
|
+
return core;
|
|
58
|
+
}
|
|
59
|
+
function readTarget(value) {
|
|
60
|
+
if (!isRecord(value)) return null;
|
|
61
|
+
if (value.v !== 1) return null;
|
|
62
|
+
const core = readCore(value.core);
|
|
63
|
+
if (!core) return null;
|
|
64
|
+
const { selector, selectorCandidates, ordinal, precision, dynamicText } = value;
|
|
65
|
+
if (selector !== null && !isBoundedString(selector, SELECTOR_MAX)) return null;
|
|
66
|
+
if (!Array.isArray(selectorCandidates)) return null;
|
|
67
|
+
if (selectorCandidates.length > SELECTOR_CANDIDATES_MAX) return null;
|
|
68
|
+
if (!selectorCandidates.every((c) => isBoundedString(c, SELECTOR_MAX)))
|
|
69
|
+
return null;
|
|
70
|
+
if (!isBoundedInteger(ordinal, 1, ORDINAL_MAX)) return null;
|
|
71
|
+
if (!isBoundedInteger(precision, 1, 5)) return null;
|
|
72
|
+
if (typeof dynamicText !== "boolean") return null;
|
|
73
|
+
return {
|
|
74
|
+
v: 1,
|
|
75
|
+
core,
|
|
76
|
+
selector,
|
|
77
|
+
selectorCandidates,
|
|
78
|
+
ordinal,
|
|
79
|
+
precision,
|
|
80
|
+
dynamicText
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function readDesignModeMessage(data) {
|
|
84
|
+
if (!isRecord(data)) return null;
|
|
85
|
+
if (data.protocol !== DESIGN_MODE_PROTOCOL) return null;
|
|
86
|
+
switch (data.type) {
|
|
87
|
+
case DESIGN_MODE_READY:
|
|
88
|
+
return { type: DESIGN_MODE_READY, protocol: DESIGN_MODE_PROTOCOL };
|
|
89
|
+
case DESIGN_MODE_EXIT:
|
|
90
|
+
return { type: DESIGN_MODE_EXIT, protocol: DESIGN_MODE_PROTOCOL };
|
|
91
|
+
case DESIGN_MODE_PICK: {
|
|
92
|
+
const target = readTarget(data.target);
|
|
93
|
+
if (!target) return null;
|
|
94
|
+
const { route, matchCount } = data;
|
|
95
|
+
if (!isBoundedString(route, ROUTE_MAX)) return null;
|
|
96
|
+
if (!route.startsWith("/")) return null;
|
|
97
|
+
if (route.startsWith("//")) return null;
|
|
98
|
+
if (!isBoundedInteger(matchCount, 0, MATCH_COUNT_MAX)) return null;
|
|
99
|
+
return {
|
|
100
|
+
type: DESIGN_MODE_PICK,
|
|
101
|
+
protocol: DESIGN_MODE_PROTOCOL,
|
|
102
|
+
target,
|
|
103
|
+
route,
|
|
104
|
+
matchCount
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
case DESIGN_MODE_SCREENSHOT: {
|
|
108
|
+
const { screenshot } = data;
|
|
109
|
+
if (!isBoundedString(screenshot, SCREENSHOT_DATA_URL_MAX)) return null;
|
|
110
|
+
if (!screenshot.startsWith(SCREENSHOT_PREFIX)) return null;
|
|
111
|
+
return {
|
|
112
|
+
type: DESIGN_MODE_SCREENSHOT,
|
|
113
|
+
protocol: DESIGN_MODE_PROTOCOL,
|
|
114
|
+
screenshot
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export { DESIGN_MODE_EXIT, DESIGN_MODE_PICK, DESIGN_MODE_PROTOCOL, DESIGN_MODE_READY, DESIGN_MODE_SCREENSHOT, readDesignModeMessage };
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/* arcy.js — https://arcyai.com */
|
|
2
|
+
|
|
3
|
+
// src/shell/fill-contract.ts
|
|
4
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
5
|
+
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
6
|
+
var TIME_RE = /^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$/;
|
|
7
|
+
var DATETIME_RE = /^\d{4}-\d{2}-\d{2}T([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$/;
|
|
8
|
+
var MONTH_RE = /^\d{4}-\d{2}$/;
|
|
9
|
+
var WEEK_RE = /^\d{4}-W\d{2}$/;
|
|
10
|
+
var TEL_RE = /^\+?[\d\s().-]{3,}$/;
|
|
11
|
+
function isRealDate(value) {
|
|
12
|
+
const time = Date.parse(value);
|
|
13
|
+
return Number.isFinite(time);
|
|
14
|
+
}
|
|
15
|
+
function numberError(value, request) {
|
|
16
|
+
if (request.min !== null) {
|
|
17
|
+
const min = Number(request.min);
|
|
18
|
+
if (Number.isFinite(min) && value < min) return "number_too_small";
|
|
19
|
+
}
|
|
20
|
+
if (request.max !== null) {
|
|
21
|
+
const max = Number(request.max);
|
|
22
|
+
if (Number.isFinite(max) && value > max) return "number_too_large";
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function padTime(text) {
|
|
27
|
+
return /^\d:/.test(text) ? `0${text}` : text;
|
|
28
|
+
}
|
|
29
|
+
function dateRangeError(value, request) {
|
|
30
|
+
if (request.min !== null && request.min.length > 0 && value < request.min) {
|
|
31
|
+
return "date_too_early";
|
|
32
|
+
}
|
|
33
|
+
if (request.max !== null && request.max.length > 0 && value > request.max) {
|
|
34
|
+
return "date_too_late";
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function validateFillReply(reply, request) {
|
|
39
|
+
const value = request.kind === "password" ? reply : reply.trim();
|
|
40
|
+
if (value.length === 0) {
|
|
41
|
+
return request.required ? "required" : null;
|
|
42
|
+
}
|
|
43
|
+
if (request.maxLength !== null && value.length > request.maxLength) {
|
|
44
|
+
return "too_long";
|
|
45
|
+
}
|
|
46
|
+
switch (request.kind) {
|
|
47
|
+
case "number":
|
|
48
|
+
case "range": {
|
|
49
|
+
const parsed = Number(value);
|
|
50
|
+
if (!Number.isFinite(parsed)) return "invalid_number";
|
|
51
|
+
return numberError(parsed, request);
|
|
52
|
+
}
|
|
53
|
+
case "email":
|
|
54
|
+
return EMAIL_RE.test(value) ? null : "invalid_email";
|
|
55
|
+
case "url": {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = new URL(value);
|
|
58
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:" ? null : "invalid_url";
|
|
59
|
+
} catch {
|
|
60
|
+
return "invalid_url";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
case "tel":
|
|
64
|
+
return TEL_RE.test(value) ? null : "invalid";
|
|
65
|
+
case "date":
|
|
66
|
+
if (!DATE_RE.test(value) || !isRealDate(value)) return "invalid_date";
|
|
67
|
+
return dateRangeError(value, request);
|
|
68
|
+
case "datetime":
|
|
69
|
+
if (!DATETIME_RE.test(value)) return "invalid_date";
|
|
70
|
+
return dateRangeError(value, request);
|
|
71
|
+
case "month":
|
|
72
|
+
if (!MONTH_RE.test(value)) return "invalid_date";
|
|
73
|
+
return dateRangeError(value, request);
|
|
74
|
+
case "week":
|
|
75
|
+
if (!WEEK_RE.test(value)) return "invalid_date";
|
|
76
|
+
return dateRangeError(value, request);
|
|
77
|
+
case "time": {
|
|
78
|
+
if (!TIME_RE.test(value)) return "invalid_time";
|
|
79
|
+
return dateRangeError(padTime(value), {
|
|
80
|
+
...request,
|
|
81
|
+
min: request.min !== null ? padTime(request.min) : null,
|
|
82
|
+
max: request.max !== null ? padTime(request.max) : null
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
case "select":
|
|
86
|
+
case "radio": {
|
|
87
|
+
const options = request.options ?? [];
|
|
88
|
+
return options.some((option) => option.value === value) ? null : "invalid_option";
|
|
89
|
+
}
|
|
90
|
+
case "checkbox":
|
|
91
|
+
return value === "true" || value === "false" ? null : "invalid_option";
|
|
92
|
+
default: {
|
|
93
|
+
if (request.pattern) {
|
|
94
|
+
try {
|
|
95
|
+
if (!new RegExp(`^(?:${request.pattern})$`).test(value)) {
|
|
96
|
+
return "invalid";
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function normalizeOptionReply(reply, options) {
|
|
106
|
+
if (!options || options.length === 0) return reply;
|
|
107
|
+
const value = reply.trim();
|
|
108
|
+
if (options.some((option) => option.value === value)) return value;
|
|
109
|
+
const lower = value.toLowerCase();
|
|
110
|
+
const byLabel = options.find(
|
|
111
|
+
(option) => option.label.trim().toLowerCase() === lower
|
|
112
|
+
);
|
|
113
|
+
return byLabel ? byLabel.value : reply;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/flow/api.ts
|
|
117
|
+
var DEFAULT_API_BASE = "https://api.arcyai.com";
|
|
118
|
+
var FLOW_CATALOG_PATH = "/api/v1/sdk/flows/catalog";
|
|
119
|
+
var FLOW_RUN_PATH = "/api/v1/sdk/flows";
|
|
120
|
+
var FLOW_FETCH_TIMEOUT_MS = 8e3;
|
|
121
|
+
function resolveFetch(fetchImpl) {
|
|
122
|
+
if (fetchImpl !== void 0) return fetchImpl;
|
|
123
|
+
return typeof fetch !== "undefined" ? fetch.bind(globalThis) : null;
|
|
124
|
+
}
|
|
125
|
+
function bounded(promise, timeoutMs, setTimeoutImpl, onTimeout) {
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
let settled = false;
|
|
128
|
+
const settle = (value) => {
|
|
129
|
+
if (settled) return;
|
|
130
|
+
settled = true;
|
|
131
|
+
resolve(value);
|
|
132
|
+
};
|
|
133
|
+
promise.then(settle, () => settle(onTimeout));
|
|
134
|
+
try {
|
|
135
|
+
setTimeoutImpl(() => settle(onTimeout), timeoutMs);
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function isCatalogEntry(value) {
|
|
141
|
+
if (!value || typeof value !== "object") return false;
|
|
142
|
+
const v = value;
|
|
143
|
+
return typeof v.cvid === "string" && (v.publicName === null || typeof v.publicName === "string") && (v.summary === null || typeof v.summary === "string") && (v.firstStepRoute === void 0 || v.firstStepRoute === null || typeof v.firstStepRoute === "string");
|
|
144
|
+
}
|
|
145
|
+
function isStarterFlow(value) {
|
|
146
|
+
if (!value || typeof value !== "object") return false;
|
|
147
|
+
const v = value;
|
|
148
|
+
return typeof v.cvid === "string" && typeof v.publicName === "string" && v.publicName.trim().length > 0 && (v.summary === null || v.summary === void 0 || typeof v.summary === "string");
|
|
149
|
+
}
|
|
150
|
+
function readCatalog(body) {
|
|
151
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
152
|
+
const raw = body.flows;
|
|
153
|
+
if (!Array.isArray(raw)) return null;
|
|
154
|
+
const flows = [];
|
|
155
|
+
for (const entry of raw) {
|
|
156
|
+
if (isCatalogEntry(entry)) {
|
|
157
|
+
flows.push({ ...entry, firstStepRoute: entry.firstStepRoute ?? null });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const rawStarters = body.starterFlows;
|
|
161
|
+
const starterFlows = Array.isArray(rawStarters) ? rawStarters.filter(isStarterFlow).map((entry) => ({
|
|
162
|
+
cvid: entry.cvid,
|
|
163
|
+
publicName: entry.publicName,
|
|
164
|
+
summary: entry.summary ?? null
|
|
165
|
+
})) : [];
|
|
166
|
+
return { flows, starterFlows };
|
|
167
|
+
}
|
|
168
|
+
function readRun(body) {
|
|
169
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
170
|
+
return readFlowDetail(body.flow);
|
|
171
|
+
}
|
|
172
|
+
function readFlowDetail(flow) {
|
|
173
|
+
if (!flow || typeof flow !== "object" || Array.isArray(flow)) return null;
|
|
174
|
+
const f = flow;
|
|
175
|
+
if (typeof f.cvid !== "string" || !Array.isArray(f.steps)) return null;
|
|
176
|
+
return flow;
|
|
177
|
+
}
|
|
178
|
+
function headers(sessionToken) {
|
|
179
|
+
return {
|
|
180
|
+
"Content-Type": "application/json",
|
|
181
|
+
Authorization: `Bearer ${sessionToken}`
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function fetchFlowCatalog(options) {
|
|
185
|
+
const {
|
|
186
|
+
apiBase = DEFAULT_API_BASE,
|
|
187
|
+
sessionToken,
|
|
188
|
+
sessionId,
|
|
189
|
+
setTimeoutImpl = (fn, ms) => setTimeout(fn, ms),
|
|
190
|
+
timeoutMs = FLOW_FETCH_TIMEOUT_MS
|
|
191
|
+
} = options;
|
|
192
|
+
const fetchImpl = resolveFetch(options.fetchImpl);
|
|
193
|
+
if (!fetchImpl) return { ok: false, reason: "unusable" };
|
|
194
|
+
const attempt = (async () => {
|
|
195
|
+
let response;
|
|
196
|
+
try {
|
|
197
|
+
response = await fetchImpl(`${apiBase}${FLOW_CATALOG_PATH}`, {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers: headers(sessionToken),
|
|
200
|
+
body: JSON.stringify(sessionId ? { sessionId } : {}),
|
|
201
|
+
credentials: "omit"
|
|
202
|
+
});
|
|
203
|
+
} catch {
|
|
204
|
+
return { ok: false, reason: "network" };
|
|
205
|
+
}
|
|
206
|
+
if (!response.ok) return { ok: false, reason: "rejected" };
|
|
207
|
+
let parsed;
|
|
208
|
+
try {
|
|
209
|
+
parsed = await response.json();
|
|
210
|
+
} catch {
|
|
211
|
+
return { ok: false, reason: "unusable" };
|
|
212
|
+
}
|
|
213
|
+
const catalog = readCatalog(parsed);
|
|
214
|
+
return catalog ? { ok: true, ...catalog } : { ok: false, reason: "unusable" };
|
|
215
|
+
})();
|
|
216
|
+
return bounded(attempt, timeoutMs, setTimeoutImpl, {
|
|
217
|
+
ok: false,
|
|
218
|
+
reason: "network"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async function fetchFlowRun(flowCvid, options) {
|
|
222
|
+
const {
|
|
223
|
+
apiBase = DEFAULT_API_BASE,
|
|
224
|
+
sessionToken,
|
|
225
|
+
sessionId,
|
|
226
|
+
setTimeoutImpl = (fn, ms) => setTimeout(fn, ms),
|
|
227
|
+
timeoutMs = FLOW_FETCH_TIMEOUT_MS
|
|
228
|
+
} = options;
|
|
229
|
+
const fetchImpl = resolveFetch(options.fetchImpl);
|
|
230
|
+
if (!fetchImpl) return { ok: false, reason: "unusable" };
|
|
231
|
+
const attempt = (async () => {
|
|
232
|
+
let response;
|
|
233
|
+
try {
|
|
234
|
+
response = await fetchImpl(
|
|
235
|
+
`${apiBase}${FLOW_RUN_PATH}/${encodeURIComponent(flowCvid)}/run`,
|
|
236
|
+
{
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: headers(sessionToken),
|
|
239
|
+
body: JSON.stringify(sessionId ? { sessionId } : {}),
|
|
240
|
+
credentials: "omit"
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
} catch {
|
|
244
|
+
return { ok: false, reason: "network" };
|
|
245
|
+
}
|
|
246
|
+
if (response.status === 404) return { ok: false, reason: "not_found" };
|
|
247
|
+
if (!response.ok) return { ok: false, reason: "rejected" };
|
|
248
|
+
let parsed;
|
|
249
|
+
try {
|
|
250
|
+
parsed = await response.json();
|
|
251
|
+
} catch {
|
|
252
|
+
return { ok: false, reason: "unusable" };
|
|
253
|
+
}
|
|
254
|
+
const flow = readRun(parsed);
|
|
255
|
+
return flow ? { ok: true, flow } : { ok: false, reason: "unusable" };
|
|
256
|
+
})();
|
|
257
|
+
return bounded(attempt, timeoutMs, setTimeoutImpl, {
|
|
258
|
+
ok: false,
|
|
259
|
+
reason: "network"
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export { fetchFlowCatalog, fetchFlowRun, normalizeOptionReply, readFlowDetail, validateFillReply };
|