mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-25 12:01:04 +02:00
fix: incorporate review comments
This commit is contained in:
parent
534672e659
commit
e486920db2
3 changed files with 103 additions and 17 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue