myoperator-ui 0.0.210-beta.9 → 0.0.210
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 +455 -256
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13604,17 +13604,10 @@ export type {
|
|
|
13604
13604
|
{
|
|
13605
13605
|
name: "ivr-bot-config.tsx",
|
|
13606
13606
|
content: prefixTailwindClasses(`import * as React from "react";
|
|
13607
|
-
import { Info } from "lucide-react";
|
|
13608
13607
|
import { cn } from "../../../lib/utils";
|
|
13609
13608
|
import { Button } from "../button";
|
|
13610
13609
|
import { Badge } from "../badge";
|
|
13611
13610
|
import { PageHeader } from "../page-header";
|
|
13612
|
-
import {
|
|
13613
|
-
Accordion,
|
|
13614
|
-
AccordionItem,
|
|
13615
|
-
AccordionTrigger,
|
|
13616
|
-
AccordionContent,
|
|
13617
|
-
} from "../accordion";
|
|
13618
13611
|
import { BotIdentityCard } from "./bot-identity-card";
|
|
13619
13612
|
import { BotBehaviorCard } from "./bot-behavior-card";
|
|
13620
13613
|
import { KnowledgeBaseCard } from "./knowledge-base-card";
|
|
@@ -13628,115 +13621,8 @@ import type {
|
|
|
13628
13621
|
IvrBotConfigData,
|
|
13629
13622
|
CreateFunctionData,
|
|
13630
13623
|
} from "./types";
|
|
13624
|
+
import { FallbackPromptsCard } from "./fallback-prompts-card";
|
|
13631
13625
|
|
|
13632
|
-
// \u2500\u2500\u2500 Styled Textarea (still used by FallbackPromptsAccordion) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
13633
|
-
function StyledTextarea({
|
|
13634
|
-
placeholder,
|
|
13635
|
-
value,
|
|
13636
|
-
rows = 3,
|
|
13637
|
-
onChange,
|
|
13638
|
-
onBlur,
|
|
13639
|
-
disabled,
|
|
13640
|
-
className,
|
|
13641
|
-
}: {
|
|
13642
|
-
placeholder?: string;
|
|
13643
|
-
value?: string;
|
|
13644
|
-
rows?: number;
|
|
13645
|
-
onChange?: (v: string) => void;
|
|
13646
|
-
onBlur?: (v: string) => void;
|
|
13647
|
-
disabled?: boolean;
|
|
13648
|
-
className?: string;
|
|
13649
|
-
}) {
|
|
13650
|
-
return (
|
|
13651
|
-
<textarea
|
|
13652
|
-
value={value ?? ""}
|
|
13653
|
-
rows={rows}
|
|
13654
|
-
onChange={(e) => onChange?.(e.target.value)}
|
|
13655
|
-
onBlur={(e) => onBlur?.(e.target.value)}
|
|
13656
|
-
placeholder={placeholder}
|
|
13657
|
-
disabled={disabled}
|
|
13658
|
-
className={cn(
|
|
13659
|
-
"w-full px-4 py-2.5 text-base rounded border resize-none",
|
|
13660
|
-
"border-semantic-border-input bg-semantic-bg-primary",
|
|
13661
|
-
"text-semantic-text-primary placeholder:text-semantic-text-muted",
|
|
13662
|
-
"outline-none hover:border-semantic-border-input-focus",
|
|
13663
|
-
"focus:border-semantic-border-input-focus focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
|
|
13664
|
-
disabled && "opacity-50 cursor-not-allowed",
|
|
13665
|
-
className
|
|
13666
|
-
)}
|
|
13667
|
-
/>
|
|
13668
|
-
);
|
|
13669
|
-
}
|
|
13670
|
-
|
|
13671
|
-
// \u2500\u2500\u2500 Field wrapper (still used by FallbackPromptsAccordion) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
13672
|
-
function Field({
|
|
13673
|
-
label,
|
|
13674
|
-
children,
|
|
13675
|
-
}: {
|
|
13676
|
-
label: string;
|
|
13677
|
-
children: React.ReactNode;
|
|
13678
|
-
}) {
|
|
13679
|
-
return (
|
|
13680
|
-
<div className="flex flex-col gap-1.5">
|
|
13681
|
-
<label className="text-sm font-semibold text-semantic-text-secondary tracking-[0.014px]">
|
|
13682
|
-
{label}
|
|
13683
|
-
</label>
|
|
13684
|
-
{children}
|
|
13685
|
-
</div>
|
|
13686
|
-
);
|
|
13687
|
-
}
|
|
13688
|
-
|
|
13689
|
-
// \u2500\u2500\u2500 Fallback Prompts (accordion) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
13690
|
-
function FallbackPromptsAccordion({
|
|
13691
|
-
data,
|
|
13692
|
-
onChange,
|
|
13693
|
-
onAgentBusyPromptBlur,
|
|
13694
|
-
onNoExtensionFoundPromptBlur,
|
|
13695
|
-
disabled,
|
|
13696
|
-
}: {
|
|
13697
|
-
data: Partial<IvrBotConfigData>;
|
|
13698
|
-
onChange: (patch: Partial<IvrBotConfigData>) => void;
|
|
13699
|
-
onAgentBusyPromptBlur?: (value: string) => void;
|
|
13700
|
-
onNoExtensionFoundPromptBlur?: (value: string) => void;
|
|
13701
|
-
disabled?: boolean;
|
|
13702
|
-
}) {
|
|
13703
|
-
return (
|
|
13704
|
-
<div className="bg-semantic-bg-primary border border-semantic-border-layout rounded-lg overflow-hidden">
|
|
13705
|
-
<Accordion type="single">
|
|
13706
|
-
<AccordionItem value="fallback">
|
|
13707
|
-
<AccordionTrigger className="px-4 py-4 border-b border-semantic-border-layout hover:no-underline sm:px-6 sm:py-5">
|
|
13708
|
-
<span className="flex items-center gap-1.5 text-base font-semibold text-semantic-text-primary">
|
|
13709
|
-
Fallback Prompts
|
|
13710
|
-
<Info className="size-3.5 text-semantic-text-muted shrink-0" />
|
|
13711
|
-
</span>
|
|
13712
|
-
</AccordionTrigger>
|
|
13713
|
-
<AccordionContent>
|
|
13714
|
-
<div className="px-4 pt-4 pb-2 flex flex-col gap-6 sm:px-6 sm:pt-6">
|
|
13715
|
-
<Field label="Agent Busy Prompt">
|
|
13716
|
-
<StyledTextarea
|
|
13717
|
-
value={data.agentBusyPrompt ?? ""}
|
|
13718
|
-
onChange={(v) => onChange({ agentBusyPrompt: v })}
|
|
13719
|
-
onBlur={onAgentBusyPromptBlur}
|
|
13720
|
-
placeholder="Executives are busy at the moment, we will connect you soon."
|
|
13721
|
-
disabled={disabled}
|
|
13722
|
-
/>
|
|
13723
|
-
</Field>
|
|
13724
|
-
<Field label="No Extension Found">
|
|
13725
|
-
<StyledTextarea
|
|
13726
|
-
value={data.noExtensionPrompt ?? ""}
|
|
13727
|
-
onChange={(v) => onChange({ noExtensionPrompt: v })}
|
|
13728
|
-
onBlur={onNoExtensionFoundPromptBlur}
|
|
13729
|
-
placeholder="Sorry, the requested extension is currently unavailable. Let me help you directly."
|
|
13730
|
-
disabled={disabled}
|
|
13731
|
-
/>
|
|
13732
|
-
</Field>
|
|
13733
|
-
</div>
|
|
13734
|
-
</AccordionContent>
|
|
13735
|
-
</AccordionItem>
|
|
13736
|
-
</Accordion>
|
|
13737
|
-
</div>
|
|
13738
|
-
);
|
|
13739
|
-
}
|
|
13740
13626
|
|
|
13741
13627
|
// \u2500\u2500\u2500 Default data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
13742
13628
|
const DEFAULT_DATA: IvrBotConfigData = {
|
|
@@ -13780,8 +13666,14 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13780
13666
|
onDeleteFunction,
|
|
13781
13667
|
onTestApi,
|
|
13782
13668
|
functionsInfoTooltip,
|
|
13669
|
+
knowledgeBaseInfoTooltip,
|
|
13783
13670
|
functionPromptMinLength,
|
|
13784
13671
|
functionPromptMaxLength,
|
|
13672
|
+
functionEditData,
|
|
13673
|
+
systemPromptMaxLength,
|
|
13674
|
+
onSystemPromptBlur,
|
|
13675
|
+
onAgentBusyPromptBlur,
|
|
13676
|
+
onNoExtensionFoundPromptBlur,
|
|
13785
13677
|
onBack,
|
|
13786
13678
|
onPlayVoice,
|
|
13787
13679
|
onPauseVoice,
|
|
@@ -13797,9 +13689,6 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13797
13689
|
silenceTimeoutMax,
|
|
13798
13690
|
callEndThresholdMin,
|
|
13799
13691
|
callEndThresholdMax,
|
|
13800
|
-
onSystemPromptBlur,
|
|
13801
|
-
onAgentBusyPromptBlur,
|
|
13802
|
-
onNoExtensionFoundPromptBlur,
|
|
13803
13692
|
className,
|
|
13804
13693
|
},
|
|
13805
13694
|
ref
|
|
@@ -13809,6 +13698,7 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13809
13698
|
...initialData,
|
|
13810
13699
|
});
|
|
13811
13700
|
const [createFnOpen, setCreateFnOpen] = React.useState(false);
|
|
13701
|
+
const [editFnOpen, setEditFnOpen] = React.useState(false);
|
|
13812
13702
|
const [uploadOpen, setUploadOpen] = React.useState(false);
|
|
13813
13703
|
|
|
13814
13704
|
const update = (patch: Partial<IvrBotConfigData>) =>
|
|
@@ -13820,6 +13710,16 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13820
13710
|
onCreateFunction?.(fnData);
|
|
13821
13711
|
};
|
|
13822
13712
|
|
|
13713
|
+
const handleEditFunction = (id: string) => {
|
|
13714
|
+
onEditFunction?.(id);
|
|
13715
|
+
setEditFnOpen(true);
|
|
13716
|
+
};
|
|
13717
|
+
|
|
13718
|
+
const handleEditFunctionSubmit = (fnData: CreateFunctionData) => {
|
|
13719
|
+
onCreateFunction?.(fnData);
|
|
13720
|
+
setEditFnOpen(false);
|
|
13721
|
+
};
|
|
13722
|
+
|
|
13823
13723
|
return (
|
|
13824
13724
|
<div ref={ref} className={cn("flex flex-col min-h-screen bg-semantic-bg-primary", className)}>
|
|
13825
13725
|
{/* Page header */}
|
|
@@ -13876,13 +13776,22 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13876
13776
|
<BotBehaviorCard
|
|
13877
13777
|
data={data}
|
|
13878
13778
|
onChange={update}
|
|
13879
|
-
|
|
13779
|
+
onSystemPromptBlur={onSystemPromptBlur}
|
|
13880
13780
|
sessionVariables={sessionVariables}
|
|
13781
|
+
maxLength={systemPromptMaxLength}
|
|
13881
13782
|
disabled={disabled}
|
|
13882
13783
|
/>
|
|
13883
|
-
<
|
|
13884
|
-
data={
|
|
13885
|
-
|
|
13784
|
+
<FallbackPromptsCard
|
|
13785
|
+
data={{
|
|
13786
|
+
agentBusyPrompt: data.agentBusyPrompt,
|
|
13787
|
+
noExtensionFoundPrompt: data.noExtensionPrompt,
|
|
13788
|
+
}}
|
|
13789
|
+
onChange={(patch) =>
|
|
13790
|
+
update({
|
|
13791
|
+
...(patch.agentBusyPrompt !== undefined && { agentBusyPrompt: patch.agentBusyPrompt }),
|
|
13792
|
+
...(patch.noExtensionFoundPrompt !== undefined && { noExtensionPrompt: patch.noExtensionFoundPrompt }),
|
|
13793
|
+
})
|
|
13794
|
+
}
|
|
13886
13795
|
onAgentBusyPromptBlur={onAgentBusyPromptBlur}
|
|
13887
13796
|
onNoExtensionFoundPromptBlur={onNoExtensionFoundPromptBlur}
|
|
13888
13797
|
disabled={disabled}
|
|
@@ -13895,6 +13804,7 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13895
13804
|
files={data.knowledgeBaseFiles}
|
|
13896
13805
|
onAdd={() => setUploadOpen(true)}
|
|
13897
13806
|
onDownload={onDownloadKnowledgeFile}
|
|
13807
|
+
infoTooltip={knowledgeBaseInfoTooltip}
|
|
13898
13808
|
disabled={disabled}
|
|
13899
13809
|
onDelete={(id) => {
|
|
13900
13810
|
update({
|
|
@@ -13908,7 +13818,7 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13908
13818
|
<FunctionsCard
|
|
13909
13819
|
functions={data.functions}
|
|
13910
13820
|
onAddFunction={() => setCreateFnOpen(true)}
|
|
13911
|
-
onEditFunction={
|
|
13821
|
+
onEditFunction={handleEditFunction}
|
|
13912
13822
|
infoTooltip={functionsInfoTooltip}
|
|
13913
13823
|
disabled={disabled}
|
|
13914
13824
|
onDeleteFunction={(id) => {
|
|
@@ -13946,6 +13856,18 @@ export const IvrBotConfig = React.forwardRef<HTMLDivElement, IvrBotConfigProps>(
|
|
|
13946
13856
|
promptMaxLength={functionPromptMaxLength}
|
|
13947
13857
|
/>
|
|
13948
13858
|
|
|
13859
|
+
{/* Edit Function Modal */}
|
|
13860
|
+
<CreateFunctionModal
|
|
13861
|
+
open={editFnOpen}
|
|
13862
|
+
onOpenChange={setEditFnOpen}
|
|
13863
|
+
onSubmit={handleEditFunctionSubmit}
|
|
13864
|
+
onTestApi={onTestApi}
|
|
13865
|
+
initialData={functionEditData}
|
|
13866
|
+
isEditing
|
|
13867
|
+
promptMinLength={functionPromptMinLength}
|
|
13868
|
+
promptMaxLength={functionPromptMaxLength}
|
|
13869
|
+
/>
|
|
13870
|
+
|
|
13949
13871
|
{/* File Upload Modal */}
|
|
13950
13872
|
<FileUploadModal
|
|
13951
13873
|
open={uploadOpen}
|
|
@@ -13989,13 +13911,27 @@ const BODY_MAX = 4000;
|
|
|
13989
13911
|
const URL_MAX = 500;
|
|
13990
13912
|
const HEADER_KEY_MAX = 512;
|
|
13991
13913
|
const HEADER_VALUE_MAX = 2048;
|
|
13992
|
-
const QUERY_KEY_MAX = 512;
|
|
13993
|
-
const QUERY_VALUE_MAX = 2048;
|
|
13994
13914
|
|
|
13995
13915
|
const FUNCTION_NAME_REGEX = /^(?!_+$)(?=.*[a-zA-Z])[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
13996
13916
|
const URL_REGEX = /^https?:\\/\\//;
|
|
13997
13917
|
const HEADER_KEY_REGEX = /^[!#$%&'*+\\-.^_\`|~0-9a-zA-Z]+$/;
|
|
13998
|
-
|
|
13918
|
+
// Query parameter validation (aligned with apiIntegrationSchema.queryParams)
|
|
13919
|
+
const QUERY_PARAM_KEY_MAX = 512;
|
|
13920
|
+
const QUERY_PARAM_VALUE_MAX = 2048;
|
|
13921
|
+
const QUERY_PARAM_KEY_PATTERN = /^[a-zA-Z0-9_.\\-~]+$/;
|
|
13922
|
+
|
|
13923
|
+
function validateQueryParamKey(key: string): string | undefined {
|
|
13924
|
+
if (!key.trim()) return "Query param key is required";
|
|
13925
|
+
if (key.length > QUERY_PARAM_KEY_MAX) return "key cannot exceed 512 characters.";
|
|
13926
|
+
if (!QUERY_PARAM_KEY_PATTERN.test(key)) return "Invalid query parameter key.";
|
|
13927
|
+
return undefined;
|
|
13928
|
+
}
|
|
13929
|
+
|
|
13930
|
+
function validateQueryParamValue(value: string): string | undefined {
|
|
13931
|
+
if (!value.trim()) return "Query param value is required";
|
|
13932
|
+
if (value.length > QUERY_PARAM_VALUE_MAX) return "value cannot exceed 2048 characters.";
|
|
13933
|
+
return undefined;
|
|
13934
|
+
}
|
|
13999
13935
|
|
|
14000
13936
|
function generateId() {
|
|
14001
13937
|
return Math.random().toString(36).slice(2, 9);
|
|
@@ -14019,10 +13955,13 @@ const textareaCls = cn(
|
|
|
14019
13955
|
);
|
|
14020
13956
|
|
|
14021
13957
|
// \u2500\u2500 KeyValueTable \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
13958
|
+
type RowErrors = { key?: string; value?: string };
|
|
13959
|
+
|
|
14022
13960
|
function KeyValueTable({
|
|
14023
13961
|
rows,
|
|
14024
13962
|
onChange,
|
|
14025
13963
|
label,
|
|
13964
|
+
getRowErrors,
|
|
14026
13965
|
keyMaxLength,
|
|
14027
13966
|
valueMaxLength,
|
|
14028
13967
|
keyRegex,
|
|
@@ -14031,17 +13970,18 @@ function KeyValueTable({
|
|
|
14031
13970
|
rows: KeyValuePair[];
|
|
14032
13971
|
onChange: (rows: KeyValuePair[]) => void;
|
|
14033
13972
|
label: string;
|
|
13973
|
+
getRowErrors?: (row: KeyValuePair) => RowErrors;
|
|
14034
13974
|
keyMaxLength?: number;
|
|
14035
13975
|
valueMaxLength?: number;
|
|
14036
13976
|
keyRegex?: RegExp;
|
|
14037
13977
|
keyRegexError?: string;
|
|
14038
13978
|
}) {
|
|
14039
13979
|
const update = (id: string, patch: Partial<KeyValuePair>) => {
|
|
14040
|
-
|
|
14041
|
-
if (
|
|
14042
|
-
|
|
13980
|
+
// Replace spaces with hyphens in key values
|
|
13981
|
+
if (patch.key !== undefined) {
|
|
13982
|
+
patch = { ...patch, key: patch.key.replace(/ /g, "-") };
|
|
14043
13983
|
}
|
|
14044
|
-
onChange(rows.map((r) => (r.id === id ? { ...r, ...
|
|
13984
|
+
onChange(rows.map((r) => (r.id === id ? { ...r, ...patch } : r)));
|
|
14045
13985
|
};
|
|
14046
13986
|
|
|
14047
13987
|
const remove = (id: string) => onChange(rows.filter((r) => r.id !== id));
|
|
@@ -14049,105 +13989,109 @@ function KeyValueTable({
|
|
|
14049
13989
|
const add = () =>
|
|
14050
13990
|
onChange([...rows, { id: generateId(), key: "", value: "" }]);
|
|
14051
13991
|
|
|
13992
|
+
const getErrors = (row: KeyValuePair): RowErrors => {
|
|
13993
|
+
if (getRowErrors) return getRowErrors(row);
|
|
13994
|
+
// Inline validation from keyRegex prop when no getRowErrors provided
|
|
13995
|
+
const errors: RowErrors = {};
|
|
13996
|
+
if (keyRegex && row.key.trim() && !keyRegex.test(row.key)) {
|
|
13997
|
+
errors.key = keyRegexError ?? "Invalid key format";
|
|
13998
|
+
}
|
|
13999
|
+
return errors;
|
|
14000
|
+
};
|
|
14001
|
+
|
|
14002
|
+
// Reusable delete row action \u2014 same placement and styling as KeyValueRow / knowledge-base-card
|
|
14003
|
+
const deleteRowButtonClass =
|
|
14004
|
+
"text-semantic-text-muted hover:text-semantic-error-primary hover:bg-semantic-error-surface transition-colors shrink-0";
|
|
14005
|
+
|
|
14052
14006
|
return (
|
|
14053
14007
|
<div className="flex flex-col gap-1.5">
|
|
14054
14008
|
<span className="text-xs text-semantic-text-muted">{label}</span>
|
|
14055
14009
|
<div className="border border-semantic-border-layout rounded overflow-hidden">
|
|
14056
|
-
{/* Column headers \u2014 desktop only */}
|
|
14010
|
+
{/* Column headers \u2014 desktop only; border-r on Key cell defines column boundary */}
|
|
14057
14011
|
<div className="hidden sm:flex bg-semantic-bg-ui border-b border-semantic-border-layout">
|
|
14058
|
-
<div className="flex-1 px-3 py-2 text-xs font-semibold text-semantic-text-muted border-r border-semantic-border-layout">
|
|
14012
|
+
<div className="flex-1 min-w-0 px-3 py-2 text-xs font-semibold text-semantic-text-muted border-r border-semantic-border-layout">
|
|
14059
14013
|
Key
|
|
14060
14014
|
</div>
|
|
14061
|
-
<div className="flex-[2] px-3 py-2 text-xs font-semibold text-semantic-text-muted">
|
|
14015
|
+
<div className="flex-[2] min-w-0 px-3 py-2 text-xs font-semibold text-semantic-text-muted">
|
|
14062
14016
|
Value
|
|
14063
14017
|
</div>
|
|
14064
|
-
<div className="w-10 shrink-0" />
|
|
14018
|
+
<div className="w-10 shrink-0" aria-hidden="true" />
|
|
14065
14019
|
</div>
|
|
14066
14020
|
|
|
14067
|
-
{/* Filled rows */}
|
|
14068
|
-
{rows.map((row) =>
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
|
|
14021
|
+
{/* Filled rows \u2014 same flex ratio (flex-1 / flex-2 / w-10) so middle border aligns with header */}
|
|
14022
|
+
{rows.map((row) => {
|
|
14023
|
+
const errors = getErrors(row);
|
|
14024
|
+
return (
|
|
14025
|
+
<div
|
|
14026
|
+
key={row.id}
|
|
14027
|
+
className="border-b border-semantic-border-layout last:border-b-0 flex items-center min-h-0"
|
|
14028
|
+
>
|
|
14029
|
+
{/* Key column \u2014 border-r on column (not input) so it aligns with header */}
|
|
14030
|
+
<div className="flex-1 flex flex-col min-w-0 sm:border-r sm:border-semantic-border-layout">
|
|
14031
|
+
<span className="sm:hidden px-3 pt-2.5 pb-0.5 text-[10px] font-semibold text-semantic-text-muted uppercase tracking-wide">
|
|
14077
14032
|
Key
|
|
14078
14033
|
</span>
|
|
14079
14034
|
<input
|
|
14080
14035
|
type="text"
|
|
14081
14036
|
value={row.key}
|
|
14082
|
-
maxLength={keyMaxLength}
|
|
14083
14037
|
onChange={(e) => update(row.id, { key: e.target.value })}
|
|
14084
14038
|
placeholder="Key"
|
|
14085
|
-
|
|
14039
|
+
maxLength={keyMaxLength}
|
|
14040
|
+
className={cn(
|
|
14041
|
+
"w-full px-3 py-2.5 text-base text-semantic-text-primary placeholder:text-semantic-text-muted bg-semantic-bg-primary outline-none focus:bg-semantic-bg-hover",
|
|
14042
|
+
errors.key && "border-semantic-error-primary"
|
|
14043
|
+
)}
|
|
14044
|
+
aria-invalid={Boolean(errors.key)}
|
|
14045
|
+
aria-describedby={errors.key ? \`err-key-\${row.id}\` : undefined}
|
|
14086
14046
|
/>
|
|
14087
|
-
{
|
|
14088
|
-
<p className="m-0 text-xs text-semantic-error-primary">
|
|
14047
|
+
{errors.key && (
|
|
14048
|
+
<p id={\`err-key-\${row.id}\`} className="m-0 px-3 pt-0.5 text-xs text-semantic-error-primary">
|
|
14049
|
+
{errors.key}
|
|
14050
|
+
</p>
|
|
14089
14051
|
)}
|
|
14090
14052
|
</div>
|
|
14091
|
-
<div className="h-px bg-semantic-border-layout mx-3" />
|
|
14092
|
-
<div className="flex items-start gap-2 px-3 py-2.5">
|
|
14093
|
-
<div className="flex flex-col flex-1 gap-0.5">
|
|
14094
|
-
<span className="text-[10px] font-semibold text-semantic-text-muted uppercase tracking-wide">
|
|
14095
|
-
Value
|
|
14096
|
-
</span>
|
|
14097
|
-
<input
|
|
14098
|
-
type="text"
|
|
14099
|
-
value={row.value}
|
|
14100
|
-
maxLength={valueMaxLength}
|
|
14101
|
-
onChange={(e) => update(row.id, { value: e.target.value })}
|
|
14102
|
-
placeholder="Type {{ to add variables"
|
|
14103
|
-
className="w-full text-base text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none"
|
|
14104
|
-
/>
|
|
14105
|
-
</div>
|
|
14106
|
-
<button
|
|
14107
|
-
type="button"
|
|
14108
|
-
onClick={() => remove(row.id)}
|
|
14109
|
-
className="mt-4 size-8 flex items-center justify-center text-semantic-text-muted hover:text-semantic-error-primary hover:bg-semantic-error-surface rounded transition-colors shrink-0"
|
|
14110
|
-
aria-label="Delete row"
|
|
14111
|
-
>
|
|
14112
|
-
<Trash2 className="size-3.5" />
|
|
14113
|
-
</button>
|
|
14114
|
-
</div>
|
|
14115
|
-
</div>
|
|
14116
14053
|
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
value={row.key}
|
|
14123
|
-
maxLength={keyMaxLength}
|
|
14124
|
-
onChange={(e) => update(row.id, { key: e.target.value })}
|
|
14125
|
-
placeholder="Key"
|
|
14126
|
-
className="flex-1 px-3 py-2.5 text-base text-semantic-text-primary placeholder:text-semantic-text-muted bg-semantic-bg-primary border-r border-semantic-border-layout outline-none focus:bg-semantic-bg-hover"
|
|
14127
|
-
/>
|
|
14054
|
+
{/* Value column */}
|
|
14055
|
+
<div className="flex-[2] flex flex-col min-w-0">
|
|
14056
|
+
<span className="sm:hidden px-3 pt-2.5 pb-0.5 text-[10px] font-semibold text-semantic-text-muted uppercase tracking-wide">
|
|
14057
|
+
Value
|
|
14058
|
+
</span>
|
|
14128
14059
|
<input
|
|
14129
14060
|
type="text"
|
|
14130
14061
|
value={row.value}
|
|
14131
|
-
maxLength={valueMaxLength}
|
|
14132
14062
|
onChange={(e) => update(row.id, { value: e.target.value })}
|
|
14133
14063
|
placeholder="Type {{ to add variables"
|
|
14134
|
-
|
|
14064
|
+
maxLength={valueMaxLength}
|
|
14065
|
+
className={cn(
|
|
14066
|
+
"w-full px-3 py-2.5 text-base text-semantic-text-primary placeholder:text-semantic-text-muted bg-semantic-bg-primary outline-none focus:bg-semantic-bg-hover",
|
|
14067
|
+
errors.value && "border-semantic-error-primary"
|
|
14068
|
+
)}
|
|
14069
|
+
aria-invalid={Boolean(errors.value)}
|
|
14070
|
+
aria-describedby={errors.value ? \`err-value-\${row.id}\` : undefined}
|
|
14135
14071
|
/>
|
|
14136
|
-
|
|
14072
|
+
{errors.value && (
|
|
14073
|
+
<p id={\`err-value-\${row.id}\`} className="m-0 px-3 pt-0.5 text-xs text-semantic-error-primary">
|
|
14074
|
+
{errors.value}
|
|
14075
|
+
</p>
|
|
14076
|
+
)}
|
|
14077
|
+
</div>
|
|
14078
|
+
|
|
14079
|
+
{/* Action column \u2014 delete aligned with row (same as KeyValueRow / knowledge-base-card) */}
|
|
14080
|
+
<div className="w-10 sm:w-10 flex items-center justify-center shrink-0 self-stretch border-l border-semantic-border-layout sm:border-l-0">
|
|
14081
|
+
<Button
|
|
14137
14082
|
type="button"
|
|
14083
|
+
variant="ghost"
|
|
14084
|
+
size="icon"
|
|
14138
14085
|
onClick={() => remove(row.id)}
|
|
14139
|
-
className="
|
|
14086
|
+
className={cn("rounded-md", deleteRowButtonClass)}
|
|
14140
14087
|
aria-label="Delete row"
|
|
14141
14088
|
>
|
|
14142
|
-
<Trash2 className="size-
|
|
14143
|
-
</
|
|
14089
|
+
<Trash2 className="size-4" />
|
|
14090
|
+
</Button>
|
|
14144
14091
|
</div>
|
|
14145
|
-
{keyRegex && row.key && !keyRegex.test(row.key) && (
|
|
14146
|
-
<p className="m-0 px-3 py-1 text-xs text-semantic-error-primary">{keyRegexError ?? "Invalid key format"}</p>
|
|
14147
|
-
)}
|
|
14148
14092
|
</div>
|
|
14149
|
-
|
|
14150
|
-
)
|
|
14093
|
+
);
|
|
14094
|
+
})}
|
|
14151
14095
|
|
|
14152
14096
|
{/* Add row \u2014 always visible */}
|
|
14153
14097
|
<button
|
|
@@ -14174,6 +14118,8 @@ export const CreateFunctionModal = React.forwardRef<
|
|
|
14174
14118
|
onOpenChange,
|
|
14175
14119
|
onSubmit,
|
|
14176
14120
|
onTestApi,
|
|
14121
|
+
initialData,
|
|
14122
|
+
isEditing = false,
|
|
14177
14123
|
promptMinLength = 100,
|
|
14178
14124
|
promptMaxLength = 1000,
|
|
14179
14125
|
initialStep = 1,
|
|
@@ -14184,39 +14130,61 @@ export const CreateFunctionModal = React.forwardRef<
|
|
|
14184
14130
|
) => {
|
|
14185
14131
|
const [step, setStep] = React.useState<1 | 2>(initialStep);
|
|
14186
14132
|
|
|
14187
|
-
const [name, setName] = React.useState("");
|
|
14188
|
-
const [prompt, setPrompt] = React.useState("");
|
|
14133
|
+
const [name, setName] = React.useState(initialData?.name ?? "");
|
|
14134
|
+
const [prompt, setPrompt] = React.useState(initialData?.prompt ?? "");
|
|
14189
14135
|
|
|
14190
|
-
const [method, setMethod] = React.useState<HttpMethod>("GET");
|
|
14191
|
-
const [url, setUrl] = React.useState("");
|
|
14136
|
+
const [method, setMethod] = React.useState<HttpMethod>(initialData?.method ?? "GET");
|
|
14137
|
+
const [url, setUrl] = React.useState(initialData?.url ?? "");
|
|
14192
14138
|
const [activeTab, setActiveTab] =
|
|
14193
14139
|
React.useState<FunctionTabType>(initialTab);
|
|
14194
|
-
const [headers, setHeaders] = React.useState<KeyValuePair[]>([]);
|
|
14195
|
-
const [queryParams, setQueryParams] = React.useState<KeyValuePair[]>([]);
|
|
14196
|
-
const [body, setBody] = React.useState("");
|
|
14140
|
+
const [headers, setHeaders] = React.useState<KeyValuePair[]>(initialData?.headers ?? []);
|
|
14141
|
+
const [queryParams, setQueryParams] = React.useState<KeyValuePair[]>(initialData?.queryParams ?? []);
|
|
14142
|
+
const [body, setBody] = React.useState(initialData?.body ?? "");
|
|
14197
14143
|
const [apiResponse, setApiResponse] = React.useState("");
|
|
14198
14144
|
const [isTesting, setIsTesting] = React.useState(false);
|
|
14199
|
-
|
|
14200
|
-
// Validation errors (shown on blur)
|
|
14145
|
+
const [step2SubmitAttempted, setStep2SubmitAttempted] = React.useState(false);
|
|
14201
14146
|
const [nameError, setNameError] = React.useState("");
|
|
14202
14147
|
const [urlError, setUrlError] = React.useState("");
|
|
14203
14148
|
const [bodyError, setBodyError] = React.useState("");
|
|
14204
14149
|
|
|
14150
|
+
// Sync form state from initialData each time the modal opens
|
|
14151
|
+
React.useEffect(() => {
|
|
14152
|
+
if (open) {
|
|
14153
|
+
setStep(initialStep);
|
|
14154
|
+
setName(initialData?.name ?? "");
|
|
14155
|
+
setPrompt(initialData?.prompt ?? "");
|
|
14156
|
+
setMethod(initialData?.method ?? "GET");
|
|
14157
|
+
setUrl(initialData?.url ?? "");
|
|
14158
|
+
setActiveTab(initialTab);
|
|
14159
|
+
setHeaders(initialData?.headers ?? []);
|
|
14160
|
+
setQueryParams(initialData?.queryParams ?? []);
|
|
14161
|
+
setBody(initialData?.body ?? "");
|
|
14162
|
+
setApiResponse("");
|
|
14163
|
+
setStep2SubmitAttempted(false);
|
|
14164
|
+
setNameError("");
|
|
14165
|
+
setUrlError("");
|
|
14166
|
+
setBodyError("");
|
|
14167
|
+
}
|
|
14168
|
+
// Re-run only when modal opens; intentionally exclude deep deps to avoid mid-session resets
|
|
14169
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
14170
|
+
}, [open]);
|
|
14171
|
+
|
|
14205
14172
|
const reset = React.useCallback(() => {
|
|
14206
14173
|
setStep(initialStep);
|
|
14207
|
-
setName("");
|
|
14208
|
-
setPrompt("");
|
|
14209
|
-
setMethod("GET");
|
|
14210
|
-
setUrl("");
|
|
14174
|
+
setName(initialData?.name ?? "");
|
|
14175
|
+
setPrompt(initialData?.prompt ?? "");
|
|
14176
|
+
setMethod(initialData?.method ?? "GET");
|
|
14177
|
+
setUrl(initialData?.url ?? "");
|
|
14211
14178
|
setActiveTab(initialTab);
|
|
14212
|
-
setHeaders([]);
|
|
14213
|
-
setQueryParams([]);
|
|
14214
|
-
setBody("");
|
|
14179
|
+
setHeaders(initialData?.headers ?? []);
|
|
14180
|
+
setQueryParams(initialData?.queryParams ?? []);
|
|
14181
|
+
setBody(initialData?.body ?? "");
|
|
14215
14182
|
setApiResponse("");
|
|
14183
|
+
setStep2SubmitAttempted(false);
|
|
14216
14184
|
setNameError("");
|
|
14217
14185
|
setUrlError("");
|
|
14218
14186
|
setBodyError("");
|
|
14219
|
-
}, [initialStep, initialTab]);
|
|
14187
|
+
}, [initialData, initialStep, initialTab]);
|
|
14220
14188
|
|
|
14221
14189
|
const handleClose = React.useCallback(() => {
|
|
14222
14190
|
reset();
|
|
@@ -14232,26 +14200,26 @@ export const CreateFunctionModal = React.forwardRef<
|
|
|
14232
14200
|
}
|
|
14233
14201
|
}, [supportsBody, activeTab]);
|
|
14234
14202
|
|
|
14235
|
-
const validateName = (
|
|
14236
|
-
if (
|
|
14203
|
+
const validateName = (value: string) => {
|
|
14204
|
+
if (value.trim() && !FUNCTION_NAME_REGEX.test(value.trim())) {
|
|
14237
14205
|
setNameError("Must start with a letter and contain only letters, numbers, and underscores");
|
|
14238
14206
|
} else {
|
|
14239
14207
|
setNameError("");
|
|
14240
14208
|
}
|
|
14241
14209
|
};
|
|
14242
14210
|
|
|
14243
|
-
const validateUrl = (
|
|
14244
|
-
if (
|
|
14211
|
+
const validateUrl = (value: string) => {
|
|
14212
|
+
if (value.trim() && !URL_REGEX.test(value.trim())) {
|
|
14245
14213
|
setUrlError("URL must start with http:// or https://");
|
|
14246
14214
|
} else {
|
|
14247
14215
|
setUrlError("");
|
|
14248
14216
|
}
|
|
14249
14217
|
};
|
|
14250
14218
|
|
|
14251
|
-
const validateBody = (
|
|
14252
|
-
if (
|
|
14219
|
+
const validateBody = (value: string) => {
|
|
14220
|
+
if (value.trim()) {
|
|
14253
14221
|
try {
|
|
14254
|
-
JSON.parse(
|
|
14222
|
+
JSON.parse(value.trim());
|
|
14255
14223
|
setBodyError("");
|
|
14256
14224
|
} catch {
|
|
14257
14225
|
setBodyError("Body must be valid JSON");
|
|
@@ -14262,10 +14230,24 @@ export const CreateFunctionModal = React.forwardRef<
|
|
|
14262
14230
|
};
|
|
14263
14231
|
|
|
14264
14232
|
const handleNext = () => {
|
|
14265
|
-
if (
|
|
14233
|
+
if (name.trim() && prompt.trim().length >= promptMinLength) setStep(2);
|
|
14266
14234
|
};
|
|
14267
14235
|
|
|
14236
|
+
const queryParamsHaveErrors = (rows: KeyValuePair[]): boolean =>
|
|
14237
|
+
rows.some((row) => {
|
|
14238
|
+
const hasInput = row.key.trim() !== "" || row.value.trim() !== "";
|
|
14239
|
+
if (!hasInput) return false;
|
|
14240
|
+
return (
|
|
14241
|
+
validateQueryParamKey(row.key) !== undefined ||
|
|
14242
|
+
validateQueryParamValue(row.value) !== undefined
|
|
14243
|
+
);
|
|
14244
|
+
});
|
|
14245
|
+
|
|
14268
14246
|
const handleSubmit = () => {
|
|
14247
|
+
if (step === 2) {
|
|
14248
|
+
setStep2SubmitAttempted(true);
|
|
14249
|
+
if (queryParamsHaveErrors(queryParams)) return;
|
|
14250
|
+
}
|
|
14269
14251
|
const data: CreateFunctionData = {
|
|
14270
14252
|
name: name.trim(),
|
|
14271
14253
|
prompt: prompt.trim(),
|
|
@@ -14297,16 +14279,17 @@ export const CreateFunctionModal = React.forwardRef<
|
|
|
14297
14279
|
}
|
|
14298
14280
|
};
|
|
14299
14281
|
|
|
14300
|
-
const
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
const hasHeaderKeyErrors = headers.some(
|
|
14304
|
-
(r) => r.key && !HEADER_KEY_REGEX.test(r.key)
|
|
14282
|
+
const headersHaveKeyErrors = headers.some(
|
|
14283
|
+
(row) => row.key.trim() && HEADER_KEY_REGEX && !HEADER_KEY_REGEX.test(row.key)
|
|
14305
14284
|
);
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14285
|
+
|
|
14286
|
+
const isStep1Valid =
|
|
14287
|
+
name.trim().length > 0 &&
|
|
14288
|
+
FUNCTION_NAME_REGEX.test(name.trim()) &&
|
|
14289
|
+
prompt.trim().length >= promptMinLength;
|
|
14290
|
+
|
|
14291
|
+
const isStep2Valid =
|
|
14292
|
+
!urlError && !bodyError && !headersHaveKeyErrors && !queryParamsHaveErrors(queryParams);
|
|
14310
14293
|
|
|
14311
14294
|
const tabLabels: Record<FunctionTabType, string> = {
|
|
14312
14295
|
header: \`Header (\${headers.length})\`,
|
|
@@ -14333,7 +14316,7 @@ export const CreateFunctionModal = React.forwardRef<
|
|
|
14333
14316
|
{/* \u2500\u2500 Header \u2500\u2500 */}
|
|
14334
14317
|
<div className="flex items-center justify-between px-4 py-4 border-b border-semantic-border-layout shrink-0 sm:px-6">
|
|
14335
14318
|
<DialogTitle className="text-base font-semibold text-semantic-text-primary">
|
|
14336
|
-
Create Function
|
|
14319
|
+
{isEditing ? "Edit Function" : "Create Function"}
|
|
14337
14320
|
</DialogTitle>
|
|
14338
14321
|
<button
|
|
14339
14322
|
type="button"
|
|
@@ -14502,7 +14485,7 @@ export const CreateFunctionModal = React.forwardRef<
|
|
|
14502
14485
|
keyMaxLength={HEADER_KEY_MAX}
|
|
14503
14486
|
valueMaxLength={HEADER_VALUE_MAX}
|
|
14504
14487
|
keyRegex={HEADER_KEY_REGEX}
|
|
14505
|
-
keyRegexError="
|
|
14488
|
+
keyRegexError="Invalid header key. Use only alphanumeric and !#$%&'*+-.^_\`|~ characters."
|
|
14506
14489
|
/>
|
|
14507
14490
|
)}
|
|
14508
14491
|
{activeTab === "queryParams" && (
|
|
@@ -14510,10 +14493,16 @@ export const CreateFunctionModal = React.forwardRef<
|
|
|
14510
14493
|
rows={queryParams}
|
|
14511
14494
|
onChange={setQueryParams}
|
|
14512
14495
|
label="Query parameter"
|
|
14513
|
-
keyMaxLength={
|
|
14514
|
-
valueMaxLength={
|
|
14515
|
-
|
|
14516
|
-
|
|
14496
|
+
keyMaxLength={QUERY_PARAM_KEY_MAX}
|
|
14497
|
+
valueMaxLength={QUERY_PARAM_VALUE_MAX}
|
|
14498
|
+
getRowErrors={(row) => {
|
|
14499
|
+
const hasInput = row.key.trim() !== "" || row.value.trim() !== "";
|
|
14500
|
+
if (!hasInput && !step2SubmitAttempted) return {};
|
|
14501
|
+
return {
|
|
14502
|
+
key: validateQueryParamKey(row.key),
|
|
14503
|
+
value: validateQueryParamValue(row.value),
|
|
14504
|
+
};
|
|
14505
|
+
}}
|
|
14517
14506
|
/>
|
|
14518
14507
|
)}
|
|
14519
14508
|
{activeTab === "body" && (
|
|
@@ -14985,9 +14974,11 @@ export interface BotBehaviorCardProps {
|
|
|
14985
14974
|
/** Callback when any field changes */
|
|
14986
14975
|
onChange: (patch: Partial<BotBehaviorData>) => void;
|
|
14987
14976
|
/** Called when the system prompt textarea loses focus */
|
|
14988
|
-
|
|
14977
|
+
onSystemPromptBlur?: (value: string) => void;
|
|
14989
14978
|
/** Session variables shown as insertable chips */
|
|
14990
14979
|
sessionVariables?: string[];
|
|
14980
|
+
/** Maximum character length for the system prompt textarea (default: 25000) */
|
|
14981
|
+
maxLength?: number;
|
|
14991
14982
|
/** Disables all fields in the card (view mode) */
|
|
14992
14983
|
disabled?: boolean;
|
|
14993
14984
|
/** Additional className for the card */
|
|
@@ -15075,15 +15066,16 @@ const BotBehaviorCard = React.forwardRef<HTMLDivElement, BotBehaviorCardProps>(
|
|
|
15075
15066
|
{
|
|
15076
15067
|
data,
|
|
15077
15068
|
onChange,
|
|
15078
|
-
|
|
15069
|
+
onSystemPromptBlur,
|
|
15079
15070
|
sessionVariables = DEFAULT_SESSION_VARIABLES,
|
|
15071
|
+
maxLength = 25000,
|
|
15080
15072
|
disabled,
|
|
15081
15073
|
className,
|
|
15082
15074
|
},
|
|
15083
15075
|
ref
|
|
15084
15076
|
) => {
|
|
15085
15077
|
const prompt = data.systemPrompt ?? "";
|
|
15086
|
-
const MAX =
|
|
15078
|
+
const MAX = maxLength;
|
|
15087
15079
|
|
|
15088
15080
|
const insertVariable = (variable: string) => {
|
|
15089
15081
|
onChange({ systemPrompt: prompt + variable });
|
|
@@ -15103,7 +15095,7 @@ const BotBehaviorCard = React.forwardRef<HTMLDivElement, BotBehaviorCardProps>(
|
|
|
15103
15095
|
onChange={(v) => {
|
|
15104
15096
|
if (v.length <= MAX) onChange({ systemPrompt: v });
|
|
15105
15097
|
}}
|
|
15106
|
-
onBlur={
|
|
15098
|
+
onBlur={onSystemPromptBlur}
|
|
15107
15099
|
placeholder="You are a helpful assistant. Always start by greeting the user politely: 'Hello! Welcome. How can I assist you today?'"
|
|
15108
15100
|
disabled={disabled}
|
|
15109
15101
|
className="pb-8"
|
|
@@ -15149,6 +15141,13 @@ export { BotBehaviorCard };
|
|
|
15149
15141
|
import { Download, Trash2, Plus, Info } from "lucide-react";
|
|
15150
15142
|
import { cn } from "../../../lib/utils";
|
|
15151
15143
|
import { Badge } from "../badge";
|
|
15144
|
+
import {
|
|
15145
|
+
Tooltip,
|
|
15146
|
+
TooltipContent,
|
|
15147
|
+
TooltipProvider,
|
|
15148
|
+
TooltipTrigger,
|
|
15149
|
+
} from "../tooltip";
|
|
15150
|
+
import { BOT_KNOWLEDGE_STATUS } from "./types";
|
|
15152
15151
|
import type { KnowledgeBaseFile } from "./types";
|
|
15153
15152
|
|
|
15154
15153
|
// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -15162,6 +15161,8 @@ export interface KnowledgeBaseCardProps {
|
|
|
15162
15161
|
onDownload?: (id: string) => void;
|
|
15163
15162
|
/** Called when user clicks the delete button on a file */
|
|
15164
15163
|
onDelete?: (id: string) => void;
|
|
15164
|
+
/** Hover text shown on the info icon next to the "Knowledge Base" title */
|
|
15165
|
+
infoTooltip?: string;
|
|
15165
15166
|
/** Disables all interactive elements in the card (view mode) */
|
|
15166
15167
|
disabled?: boolean;
|
|
15167
15168
|
/** Additional className */
|
|
@@ -15170,13 +15171,13 @@ export interface KnowledgeBaseCardProps {
|
|
|
15170
15171
|
|
|
15171
15172
|
// \u2500\u2500\u2500 Status config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
15172
15173
|
|
|
15173
|
-
type BadgeVariant = "active" | "destructive";
|
|
15174
|
-
const STATUS_CONFIG: Record<
|
|
15175
|
-
{
|
|
15176
|
-
|
|
15177
|
-
|
|
15178
|
-
|
|
15179
|
-
|
|
15174
|
+
type BadgeVariant = "default" | "active" | "destructive";
|
|
15175
|
+
const STATUS_CONFIG: Record<BOT_KNOWLEDGE_STATUS, { label: string; variant: BadgeVariant }> = {
|
|
15176
|
+
[BOT_KNOWLEDGE_STATUS.PENDING]: { label: "Pending", variant: "default" },
|
|
15177
|
+
[BOT_KNOWLEDGE_STATUS.READY]: { label: "Ready", variant: "active" },
|
|
15178
|
+
[BOT_KNOWLEDGE_STATUS.PROCESSING]: { label: "Processing", variant: "active" },
|
|
15179
|
+
[BOT_KNOWLEDGE_STATUS.FAILED]: { label: "Failed", variant: "destructive" },
|
|
15180
|
+
};
|
|
15180
15181
|
|
|
15181
15182
|
// \u2500\u2500\u2500 Component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
15182
15183
|
|
|
@@ -15187,6 +15188,7 @@ const KnowledgeBaseCard = React.forwardRef<HTMLDivElement, KnowledgeBaseCardProp
|
|
|
15187
15188
|
onAdd,
|
|
15188
15189
|
onDownload,
|
|
15189
15190
|
onDelete,
|
|
15191
|
+
infoTooltip,
|
|
15190
15192
|
disabled,
|
|
15191
15193
|
className,
|
|
15192
15194
|
},
|
|
@@ -15206,7 +15208,18 @@ const KnowledgeBaseCard = React.forwardRef<HTMLDivElement, KnowledgeBaseCardProp
|
|
|
15206
15208
|
<h2 className="m-0 text-base font-semibold text-semantic-text-primary">
|
|
15207
15209
|
Knowledge Base
|
|
15208
15210
|
</h2>
|
|
15209
|
-
|
|
15211
|
+
{infoTooltip ? (
|
|
15212
|
+
<TooltipProvider delayDuration={200}>
|
|
15213
|
+
<Tooltip>
|
|
15214
|
+
<TooltipTrigger asChild>
|
|
15215
|
+
<Info className="size-3.5 text-semantic-text-muted shrink-0 cursor-help" />
|
|
15216
|
+
</TooltipTrigger>
|
|
15217
|
+
<TooltipContent>{infoTooltip}</TooltipContent>
|
|
15218
|
+
</Tooltip>
|
|
15219
|
+
</TooltipProvider>
|
|
15220
|
+
) : (
|
|
15221
|
+
<Info className="size-3.5 text-semantic-text-muted shrink-0" />
|
|
15222
|
+
)}
|
|
15210
15223
|
</div>
|
|
15211
15224
|
<button
|
|
15212
15225
|
type="button"
|
|
@@ -15227,7 +15240,7 @@ const KnowledgeBaseCard = React.forwardRef<HTMLDivElement, KnowledgeBaseCardProp
|
|
|
15227
15240
|
) : (
|
|
15228
15241
|
<div className="flex flex-col divide-y divide-semantic-border-layout">
|
|
15229
15242
|
{files.map((file) => {
|
|
15230
|
-
const status = STATUS_CONFIG[file.status] ?? STATUS_CONFIG.
|
|
15243
|
+
const status = STATUS_CONFIG[file.status] ?? STATUS_CONFIG[BOT_KNOWLEDGE_STATUS.PENDING];
|
|
15231
15244
|
return (
|
|
15232
15245
|
<div
|
|
15233
15246
|
key={file.id}
|
|
@@ -15761,6 +15774,164 @@ const AdvancedSettingsCard = React.forwardRef<HTMLDivElement, AdvancedSettingsCa
|
|
|
15761
15774
|
AdvancedSettingsCard.displayName = "AdvancedSettingsCard";
|
|
15762
15775
|
|
|
15763
15776
|
export { AdvancedSettingsCard };
|
|
15777
|
+
`, prefix)
|
|
15778
|
+
},
|
|
15779
|
+
{
|
|
15780
|
+
name: "fallback-prompts-card.tsx",
|
|
15781
|
+
content: prefixTailwindClasses(`import * as React from "react";
|
|
15782
|
+
import { Info } from "lucide-react";
|
|
15783
|
+
import { cn } from "../../../lib/utils";
|
|
15784
|
+
import {
|
|
15785
|
+
Accordion,
|
|
15786
|
+
AccordionItem,
|
|
15787
|
+
AccordionTrigger,
|
|
15788
|
+
AccordionContent,
|
|
15789
|
+
} from "../accordion";
|
|
15790
|
+
|
|
15791
|
+
// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
15792
|
+
|
|
15793
|
+
export interface FallbackPromptsData {
|
|
15794
|
+
agentBusyPrompt: string;
|
|
15795
|
+
noExtensionFoundPrompt: string;
|
|
15796
|
+
}
|
|
15797
|
+
|
|
15798
|
+
export interface FallbackPromptsCardProps {
|
|
15799
|
+
/** Current prompt text values */
|
|
15800
|
+
data: Partial<FallbackPromptsData>;
|
|
15801
|
+
/** Callback when any field changes */
|
|
15802
|
+
onChange: (patch: Partial<FallbackPromptsData>) => void;
|
|
15803
|
+
/** Called when the Agent Busy Prompt textarea loses focus */
|
|
15804
|
+
onAgentBusyPromptBlur?: (value: string) => void;
|
|
15805
|
+
/** Called when the No Extension Found textarea loses focus */
|
|
15806
|
+
onNoExtensionFoundPromptBlur?: (value: string) => void;
|
|
15807
|
+
/** Maximum character length for each prompt field (default: 25000) */
|
|
15808
|
+
maxLength?: number;
|
|
15809
|
+
/** Disables all fields in the card (view mode) */
|
|
15810
|
+
disabled?: boolean;
|
|
15811
|
+
/** Opens the accordion by default (default: false) */
|
|
15812
|
+
defaultOpen?: boolean;
|
|
15813
|
+
/** Additional className */
|
|
15814
|
+
className?: string;
|
|
15815
|
+
}
|
|
15816
|
+
|
|
15817
|
+
// \u2500\u2500\u2500 Internal helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
15818
|
+
|
|
15819
|
+
function PromptField({
|
|
15820
|
+
label,
|
|
15821
|
+
value,
|
|
15822
|
+
placeholder,
|
|
15823
|
+
maxLength,
|
|
15824
|
+
disabled,
|
|
15825
|
+
onChange,
|
|
15826
|
+
onBlur,
|
|
15827
|
+
rows = 2,
|
|
15828
|
+
}: {
|
|
15829
|
+
label: string;
|
|
15830
|
+
value: string;
|
|
15831
|
+
placeholder?: string;
|
|
15832
|
+
maxLength: number;
|
|
15833
|
+
disabled?: boolean;
|
|
15834
|
+
onChange: (value: string) => void;
|
|
15835
|
+
onBlur?: (value: string) => void;
|
|
15836
|
+
rows?: number;
|
|
15837
|
+
}) {
|
|
15838
|
+
return (
|
|
15839
|
+
<div className="flex flex-col gap-1.5">
|
|
15840
|
+
<label className="text-sm font-semibold text-semantic-text-secondary tracking-[0.014px]">
|
|
15841
|
+
{label}
|
|
15842
|
+
</label>
|
|
15843
|
+
<textarea
|
|
15844
|
+
value={value}
|
|
15845
|
+
placeholder={placeholder}
|
|
15846
|
+
maxLength={maxLength}
|
|
15847
|
+
disabled={disabled}
|
|
15848
|
+
rows={rows}
|
|
15849
|
+
onChange={(e) => onChange(e.target.value)}
|
|
15850
|
+
onBlur={(e) => onBlur?.(e.target.value)}
|
|
15851
|
+
className={cn(
|
|
15852
|
+
"w-full resize-none rounded border border-semantic-border-layout bg-semantic-bg-primary px-3 py-2.5 text-base text-semantic-text-primary placeholder:text-semantic-text-muted outline-none transition-all",
|
|
15853
|
+
"focus:outline-none focus:border-semantic-border-input-focus/50 focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
|
|
15854
|
+
disabled && "cursor-not-allowed opacity-50"
|
|
15855
|
+
)}
|
|
15856
|
+
/>
|
|
15857
|
+
<p className="m-0 text-xs text-semantic-text-muted text-right">
|
|
15858
|
+
{value.length}/{maxLength}
|
|
15859
|
+
</p>
|
|
15860
|
+
</div>
|
|
15861
|
+
);
|
|
15862
|
+
}
|
|
15863
|
+
|
|
15864
|
+
// \u2500\u2500\u2500 Component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
15865
|
+
|
|
15866
|
+
const FallbackPromptsCard = React.forwardRef<
|
|
15867
|
+
HTMLDivElement,
|
|
15868
|
+
FallbackPromptsCardProps
|
|
15869
|
+
>(
|
|
15870
|
+
(
|
|
15871
|
+
{
|
|
15872
|
+
data,
|
|
15873
|
+
onChange,
|
|
15874
|
+
onAgentBusyPromptBlur,
|
|
15875
|
+
onNoExtensionFoundPromptBlur,
|
|
15876
|
+
maxLength = 25000,
|
|
15877
|
+
disabled,
|
|
15878
|
+
defaultOpen = false,
|
|
15879
|
+
className,
|
|
15880
|
+
},
|
|
15881
|
+
ref
|
|
15882
|
+
) => {
|
|
15883
|
+
return (
|
|
15884
|
+
<div
|
|
15885
|
+
ref={ref}
|
|
15886
|
+
className={cn(
|
|
15887
|
+
"bg-semantic-bg-primary border border-semantic-border-layout rounded-lg overflow-hidden",
|
|
15888
|
+
className
|
|
15889
|
+
)}
|
|
15890
|
+
>
|
|
15891
|
+
<Accordion
|
|
15892
|
+
type="single"
|
|
15893
|
+
defaultValue={defaultOpen ? ["fallback"] : []}
|
|
15894
|
+
>
|
|
15895
|
+
<AccordionItem value="fallback">
|
|
15896
|
+
<AccordionTrigger className="px-4 py-4 border-b border-semantic-border-layout hover:no-underline sm:px-6 sm:py-5">
|
|
15897
|
+
<span className="flex items-center gap-1.5 text-base font-semibold text-semantic-text-primary">
|
|
15898
|
+
Fallback Prompts
|
|
15899
|
+
<Info className="size-3.5 text-semantic-text-muted shrink-0" />
|
|
15900
|
+
</span>
|
|
15901
|
+
</AccordionTrigger>
|
|
15902
|
+
<AccordionContent>
|
|
15903
|
+
<div className="flex flex-col gap-6 px-4 pt-4 sm:px-6 sm:pt-5">
|
|
15904
|
+
<PromptField
|
|
15905
|
+
label="Agent Busy Prompt"
|
|
15906
|
+
value={data.agentBusyPrompt ?? ""}
|
|
15907
|
+
placeholder="Executives are busy at the moment, we will connect you soon."
|
|
15908
|
+
maxLength={maxLength}
|
|
15909
|
+
disabled={disabled}
|
|
15910
|
+
onChange={(v) => onChange({ agentBusyPrompt: v })}
|
|
15911
|
+
onBlur={onAgentBusyPromptBlur}
|
|
15912
|
+
rows={2}
|
|
15913
|
+
/>
|
|
15914
|
+
<PromptField
|
|
15915
|
+
label="No Extension Found"
|
|
15916
|
+
value={data.noExtensionFoundPrompt ?? ""}
|
|
15917
|
+
placeholder="Sorry, the requested extension is currently unavailable. Let me help you directly."
|
|
15918
|
+
maxLength={maxLength}
|
|
15919
|
+
disabled={disabled}
|
|
15920
|
+
onChange={(v) => onChange({ noExtensionFoundPrompt: v })}
|
|
15921
|
+
onBlur={onNoExtensionFoundPromptBlur}
|
|
15922
|
+
rows={4}
|
|
15923
|
+
/>
|
|
15924
|
+
</div>
|
|
15925
|
+
</AccordionContent>
|
|
15926
|
+
</AccordionItem>
|
|
15927
|
+
</Accordion>
|
|
15928
|
+
</div>
|
|
15929
|
+
);
|
|
15930
|
+
}
|
|
15931
|
+
);
|
|
15932
|
+
FallbackPromptsCard.displayName = "FallbackPromptsCard";
|
|
15933
|
+
|
|
15934
|
+
export { FallbackPromptsCard };
|
|
15764
15935
|
`, prefix)
|
|
15765
15936
|
},
|
|
15766
15937
|
{
|
|
@@ -15771,7 +15942,16 @@ export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
|
15771
15942
|
|
|
15772
15943
|
export type FunctionTabType = "header" | "queryParams" | "body";
|
|
15773
15944
|
|
|
15774
|
-
export
|
|
15945
|
+
export const BOT_KNOWLEDGE_STATUS = {
|
|
15946
|
+
PENDING: "pending",
|
|
15947
|
+
READY: "ready",
|
|
15948
|
+
PROCESSING: "processing",
|
|
15949
|
+
FAILED: "failed",
|
|
15950
|
+
} as const;
|
|
15951
|
+
|
|
15952
|
+
export type BOT_KNOWLEDGE_STATUS = typeof BOT_KNOWLEDGE_STATUS[keyof typeof BOT_KNOWLEDGE_STATUS];
|
|
15953
|
+
|
|
15954
|
+
export type KnowledgeFileStatus = BOT_KNOWLEDGE_STATUS;
|
|
15775
15955
|
|
|
15776
15956
|
export interface KeyValuePair {
|
|
15777
15957
|
id: string;
|
|
@@ -15815,6 +15995,10 @@ export interface CreateFunctionModalProps {
|
|
|
15815
15995
|
onOpenChange: (open: boolean) => void;
|
|
15816
15996
|
onSubmit?: (data: CreateFunctionData) => void;
|
|
15817
15997
|
onTestApi?: (step2: CreateFunctionStep2Data) => Promise<string>;
|
|
15998
|
+
/** Pre-fills all fields \u2014 use when opening the modal to edit an existing function */
|
|
15999
|
+
initialData?: Partial<CreateFunctionData>;
|
|
16000
|
+
/** When true, changes the modal title to "Edit Function" */
|
|
16001
|
+
isEditing?: boolean;
|
|
15818
16002
|
/** Minimum character length for the prompt field (default: 100) */
|
|
15819
16003
|
promptMinLength?: number;
|
|
15820
16004
|
/** Maximum character length for the prompt field (default: 1000) */
|
|
@@ -15866,17 +16050,32 @@ export interface IvrBotConfigProps {
|
|
|
15866
16050
|
onDownloadKnowledgeFile?: (fileId: string) => void;
|
|
15867
16051
|
onDeleteKnowledgeFile?: (fileId: string) => void;
|
|
15868
16052
|
onCreateFunction?: (data: CreateFunctionData) => void;
|
|
15869
|
-
/** Called when user edits a custom function */
|
|
16053
|
+
/** Called when user edits a custom function. Receives the function id. */
|
|
15870
16054
|
onEditFunction?: (id: string) => void;
|
|
15871
16055
|
/** Called when user deletes a custom function */
|
|
15872
16056
|
onDeleteFunction?: (id: string) => void;
|
|
15873
16057
|
onTestApi?: (step2: CreateFunctionStep2Data) => Promise<string>;
|
|
15874
16058
|
/** Hover text for the info icon in the Functions card header */
|
|
15875
16059
|
functionsInfoTooltip?: string;
|
|
16060
|
+
/** Hover text for the info icon in the Knowledge Base card header */
|
|
16061
|
+
knowledgeBaseInfoTooltip?: string;
|
|
15876
16062
|
/** Minimum character length for the function prompt (default: 100) */
|
|
15877
16063
|
functionPromptMinLength?: number;
|
|
15878
16064
|
/** Maximum character length for the function prompt (default: 1000) */
|
|
15879
16065
|
functionPromptMaxLength?: number;
|
|
16066
|
+
/**
|
|
16067
|
+
* Pre-filled data shown when the edit function modal opens.
|
|
16068
|
+
* Pass when your app fetches full function data after onEditFunction fires.
|
|
16069
|
+
*/
|
|
16070
|
+
functionEditData?: Partial<CreateFunctionData>;
|
|
16071
|
+
/** Max character length for the "How It Behaves" system prompt (default: 25000) */
|
|
16072
|
+
systemPromptMaxLength?: number;
|
|
16073
|
+
/** Called when the system prompt textarea loses focus */
|
|
16074
|
+
onSystemPromptBlur?: (value: string) => void;
|
|
16075
|
+
/** Called when the Agent Busy Prompt textarea loses focus */
|
|
16076
|
+
onAgentBusyPromptBlur?: (value: string) => void;
|
|
16077
|
+
/** Called when the No Extension Found textarea loses focus */
|
|
16078
|
+
onNoExtensionFoundPromptBlur?: (value: string) => void;
|
|
15880
16079
|
onBack?: () => void;
|
|
15881
16080
|
/** Called when the play icon is clicked on a voice option */
|
|
15882
16081
|
onPlayVoice?: (voiceValue: string) => void;
|
|
@@ -15902,12 +16101,6 @@ export interface IvrBotConfigProps {
|
|
|
15902
16101
|
/** Override call end threshold bounds */
|
|
15903
16102
|
callEndThresholdMin?: number;
|
|
15904
16103
|
callEndThresholdMax?: number;
|
|
15905
|
-
/** Called when the system prompt textarea loses focus */
|
|
15906
|
-
onSystemPromptBlur?: (value: string) => void;
|
|
15907
|
-
/** Called when the Agent Busy Prompt textarea loses focus */
|
|
15908
|
-
onAgentBusyPromptBlur?: (value: string) => void;
|
|
15909
|
-
/** Called when the No Extension Found textarea loses focus */
|
|
15910
|
-
onNoExtensionFoundPromptBlur?: (value: string) => void;
|
|
15911
16104
|
className?: string;
|
|
15912
16105
|
}
|
|
15913
16106
|
|
|
@@ -15929,6 +16122,12 @@ export { KnowledgeBaseCard } from "./knowledge-base-card";
|
|
|
15929
16122
|
export { FunctionsCard } from "./functions-card";
|
|
15930
16123
|
export { FrustrationHandoverCard } from "./frustration-handover-card";
|
|
15931
16124
|
export { AdvancedSettingsCard } from "./advanced-settings-card";
|
|
16125
|
+
export { FallbackPromptsCard } from "./fallback-prompts-card";
|
|
16126
|
+
export type {
|
|
16127
|
+
FallbackPromptsData,
|
|
16128
|
+
FallbackPromptsCardProps,
|
|
16129
|
+
} from "./fallback-prompts-card";
|
|
16130
|
+
export { BOT_KNOWLEDGE_STATUS } from "./types";
|
|
15932
16131
|
export { CreateFunctionModal } from "./create-function-modal";
|
|
15933
16132
|
export { FileUploadModal } from "../file-upload-modal";
|
|
15934
16133
|
export { IvrBotConfig } from "./ivr-bot-config";
|