fix: incorporate review comments

This commit is contained in:
Abhishek Kumar 2026-07-20 21:02:09 +05:30
parent 534672e659
commit e486920db2
3 changed files with 103 additions and 17 deletions

View file

@ -536,18 +536,6 @@ async def get_model_configuration_preferences_legacy(
return await get_preferences(user=user)
@router.put(
"/model-configurations/preferences",
response_model=OrganizationPreferences,
include_in_schema=False,
)
async def save_model_configuration_preferences_legacy(
request: OrganizationPreferences,
user: UserModel = Depends(get_user_with_selected_organization),
):
return await save_preferences(request=request, user=user)
def preserve_masked_fields(provider: str, request_dict: dict, existing: dict):
"""If the client re-submitted a masked sensitive field, restore the original."""
for field_name in _sensitive_fields(provider):

View file

@ -356,7 +356,7 @@ async def resolve_transfer_config(
logger.info(
"Transfer destination resolved from context mapping "
f"context_path={resolved.metadata.get('context_path')} "
f"resolved={resolved}"
f"source={resolved.source} destination={resolved.destination}"
)
return resolved

View file

@ -1,7 +1,7 @@
"use client";
import { format } from "date-fns";
import { ArrowLeft, BookA, Brain, CalendarIcon, Clipboard, Download, ExternalLink, FileDown, Fingerprint, Loader2, Mic, Pause, PhoneOff, Play, Rocket, Settings, Trash2Icon, Upload, Variable, X } from "lucide-react";
import { ArrowLeft, BookA, Brain, CalendarIcon, Clipboard, Download, ExternalLink, FileDown, Fingerprint, Loader2, Mic, Pause, PhoneOff, Play, Plus, Rocket, Settings, Trash2Icon, Upload, Variable, X } from "lucide-react";
import Link from "next/link";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useMemo, useRef, useState } from "react";
@ -38,6 +38,7 @@ import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { SETTINGS_DOCUMENTATION_URLS } from "@/constants/documentation";
import { useOrgConfig } from "@/context/OrgConfigContext";
import { UnsavedChangesProvider, useUnsavedChanges, useUnsavedChangesContext } from "@/context/UnsavedChangesContext";
import { useAudioPlayback } from "@/hooks/useAudioPlayback";
import { detailFromError } from "@/lib/apiError";
@ -49,6 +50,7 @@ import {
DEFAULT_PROVISIONAL_VAD_PAUSE_SECS,
DEFAULT_TURN_START_MIN_WORDS,
DEFAULT_VOICEMAIL_DETECTION_CONFIGURATION,
type ExternalPBXFieldMapping,
resolveWorkflowConfigurations,
TURN_START_STRATEGY_OPTIONS,
type TurnStartStrategy,
@ -273,6 +275,7 @@ function GeneralSection({
workflowId: number;
onSave: (configurations: WorkflowConfigurations, workflowName: string) => Promise<void>;
}) {
const { externalPbxIntegrationsEnabled } = useOrgConfig();
const [name, setName] = useState(workflowName);
const [ambientNoiseConfig, setAmbientNoiseConfig] = useState<AmbientNoiseConfiguration>(
workflowConfigurations.ambient_noise_configuration,
@ -298,6 +301,9 @@ function GeneralSection({
const [includeTranscriptEndTimestamps, setIncludeTranscriptEndTimestamps] = useState(
workflowConfigurations.transcript_configuration?.include_end_timestamps ?? false,
);
const [externalPbxFieldMappings, setExternalPbxFieldMappings] = useState<ExternalPBXFieldMapping[]>(
workflowConfigurations.external_pbx_field_mappings,
);
const [isSaving, setIsSaving] = useState(false);
const [isUploadingAudio, setIsUploadingAudio] = useState(false);
const [audioUploadError, setAudioUploadError] = useState<string | null>(null);
@ -306,6 +312,11 @@ function GeneralSection({
const selectedTurnStartStrategy = TURN_START_STRATEGY_OPTIONS.find(
(option) => option.value === turnStartStrategy,
);
const externalPbxFieldMappingsValid = externalPbxFieldMappings.every(
(mapping) =>
Boolean(mapping.context_path.trim()) &&
/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(mapping.destination_field.trim()),
);
const isDirty = useMemo(() => {
const initAmbient = workflowConfigurations.ambient_noise_configuration;
@ -321,9 +332,11 @@ function GeneralSection({
turnStopStrategy !== workflowConfigurations.turn_stop_strategy ||
contextCompactionEnabled !== workflowConfigurations.context_compaction_enabled ||
includeTranscriptEndTimestamps !==
(workflowConfigurations.transcript_configuration?.include_end_timestamps ?? false)
(workflowConfigurations.transcript_configuration?.include_end_timestamps ?? false) ||
JSON.stringify(externalPbxFieldMappings) !==
JSON.stringify(workflowConfigurations.external_pbx_field_mappings)
);
}, [name, workflowName, ambientNoiseConfig, maxCallDuration, maxUserIdleTimeout, smartTurnStopSecs, turnStartStrategy, turnStartMinWords, provisionalVadPauseSecs, turnStopStrategy, contextCompactionEnabled, includeTranscriptEndTimestamps, workflowConfigurations]);
}, [name, workflowName, ambientNoiseConfig, maxCallDuration, maxUserIdleTimeout, smartTurnStopSecs, turnStartStrategy, turnStartMinWords, provisionalVadPauseSecs, turnStopStrategy, contextCompactionEnabled, includeTranscriptEndTimestamps, externalPbxFieldMappings, workflowConfigurations]);
useUnsavedChanges("general", isDirty);
@ -404,6 +417,7 @@ function GeneralSection({
...(workflowConfigurations.transcript_configuration ?? {}),
include_end_timestamps: includeTranscriptEndTimestamps,
},
external_pbx_field_mappings: externalPbxFieldMappings,
},
name,
);
@ -788,10 +802,94 @@ function GeneralSection({
</div>
</div>
</div>
{externalPbxIntegrationsEnabled && (
<>
<Separator />
{/* External PBX Field Updates */}
<div className="space-y-4">
<div>
<h3 className="text-sm font-medium">External PBX Field Updates</h3>
<p className="text-xs text-muted-foreground mt-0.5">
Optionally copy final gathered-context values into provider-native fields before transfer or hangup.
</p>
</div>
<div className="flex items-center justify-between">
<Label className="text-sm">Field Mappings</Label>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => setExternalPbxFieldMappings((current) => [
...current,
{ context_path: "", destination_field: "" },
])}
>
<Plus className="mr-1 h-4 w-4" /> Add mapping
</Button>
</div>
<div className="space-y-2">
{externalPbxFieldMappings.map((mapping, index) => (
<div key={index} className="grid grid-cols-[1fr_1fr_auto] gap-2">
<Input
aria-label={`Gathered context field ${index + 1}`}
value={mapping.context_path}
onChange={(event) => setExternalPbxFieldMappings((current) =>
current.map((item, itemIndex) =>
itemIndex === index
? { ...item, context_path: event.target.value }
: item,
)
)}
placeholder="qualified"
/>
<Input
aria-label={`External PBX destination field ${index + 1}`}
value={mapping.destination_field}
onChange={(event) => setExternalPbxFieldMappings((current) =>
current.map((item, itemIndex) =>
itemIndex === index
? { ...item, destination_field: event.target.value }
: item,
)
)}
placeholder="address3"
/>
<Button
type="button"
variant="ghost"
size="icon"
aria-label={`Remove external PBX field mapping ${index + 1}`}
onClick={() => setExternalPbxFieldMappings((current) =>
current.filter((_, itemIndex) => itemIndex !== index)
)}
>
<Trash2Icon className="h-4 w-4" />
</Button>
</div>
))}
{externalPbxFieldMappings.length === 0 && (
<p className="text-xs text-muted-foreground">
No external fields will be updated. Context names may be direct extracted-variable names or paths such as extracted_variables.qualified.
</p>
)}
{!externalPbxFieldMappingsValid && (
<p className="text-xs text-destructive">
Each mapping needs a context field and a destination field containing only letters, numbers, and underscores.
</p>
)}
</div>
</div>
</>
)}
</CardContent>
<CardFooter className="justify-end gap-3 border-t pt-6">
{isDirty && <span className="text-xs text-muted-foreground">Unsaved changes</span>}
<Button onClick={handleSave} disabled={isSaving || !isDirty}>
<Button
onClick={handleSave}
disabled={isSaving || !isDirty || (externalPbxIntegrationsEnabled && !externalPbxFieldMappingsValid)}
>
{isSaving ? "Saving..." : "Save General Settings"}
</Button>
</CardFooter>