myoperator-ui 0.0.221 → 0.0.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +293 -81
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17765,6 +17765,101 @@ export const IvrBotConfig = React.forwardRef(
|
|
|
17765
17765
|
);
|
|
17766
17766
|
|
|
17767
17767
|
IvrBotConfig.displayName = "IvrBotConfig";
|
|
17768
|
+
`, prefix)
|
|
17769
|
+
},
|
|
17770
|
+
{
|
|
17771
|
+
name: "create-function-validation.ts",
|
|
17772
|
+
content: prefixTailwindClasses(`import type { KeyValuePair } from "./types";
|
|
17773
|
+
|
|
17774
|
+
/** HTTP(S) URL prefix check \u2014 used for Create Function API URL field. */
|
|
17775
|
+
export const URL_REGEX = /^https?:\\/\\//;
|
|
17776
|
+
|
|
17777
|
+
export const HEADER_KEY_REGEX = /^[!#$%&'*+\\-.^_\`|~0-9a-zA-Z]+$/;
|
|
17778
|
+
|
|
17779
|
+
/** Single message for invalid header keys (KeyValueTable + submit validation). */
|
|
17780
|
+
export const HEADER_KEY_INVALID_MESSAGE =
|
|
17781
|
+
"Invalid header key. Use only alphanumeric and !#$%&'*+-.^_\`|~ characters.";
|
|
17782
|
+
|
|
17783
|
+
// Query parameter validation (aligned with apiIntegrationSchema.queryParams)
|
|
17784
|
+
export const QUERY_PARAM_KEY_MAX = 512;
|
|
17785
|
+
export const QUERY_PARAM_VALUE_MAX = 2048;
|
|
17786
|
+
export const QUERY_PARAM_KEY_PATTERN = /^[a-zA-Z0-9_.\\-~]+$/;
|
|
17787
|
+
|
|
17788
|
+
export function validateQueryParamKey(key: string): string | undefined {
|
|
17789
|
+
if (!key.trim()) return "Query param key is required";
|
|
17790
|
+
if (key.length > QUERY_PARAM_KEY_MAX) return "key cannot exceed 512 characters.";
|
|
17791
|
+
if (!QUERY_PARAM_KEY_PATTERN.test(key)) return "Invalid query parameter key.";
|
|
17792
|
+
return undefined;
|
|
17793
|
+
}
|
|
17794
|
+
|
|
17795
|
+
export function validateQueryParamValue(value: string): string | undefined {
|
|
17796
|
+
if (!value.trim()) return "Query param value is required";
|
|
17797
|
+
if (value.length > QUERY_PARAM_VALUE_MAX) return "value cannot exceed 2048 characters.";
|
|
17798
|
+
return undefined;
|
|
17799
|
+
}
|
|
17800
|
+
|
|
17801
|
+
export function queryParamsHaveErrors(rows: KeyValuePair[]): boolean {
|
|
17802
|
+
return rows.some((row) => {
|
|
17803
|
+
const hasInput = row.key.trim() !== "" || row.value.trim() !== "";
|
|
17804
|
+
if (!hasInput) return false;
|
|
17805
|
+
return (
|
|
17806
|
+
validateQueryParamKey(row.key) !== undefined ||
|
|
17807
|
+
validateQueryParamValue(row.value) !== undefined
|
|
17808
|
+
);
|
|
17809
|
+
});
|
|
17810
|
+
}
|
|
17811
|
+
|
|
17812
|
+
export const URL_REQUIRED_MESSAGE = "API URL is required";
|
|
17813
|
+
export const URL_FORMAT_MESSAGE = "URL must start with http:// or https://";
|
|
17814
|
+
|
|
17815
|
+
/** On save: URL is required and must start with http:// or https:// */
|
|
17816
|
+
export function getUrlSubmitValidationError(value: string): string {
|
|
17817
|
+
const t = value.trim();
|
|
17818
|
+
if (!t) return URL_REQUIRED_MESSAGE;
|
|
17819
|
+
if (!URL_REGEX.test(t)) return URL_FORMAT_MESSAGE;
|
|
17820
|
+
return "";
|
|
17821
|
+
}
|
|
17822
|
+
|
|
17823
|
+
/** On blur while typing: empty clears; non-empty must match URL_REGEX. */
|
|
17824
|
+
export function getUrlBlurValidationError(value: string): string {
|
|
17825
|
+
if (value.trim() && !URL_REGEX.test(value.trim())) return URL_FORMAT_MESSAGE;
|
|
17826
|
+
return "";
|
|
17827
|
+
}
|
|
17828
|
+
|
|
17829
|
+
export const BODY_JSON_ERROR_MESSAGE = "Body must be valid JSON";
|
|
17830
|
+
|
|
17831
|
+
/** Empty body is valid; non-empty must parse as JSON. */
|
|
17832
|
+
export function getBodyJsonValidationError(value: string): string {
|
|
17833
|
+
if (!value.trim()) return "";
|
|
17834
|
+
try {
|
|
17835
|
+
JSON.parse(value.trim());
|
|
17836
|
+
return "";
|
|
17837
|
+
} catch {
|
|
17838
|
+
return BODY_JSON_ERROR_MESSAGE;
|
|
17839
|
+
}
|
|
17840
|
+
}
|
|
17841
|
+
|
|
17842
|
+
export type HeaderRowFieldErrors = { key?: string; value?: string };
|
|
17843
|
+
|
|
17844
|
+
/** When either key or value has text, both are required; key must match HEADER_KEY_REGEX. */
|
|
17845
|
+
export function getHeaderRowSubmitErrors(row: KeyValuePair): HeaderRowFieldErrors {
|
|
17846
|
+
const hasInput = row.key.trim() !== "" || row.value.trim() !== "";
|
|
17847
|
+
if (!hasInput) return {};
|
|
17848
|
+
const errors: HeaderRowFieldErrors = {};
|
|
17849
|
+
if (!row.key.trim()) errors.key = "Header key is required";
|
|
17850
|
+
else if (!HEADER_KEY_REGEX.test(row.key)) {
|
|
17851
|
+
errors.key = HEADER_KEY_INVALID_MESSAGE;
|
|
17852
|
+
}
|
|
17853
|
+
if (!row.value.trim()) errors.value = "Header value is required";
|
|
17854
|
+
return errors;
|
|
17855
|
+
}
|
|
17856
|
+
|
|
17857
|
+
export function headerRowsHaveSubmitErrors(rows: KeyValuePair[]): boolean {
|
|
17858
|
+
return rows.some((row) => {
|
|
17859
|
+
const e = getHeaderRowSubmitErrors(row);
|
|
17860
|
+
return Boolean(e.key || e.value);
|
|
17861
|
+
});
|
|
17862
|
+
}
|
|
17768
17863
|
`, prefix)
|
|
17769
17864
|
},
|
|
17770
17865
|
{
|
|
@@ -17792,6 +17887,20 @@ import type {
|
|
|
17792
17887
|
VariableItem,
|
|
17793
17888
|
VariableFormData,
|
|
17794
17889
|
} from "./types";
|
|
17890
|
+
import {
|
|
17891
|
+
HEADER_KEY_INVALID_MESSAGE,
|
|
17892
|
+
HEADER_KEY_REGEX,
|
|
17893
|
+
QUERY_PARAM_KEY_MAX,
|
|
17894
|
+
QUERY_PARAM_VALUE_MAX,
|
|
17895
|
+
getBodyJsonValidationError,
|
|
17896
|
+
getHeaderRowSubmitErrors,
|
|
17897
|
+
getUrlBlurValidationError,
|
|
17898
|
+
getUrlSubmitValidationError,
|
|
17899
|
+
headerRowsHaveSubmitErrors,
|
|
17900
|
+
queryParamsHaveErrors,
|
|
17901
|
+
validateQueryParamKey,
|
|
17902
|
+
validateQueryParamValue,
|
|
17903
|
+
} from "./create-function-validation";
|
|
17795
17904
|
|
|
17796
17905
|
const HTTP_METHODS: HttpMethod[] = ["GET", "POST", "PUT", "DELETE", "PATCH"];
|
|
17797
17906
|
const FUNCTION_NAME_MAX = 100;
|
|
@@ -17801,14 +17910,13 @@ const HEADER_KEY_MAX = 512;
|
|
|
17801
17910
|
const HEADER_VALUE_MAX = 2048;
|
|
17802
17911
|
|
|
17803
17912
|
const FUNCTION_NAME_REGEX = /^(?!_+$)(?=.*[a-zA-Z])[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
17913
|
+
|
|
17914
|
+
/** Spaces \u2192 underscores so users can type natural phrases without invalid-name errors. */
|
|
17915
|
+
function normalizeFunctionNameInput(value: string): string {
|
|
17916
|
+
return value.replace(/ /g, "_");
|
|
17917
|
+
}
|
|
17804
17918
|
const VARIABLE_NAME_MAX = 30;
|
|
17805
17919
|
const VARIABLE_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
17806
|
-
const URL_REGEX = /^https?:\\/\\//;
|
|
17807
|
-
const HEADER_KEY_REGEX = /^[!#$%&'*+\\-.^_\`|~0-9a-zA-Z]+$/;
|
|
17808
|
-
// Query parameter validation (aligned with apiIntegrationSchema.queryParams)
|
|
17809
|
-
const QUERY_PARAM_KEY_MAX = 512;
|
|
17810
|
-
const QUERY_PARAM_VALUE_MAX = 2048;
|
|
17811
|
-
const QUERY_PARAM_KEY_PATTERN = /^[a-zA-Z0-9_.\\-~]+$/;
|
|
17812
17920
|
|
|
17813
17921
|
const DEFAULT_SESSION_VARIABLES = [
|
|
17814
17922
|
"{{Caller number}}",
|
|
@@ -17816,19 +17924,6 @@ const DEFAULT_SESSION_VARIABLES = [
|
|
|
17816
17924
|
"{{Contact Details}}",
|
|
17817
17925
|
];
|
|
17818
17926
|
|
|
17819
|
-
function validateQueryParamKey(key: string): string | undefined {
|
|
17820
|
-
if (!key.trim()) return "Query param key is required";
|
|
17821
|
-
if (key.length > QUERY_PARAM_KEY_MAX) return "key cannot exceed 512 characters.";
|
|
17822
|
-
if (!QUERY_PARAM_KEY_PATTERN.test(key)) return "Invalid query parameter key.";
|
|
17823
|
-
return undefined;
|
|
17824
|
-
}
|
|
17825
|
-
|
|
17826
|
-
function validateQueryParamValue(value: string): string | undefined {
|
|
17827
|
-
if (!value.trim()) return "Query param value is required";
|
|
17828
|
-
if (value.length > QUERY_PARAM_VALUE_MAX) return "value cannot exceed 2048 characters.";
|
|
17829
|
-
return undefined;
|
|
17830
|
-
}
|
|
17831
|
-
|
|
17832
17927
|
function generateId() {
|
|
17833
17928
|
return Math.random().toString(36).slice(2, 9);
|
|
17834
17929
|
}
|
|
@@ -17841,6 +17936,13 @@ interface TriggerState {
|
|
|
17841
17936
|
to: number;
|
|
17842
17937
|
}
|
|
17843
17938
|
|
|
17939
|
+
/** Where to insert \`{{name}}\` after the user saves "Create new variable" */
|
|
17940
|
+
type VarInsertContext =
|
|
17941
|
+
| { kind: "url"; from: number; to: number }
|
|
17942
|
+
| { kind: "body"; from: number; to: number }
|
|
17943
|
+
| { kind: "header"; rowId: string; from: number; to: number }
|
|
17944
|
+
| { kind: "query"; rowId: string; from: number; to: number };
|
|
17945
|
+
|
|
17844
17946
|
function detectVarTrigger(value: string, cursor: number): TriggerState | null {
|
|
17845
17947
|
const before = value.slice(0, cursor);
|
|
17846
17948
|
const match = /\\{\\{([^}]*)$/.exec(before);
|
|
@@ -18210,7 +18312,7 @@ function VariableInput({
|
|
|
18210
18312
|
onChange: (v: string) => void;
|
|
18211
18313
|
sessionVariables: string[];
|
|
18212
18314
|
variableGroups?: VariableGroup[];
|
|
18213
|
-
onAddVariable?: () => void;
|
|
18315
|
+
onAddVariable?: (range: { from: number; to: number }) => void;
|
|
18214
18316
|
onEditVariable?: (variable: string) => void;
|
|
18215
18317
|
placeholder?: string;
|
|
18216
18318
|
maxLength?: number;
|
|
@@ -18373,7 +18475,11 @@ function VariableInput({
|
|
|
18373
18475
|
variableGroups={trigger ? variableGroups : undefined}
|
|
18374
18476
|
filterQuery={trigger?.query ?? ""}
|
|
18375
18477
|
onSelect={handleSelect}
|
|
18376
|
-
onAddVariable={
|
|
18478
|
+
onAddVariable={
|
|
18479
|
+
onAddVariable && trigger
|
|
18480
|
+
? () => onAddVariable({ from: trigger.from, to: trigger.to })
|
|
18481
|
+
: undefined
|
|
18482
|
+
}
|
|
18377
18483
|
onEditVariable={onEditVariable}
|
|
18378
18484
|
style={popupStyle}
|
|
18379
18485
|
/>
|
|
@@ -18428,7 +18534,7 @@ function KeyValueTable({
|
|
|
18428
18534
|
keyRegexError?: string;
|
|
18429
18535
|
sessionVariables?: string[];
|
|
18430
18536
|
variableGroups?: VariableGroup[];
|
|
18431
|
-
onAddVariable?: () => void;
|
|
18537
|
+
onAddVariable?: (ctx: { rowId: string; from: number; to: number }) => void;
|
|
18432
18538
|
onEditVariable?: (variable: string) => void;
|
|
18433
18539
|
disabled?: boolean;
|
|
18434
18540
|
}) {
|
|
@@ -18513,7 +18619,11 @@ function KeyValueTable({
|
|
|
18513
18619
|
onChange={(v) => update(row.id, { value: v })}
|
|
18514
18620
|
sessionVariables={sessionVariables}
|
|
18515
18621
|
variableGroups={variableGroups}
|
|
18516
|
-
onAddVariable={
|
|
18622
|
+
onAddVariable={
|
|
18623
|
+
onAddVariable
|
|
18624
|
+
? (range) => onAddVariable({ rowId: row.id, ...range })
|
|
18625
|
+
: undefined
|
|
18626
|
+
}
|
|
18517
18627
|
onEditVariable={onEditVariable}
|
|
18518
18628
|
placeholder="Type {{ to add variables"
|
|
18519
18629
|
maxLength={valueMaxLength}
|
|
@@ -18632,14 +18742,38 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
18632
18742
|
const [varModalOpen, setVarModalOpen] = React.useState(false);
|
|
18633
18743
|
const [varModalMode, setVarModalMode] = React.useState<"create" | "edit">("create");
|
|
18634
18744
|
const [varModalInitialData, setVarModalInitialData] = React.useState<VariableItem | undefined>();
|
|
18745
|
+
/** Field + \`{{\u2026\` range to replace with \`{{name}}\` after create saves */
|
|
18746
|
+
const [varInsertContext, setVarInsertContext] = React.useState<VarInsertContext | null>(null);
|
|
18635
18747
|
|
|
18636
|
-
const
|
|
18748
|
+
const openVariableCreateModal = React.useCallback(() => {
|
|
18637
18749
|
setVarModalMode("create");
|
|
18638
18750
|
setVarModalInitialData(undefined);
|
|
18639
18751
|
setVarModalOpen(true);
|
|
18640
|
-
};
|
|
18752
|
+
}, []);
|
|
18753
|
+
|
|
18754
|
+
const handleVarModalOpenChange = React.useCallback((next: boolean) => {
|
|
18755
|
+
setVarModalOpen(next);
|
|
18756
|
+
if (!next) setVarInsertContext(null);
|
|
18757
|
+
}, []);
|
|
18758
|
+
|
|
18759
|
+
const handleAddVariableFromHeader = React.useCallback(
|
|
18760
|
+
(ctx: { rowId: string; from: number; to: number }) => {
|
|
18761
|
+
setVarInsertContext({ kind: "header", ...ctx });
|
|
18762
|
+
openVariableCreateModal();
|
|
18763
|
+
},
|
|
18764
|
+
[openVariableCreateModal]
|
|
18765
|
+
);
|
|
18766
|
+
|
|
18767
|
+
const handleAddVariableFromQuery = React.useCallback(
|
|
18768
|
+
(ctx: { rowId: string; from: number; to: number }) => {
|
|
18769
|
+
setVarInsertContext({ kind: "query", ...ctx });
|
|
18770
|
+
openVariableCreateModal();
|
|
18771
|
+
},
|
|
18772
|
+
[openVariableCreateModal]
|
|
18773
|
+
);
|
|
18641
18774
|
|
|
18642
18775
|
const handleEditVariableClick = (variableName: string) => {
|
|
18776
|
+
setVarInsertContext(null);
|
|
18643
18777
|
const variable = variableGroups
|
|
18644
18778
|
?.flatMap((g) => g.items)
|
|
18645
18779
|
.find((item) => item.name === variableName);
|
|
@@ -18649,6 +18783,35 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
18649
18783
|
};
|
|
18650
18784
|
|
|
18651
18785
|
const handleVariableSave = (data: VariableFormData) => {
|
|
18786
|
+
const trimmedName = data.name.trim();
|
|
18787
|
+
const insertToken = \`{{\${trimmedName}}}\`;
|
|
18788
|
+
|
|
18789
|
+
if (varModalMode === "create" && varInsertContext) {
|
|
18790
|
+
const ctx = varInsertContext;
|
|
18791
|
+
if (ctx.kind === "url") {
|
|
18792
|
+
setUrl((u) => insertVar(u, insertToken, ctx.from, ctx.to));
|
|
18793
|
+
} else if (ctx.kind === "body") {
|
|
18794
|
+
setBody((b) => insertVar(b, insertToken, ctx.from, ctx.to));
|
|
18795
|
+
} else if (ctx.kind === "header") {
|
|
18796
|
+
setHeaders((rows) =>
|
|
18797
|
+
rows.map((r) =>
|
|
18798
|
+
r.id === ctx.rowId
|
|
18799
|
+
? { ...r, value: insertVar(r.value, insertToken, ctx.from, ctx.to) }
|
|
18800
|
+
: r
|
|
18801
|
+
)
|
|
18802
|
+
);
|
|
18803
|
+
} else if (ctx.kind === "query") {
|
|
18804
|
+
setQueryParams((rows) =>
|
|
18805
|
+
rows.map((r) =>
|
|
18806
|
+
r.id === ctx.rowId
|
|
18807
|
+
? { ...r, value: insertVar(r.value, insertToken, ctx.from, ctx.to) }
|
|
18808
|
+
: r
|
|
18809
|
+
)
|
|
18810
|
+
);
|
|
18811
|
+
}
|
|
18812
|
+
setVarInsertContext(null);
|
|
18813
|
+
}
|
|
18814
|
+
|
|
18652
18815
|
if (varModalMode === "create") {
|
|
18653
18816
|
onAddVariable?.(data);
|
|
18654
18817
|
} else {
|
|
@@ -18672,6 +18835,28 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
18672
18835
|
? sessionVariables.filter((v) => v.toLowerCase().includes(bodyTrigger.query))
|
|
18673
18836
|
: [];
|
|
18674
18837
|
|
|
18838
|
+
const handleAddVariableFromUrl = React.useCallback(() => {
|
|
18839
|
+
if (urlTrigger) {
|
|
18840
|
+
setVarInsertContext({
|
|
18841
|
+
kind: "url",
|
|
18842
|
+
from: urlTrigger.from,
|
|
18843
|
+
to: urlTrigger.to,
|
|
18844
|
+
});
|
|
18845
|
+
}
|
|
18846
|
+
openVariableCreateModal();
|
|
18847
|
+
}, [urlTrigger, openVariableCreateModal]);
|
|
18848
|
+
|
|
18849
|
+
const handleAddVariableFromBody = React.useCallback(() => {
|
|
18850
|
+
if (bodyTrigger) {
|
|
18851
|
+
setVarInsertContext({
|
|
18852
|
+
kind: "body",
|
|
18853
|
+
from: bodyTrigger.from,
|
|
18854
|
+
to: bodyTrigger.to,
|
|
18855
|
+
});
|
|
18856
|
+
}
|
|
18857
|
+
openVariableCreateModal();
|
|
18858
|
+
}, [bodyTrigger, openVariableCreateModal]);
|
|
18859
|
+
|
|
18675
18860
|
const computePopupStyle = (
|
|
18676
18861
|
el: HTMLTextAreaElement | HTMLInputElement,
|
|
18677
18862
|
cursor: number
|
|
@@ -18719,6 +18904,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
18719
18904
|
setUrlPopupStyle(undefined);
|
|
18720
18905
|
setBodyPopupStyle(undefined);
|
|
18721
18906
|
setTestVarValues({});
|
|
18907
|
+
setVarInsertContext(null);
|
|
18722
18908
|
}
|
|
18723
18909
|
// Re-run only when modal opens; intentionally exclude deep deps to avoid mid-session resets
|
|
18724
18910
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -18744,6 +18930,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
18744
18930
|
setUrlPopupStyle(undefined);
|
|
18745
18931
|
setBodyPopupStyle(undefined);
|
|
18746
18932
|
setTestVarValues({});
|
|
18933
|
+
setVarInsertContext(null);
|
|
18747
18934
|
}, [initialData, initialStep, initialTab]);
|
|
18748
18935
|
|
|
18749
18936
|
const handleClose = React.useCallback(() => {
|
|
@@ -18762,45 +18949,33 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
18762
18949
|
};
|
|
18763
18950
|
|
|
18764
18951
|
const validateUrl = (value: string) => {
|
|
18765
|
-
|
|
18766
|
-
setUrlError("URL must start with http:// or https://");
|
|
18767
|
-
} else {
|
|
18768
|
-
setUrlError("");
|
|
18769
|
-
}
|
|
18952
|
+
setUrlError(getUrlBlurValidationError(value));
|
|
18770
18953
|
};
|
|
18771
18954
|
|
|
18772
18955
|
const validateBody = (value: string) => {
|
|
18773
|
-
|
|
18774
|
-
try {
|
|
18775
|
-
JSON.parse(value.trim());
|
|
18776
|
-
setBodyError("");
|
|
18777
|
-
} catch {
|
|
18778
|
-
setBodyError("Body must be valid JSON");
|
|
18779
|
-
}
|
|
18780
|
-
} else {
|
|
18781
|
-
setBodyError("");
|
|
18782
|
-
}
|
|
18956
|
+
setBodyError(getBodyJsonValidationError(value));
|
|
18783
18957
|
};
|
|
18784
18958
|
|
|
18785
18959
|
const handleNext = () => {
|
|
18786
18960
|
if (disabled || (name.trim() && prompt.trim().length >= promptMinLength)) setStep(2);
|
|
18787
18961
|
};
|
|
18788
18962
|
|
|
18789
|
-
const queryParamsHaveErrors = (rows: KeyValuePair[]): boolean =>
|
|
18790
|
-
rows.some((row) => {
|
|
18791
|
-
const hasInput = row.key.trim() !== "" || row.value.trim() !== "";
|
|
18792
|
-
if (!hasInput) return false;
|
|
18793
|
-
return (
|
|
18794
|
-
validateQueryParamKey(row.key) !== undefined ||
|
|
18795
|
-
validateQueryParamValue(row.value) !== undefined
|
|
18796
|
-
);
|
|
18797
|
-
});
|
|
18798
|
-
|
|
18799
18963
|
const handleSubmit = () => {
|
|
18800
|
-
if (step
|
|
18801
|
-
|
|
18802
|
-
|
|
18803
|
-
|
|
18964
|
+
if (step !== 2) return;
|
|
18965
|
+
|
|
18966
|
+
setStep2SubmitAttempted(true);
|
|
18967
|
+
|
|
18968
|
+
const urlErr = getUrlSubmitValidationError(url);
|
|
18969
|
+
setUrlError(urlErr);
|
|
18970
|
+
|
|
18971
|
+
const bodyErr = getBodyJsonValidationError(body);
|
|
18972
|
+
setBodyError(bodyErr);
|
|
18973
|
+
|
|
18974
|
+
if (queryParamsHaveErrors(queryParams)) return;
|
|
18975
|
+
if (urlErr || bodyErr) return;
|
|
18976
|
+
|
|
18977
|
+
if (headerRowsHaveSubmitErrors(headers)) return;
|
|
18978
|
+
|
|
18804
18979
|
const data: CreateFunctionData = {
|
|
18805
18980
|
name: name.trim(),
|
|
18806
18981
|
prompt: prompt.trim(),
|
|
@@ -18829,7 +19004,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
18829
19004
|
queryParams: queryParams.map((q) => ({ ...q, value: substituteVars(q.value) })),
|
|
18830
19005
|
body: substituteVars(body),
|
|
18831
19006
|
};
|
|
18832
|
-
const response = await onTestApi(step2);
|
|
19007
|
+
const response = await onTestApi(step2, { ...testVarValues });
|
|
18833
19008
|
setApiResponse(response);
|
|
18834
19009
|
} finally {
|
|
18835
19010
|
setIsTesting(false);
|
|
@@ -18868,18 +19043,11 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
18868
19043
|
});
|
|
18869
19044
|
};
|
|
18870
19045
|
|
|
18871
|
-
const headersHaveKeyErrors = headers.some(
|
|
18872
|
-
(row) => row.key.trim() && HEADER_KEY_REGEX && !HEADER_KEY_REGEX.test(row.key)
|
|
18873
|
-
);
|
|
18874
|
-
|
|
18875
19046
|
const isStep1Valid =
|
|
18876
19047
|
name.trim().length > 0 &&
|
|
18877
19048
|
FUNCTION_NAME_REGEX.test(name.trim()) &&
|
|
18878
19049
|
prompt.trim().length >= promptMinLength;
|
|
18879
19050
|
|
|
18880
|
-
const isStep2Valid =
|
|
18881
|
-
!urlError && !bodyError && !headersHaveKeyErrors && !queryParamsHaveErrors(queryParams);
|
|
18882
|
-
|
|
18883
19051
|
const tabLabels: Record<FunctionTabType, string> = {
|
|
18884
19052
|
header: \`Header (\${headers.length})\`,
|
|
18885
19053
|
queryParams: \`Query params (\${queryParams.length})\`,
|
|
@@ -18937,10 +19105,15 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
18937
19105
|
maxLength={FUNCTION_NAME_MAX}
|
|
18938
19106
|
disabled={disabled}
|
|
18939
19107
|
onChange={(e) => {
|
|
18940
|
-
|
|
18941
|
-
|
|
19108
|
+
const normalized = normalizeFunctionNameInput(
|
|
19109
|
+
e.target.value
|
|
19110
|
+
);
|
|
19111
|
+
setName(normalized);
|
|
19112
|
+
if (nameError) validateName(normalized);
|
|
18942
19113
|
}}
|
|
18943
|
-
onBlur={(e) =>
|
|
19114
|
+
onBlur={(e) =>
|
|
19115
|
+
validateName(normalizeFunctionNameInput(e.target.value))
|
|
19116
|
+
}
|
|
18944
19117
|
placeholder="Enter name of the function"
|
|
18945
19118
|
className={cn(inputCls, "pr-16")}
|
|
18946
19119
|
/>
|
|
@@ -18982,11 +19155,13 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
18982
19155
|
<span className="text-sm text-semantic-text-muted tracking-[0.048px]">
|
|
18983
19156
|
API URL
|
|
18984
19157
|
</span>
|
|
18985
|
-
|
|
19158
|
+
<div
|
|
18986
19159
|
className={cn(
|
|
18987
|
-
"flex h-[42px] rounded border border-solid
|
|
18988
|
-
"
|
|
18989
|
-
|
|
19160
|
+
"flex h-[42px] rounded border border-solid overflow-visible bg-semantic-bg-primary",
|
|
19161
|
+
"transition-shadow",
|
|
19162
|
+
urlError
|
|
19163
|
+
? "border-semantic-error-primary focus-within:border-semantic-error-primary"
|
|
19164
|
+
: "border-semantic-border-input focus-within:border-semantic-border-input-focus focus-within:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
|
|
18990
19165
|
)}
|
|
18991
19166
|
>
|
|
18992
19167
|
{/* Method selector */}
|
|
@@ -19040,6 +19215,8 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19040
19215
|
setUrlPopupStyle(undefined);
|
|
19041
19216
|
}}
|
|
19042
19217
|
placeholder="Enter URL or Type {{ to add variables"
|
|
19218
|
+
aria-invalid={Boolean(urlError)}
|
|
19219
|
+
aria-describedby={urlError ? "fn-api-url-error" : undefined}
|
|
19043
19220
|
className={cn(
|
|
19044
19221
|
"h-full w-full px-3 text-base text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none",
|
|
19045
19222
|
disabled && "opacity-50 cursor-not-allowed"
|
|
@@ -19050,14 +19227,16 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19050
19227
|
variableGroups={urlTrigger ? variableGroups : undefined}
|
|
19051
19228
|
filterQuery={urlTrigger?.query ?? ""}
|
|
19052
19229
|
onSelect={handleUrlVarSelect}
|
|
19053
|
-
onAddVariable={onAddVariable ?
|
|
19230
|
+
onAddVariable={onAddVariable ? handleAddVariableFromUrl : undefined}
|
|
19054
19231
|
onEditVariable={onEditVariable ? handleEditVariableClick : undefined}
|
|
19055
19232
|
style={urlPopupStyle}
|
|
19056
19233
|
/>
|
|
19057
19234
|
</div>
|
|
19058
19235
|
</div>
|
|
19059
19236
|
{urlError && (
|
|
19060
|
-
<p className="m-0 text-sm text-semantic-error-primary">
|
|
19237
|
+
<p id="fn-api-url-error" className="m-0 text-sm text-semantic-error-primary">
|
|
19238
|
+
{urlError}
|
|
19239
|
+
</p>
|
|
19061
19240
|
)}
|
|
19062
19241
|
</div>
|
|
19063
19242
|
|
|
@@ -19095,10 +19274,20 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19095
19274
|
keyMaxLength={HEADER_KEY_MAX}
|
|
19096
19275
|
valueMaxLength={HEADER_VALUE_MAX}
|
|
19097
19276
|
keyRegex={HEADER_KEY_REGEX}
|
|
19098
|
-
keyRegexError=
|
|
19277
|
+
keyRegexError={HEADER_KEY_INVALID_MESSAGE}
|
|
19278
|
+
getRowErrors={(row) => {
|
|
19279
|
+
if (!step2SubmitAttempted) {
|
|
19280
|
+
const errors: RowErrors = {};
|
|
19281
|
+
if (row.key.trim() && !HEADER_KEY_REGEX.test(row.key)) {
|
|
19282
|
+
errors.key = HEADER_KEY_INVALID_MESSAGE;
|
|
19283
|
+
}
|
|
19284
|
+
return errors;
|
|
19285
|
+
}
|
|
19286
|
+
return getHeaderRowSubmitErrors(row);
|
|
19287
|
+
}}
|
|
19099
19288
|
sessionVariables={sessionVariables}
|
|
19100
19289
|
variableGroups={variableGroups}
|
|
19101
|
-
onAddVariable={
|
|
19290
|
+
onAddVariable={handleAddVariableFromHeader}
|
|
19102
19291
|
onEditVariable={handleEditVariableClick}
|
|
19103
19292
|
disabled={disabled}
|
|
19104
19293
|
/>
|
|
@@ -19120,7 +19309,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19120
19309
|
}}
|
|
19121
19310
|
sessionVariables={sessionVariables}
|
|
19122
19311
|
variableGroups={variableGroups}
|
|
19123
|
-
onAddVariable={
|
|
19312
|
+
onAddVariable={handleAddVariableFromQuery}
|
|
19124
19313
|
onEditVariable={handleEditVariableClick}
|
|
19125
19314
|
disabled={disabled}
|
|
19126
19315
|
/>
|
|
@@ -19136,6 +19325,8 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19136
19325
|
value={body}
|
|
19137
19326
|
maxLength={BODY_MAX}
|
|
19138
19327
|
disabled={disabled}
|
|
19328
|
+
aria-invalid={Boolean(bodyError)}
|
|
19329
|
+
aria-describedby={bodyError ? "fn-body-error" : undefined}
|
|
19139
19330
|
onChange={(e) => {
|
|
19140
19331
|
setBody(e.target.value);
|
|
19141
19332
|
if (bodyError) validateBody(e.target.value);
|
|
@@ -19165,13 +19356,15 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19165
19356
|
variableGroups={bodyTrigger ? variableGroups : undefined}
|
|
19166
19357
|
filterQuery={bodyTrigger?.query ?? ""}
|
|
19167
19358
|
onSelect={handleBodyVarSelect}
|
|
19168
|
-
onAddVariable={onAddVariable ?
|
|
19359
|
+
onAddVariable={onAddVariable ? handleAddVariableFromBody : undefined}
|
|
19169
19360
|
onEditVariable={onEditVariable ? handleEditVariableClick : undefined}
|
|
19170
19361
|
style={bodyPopupStyle}
|
|
19171
19362
|
/>
|
|
19172
19363
|
</div>
|
|
19173
19364
|
{bodyError && (
|
|
19174
|
-
<p className="m-0 text-sm text-semantic-error-primary">
|
|
19365
|
+
<p id="fn-body-error" className="m-0 text-sm text-semantic-error-primary">
|
|
19366
|
+
{bodyError}
|
|
19367
|
+
</p>
|
|
19175
19368
|
)}
|
|
19176
19369
|
</div>
|
|
19177
19370
|
)}
|
|
@@ -19270,10 +19463,11 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19270
19463
|
Back
|
|
19271
19464
|
</Button>
|
|
19272
19465
|
<Button
|
|
19466
|
+
type="button"
|
|
19273
19467
|
variant="default"
|
|
19274
19468
|
className="flex-1 sm:flex-none"
|
|
19275
19469
|
onClick={handleSubmit}
|
|
19276
|
-
disabled={
|
|
19470
|
+
disabled={disabled}
|
|
19277
19471
|
>
|
|
19278
19472
|
Submit
|
|
19279
19473
|
</Button>
|
|
@@ -19285,7 +19479,7 @@ export const CreateFunctionModal = React.forwardRef(
|
|
|
19285
19479
|
|
|
19286
19480
|
<VariableFormModal
|
|
19287
19481
|
open={varModalOpen}
|
|
19288
|
-
onOpenChange={
|
|
19482
|
+
onOpenChange={handleVarModalOpenChange}
|
|
19289
19483
|
mode={varModalMode}
|
|
19290
19484
|
initialData={varModalInitialData}
|
|
19291
19485
|
onSave={handleVariableSave}
|
|
@@ -20956,11 +21150,20 @@ export interface CreateFunctionData
|
|
|
20956
21150
|
extends CreateFunctionStep1Data,
|
|
20957
21151
|
CreateFunctionStep2Data {}
|
|
20958
21152
|
|
|
21153
|
+
/**
|
|
21154
|
+
* Test values for "Test Your API": keys are full placeholders as in the form
|
|
21155
|
+
* (e.g. \`"{{Caller number}}"\`), values are the strings the user entered.
|
|
21156
|
+
*/
|
|
21157
|
+
export type CreateFunctionTestValues = Record<string, string>;
|
|
21158
|
+
|
|
20959
21159
|
export interface CreateFunctionModalProps {
|
|
20960
21160
|
open: boolean;
|
|
20961
21161
|
onOpenChange: (open: boolean) => void;
|
|
20962
21162
|
onSubmit?: (data: CreateFunctionData) => void;
|
|
20963
|
-
onTestApi?: (
|
|
21163
|
+
onTestApi?: (
|
|
21164
|
+
step2: CreateFunctionStep2Data,
|
|
21165
|
+
testValues?: CreateFunctionTestValues
|
|
21166
|
+
) => Promise<string>;
|
|
20964
21167
|
/** Pre-fills all fields \u2014 use when opening the modal to edit an existing function */
|
|
20965
21168
|
initialData?: Partial<CreateFunctionData>;
|
|
20966
21169
|
/** When true, changes the modal title to "Edit Function" */
|
|
@@ -20977,7 +21180,12 @@ export interface CreateFunctionModalProps {
|
|
|
20977
21180
|
sessionVariables?: string[];
|
|
20978
21181
|
/** Grouped variables shown in the {{ autocomplete popup (overrides flat list display when provided) */
|
|
20979
21182
|
variableGroups?: VariableGroup[];
|
|
20980
|
-
/**
|
|
21183
|
+
/**
|
|
21184
|
+
* Called when user saves a new variable from the autocomplete popup.
|
|
21185
|
+
* The modal replaces the open \`{{\u2026\` fragment in the focused field with \`{{name}}\`.
|
|
21186
|
+
* When using \`variableGroups\`, merge the new item into the matching group in your state
|
|
21187
|
+
* so it appears in the dropdown on the next open.
|
|
21188
|
+
*/
|
|
20981
21189
|
onAddVariable?: (data: VariableFormData) => void;
|
|
20982
21190
|
/** Called when user edits a variable from the autocomplete popup */
|
|
20983
21191
|
onEditVariable?: (originalName: string, data: VariableFormData) => void;
|
|
@@ -21040,7 +21248,10 @@ export interface IvrBotConfigProps {
|
|
|
21040
21248
|
functionEditDisabled?: boolean;
|
|
21041
21249
|
/** Independently disables the function delete button */
|
|
21042
21250
|
functionDeleteDisabled?: boolean;
|
|
21043
|
-
onTestApi?: (
|
|
21251
|
+
onTestApi?: (
|
|
21252
|
+
step2: CreateFunctionStep2Data,
|
|
21253
|
+
testValues?: CreateFunctionTestValues
|
|
21254
|
+
) => Promise<string>;
|
|
21044
21255
|
/** Hover text for the info icon in the Functions card header */
|
|
21045
21256
|
functionsInfoTooltip?: string;
|
|
21046
21257
|
/** Hover text for the info icon in the Knowledge Base card header */
|
|
@@ -21132,6 +21343,7 @@ export type {
|
|
|
21132
21343
|
IvrBotConfigProps,
|
|
21133
21344
|
IvrBotConfigData,
|
|
21134
21345
|
CreateFunctionData,
|
|
21346
|
+
CreateFunctionTestValues,
|
|
21135
21347
|
CreateFunctionStep1Data,
|
|
21136
21348
|
CreateFunctionStep2Data,
|
|
21137
21349
|
FunctionItem,
|