mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-25 08:48:13 +02:00
feat: add openai realtime models (#298)
* feat: add openai realtime models * chore: bump pipecat * fix: resample telephony audio for openai realtime * fix: sampling rate fix for openai realtime * chore: clean up dead code
This commit is contained in:
parent
45b00cd5d0
commit
2381a803ad
45 changed files with 1991 additions and 173 deletions
|
|
@ -13,6 +13,7 @@ import { Label } from "@/components/ui/label";
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { VoiceSelector } from "@/components/VoiceSelector";
|
||||
import { LANGUAGE_DISPLAY_NAMES } from "@/constants/languages";
|
||||
import { useUserConfig } from "@/context/UserConfigContext";
|
||||
|
|
@ -30,6 +31,7 @@ interface SchemaProperty {
|
|||
$ref?: string;
|
||||
description?: string;
|
||||
format?: string;
|
||||
multiline?: boolean;
|
||||
}
|
||||
|
||||
interface ProviderSchema {
|
||||
|
|
@ -501,18 +503,26 @@ export function ServiceConfigurationForm({
|
|||
|
||||
{currentProvider && providerSchema && configFields.length > 1 && (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{configFields.slice(1).map((field) => (
|
||||
<div key={field} className="space-y-2">
|
||||
<Label className="capitalize">{field.replace(/_/g, ' ')}</Label>
|
||||
{renderField(service, field, providerSchema)}
|
||||
</div>
|
||||
))}
|
||||
{configFields.slice(1).map((field) => {
|
||||
const fieldSchema = providerSchema.properties[field];
|
||||
const actualFieldSchema = fieldSchema?.$ref && providerSchema.$defs
|
||||
? providerSchema.$defs[fieldSchema.$ref.split('/').pop() || '']
|
||||
: fieldSchema;
|
||||
const fullWidth = actualFieldSchema?.multiline;
|
||||
return (
|
||||
<div key={field} className={`space-y-2 ${fullWidth ? "col-span-2" : ""}`}>
|
||||
<Label className="capitalize">{field.replace(/_/g, ' ')}</Label>
|
||||
{renderField(service, field, providerSchema)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentProvider && providerSchema && providerSchema.properties.api_key && (
|
||||
<div className="space-y-2">
|
||||
<Label>{mode === 'override' ? 'API Key (leave empty to use global)' : 'API Key(s)'}</Label>
|
||||
{renderFieldDescription("api_key", providerSchema)}
|
||||
{apiKeys[service].map((key, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<Input
|
||||
|
|
@ -564,7 +574,28 @@ export function ServiceConfigurationForm({
|
|||
);
|
||||
};
|
||||
|
||||
const renderFieldDescription = (field: string, providerSchema: ProviderSchema) => {
|
||||
const schema = providerSchema.properties[field];
|
||||
if (!schema) return null;
|
||||
const actualSchema = schema.$ref && providerSchema.$defs
|
||||
? providerSchema.$defs[schema.$ref.split('/').pop() || '']
|
||||
: schema;
|
||||
if (!actualSchema?.description) return null;
|
||||
return (
|
||||
<p className="text-xs text-muted-foreground">{actualSchema.description}</p>
|
||||
);
|
||||
};
|
||||
|
||||
const renderField = (service: ServiceSegment, field: string, providerSchema: ProviderSchema) => {
|
||||
return (
|
||||
<>
|
||||
{renderFieldInput(service, field, providerSchema)}
|
||||
{renderFieldDescription(field, providerSchema)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFieldInput = (service: ServiceSegment, field: string, providerSchema: ProviderSchema) => {
|
||||
const schema = providerSchema.properties[field];
|
||||
const actualSchema = schema.$ref && providerSchema.$defs
|
||||
? providerSchema.$defs[schema.$ref.split('/').pop() || '']
|
||||
|
|
@ -699,6 +730,19 @@ export function ServiceConfigurationForm({
|
|||
);
|
||||
}
|
||||
|
||||
if (actualSchema?.multiline) {
|
||||
return (
|
||||
<Textarea
|
||||
rows={6}
|
||||
className="font-mono text-xs"
|
||||
placeholder={`Enter ${field}`}
|
||||
{...register(`${service}_${field}`, {
|
||||
required: service !== "embeddings" && providerSchema.required?.includes(field),
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
type={actualSchema?.type === "number" ? "number" : "text"}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,11 @@ export { CreateCredentialDialog } from "./create-credential-dialog";
|
|||
export { CredentialSelector } from "./credential-selector";
|
||||
export { type HttpMethod, HttpMethodSelector } from "./http-method-selector";
|
||||
export { KeyValueEditor, type KeyValueItem } from "./key-value-editor";
|
||||
export { ParameterEditor, type ParameterType,type ToolParameter } from "./parameter-editor";
|
||||
export { UrlInput, type UrlValidationResult,validateUrl } from "./url-input";
|
||||
export {
|
||||
ParameterEditor,
|
||||
type ParameterType,
|
||||
PresetParameterEditor,
|
||||
type PresetToolParameter,
|
||||
type ToolParameter,
|
||||
} from "./parameter-editor";
|
||||
export { UrlInput, type UrlValidationResult, validateUrl } from "./url-input";
|
||||
|
|
|
|||
|
|
@ -23,6 +23,13 @@ export interface ToolParameter {
|
|||
required: boolean;
|
||||
}
|
||||
|
||||
export interface PresetToolParameter {
|
||||
name: string;
|
||||
type: ParameterType;
|
||||
valueTemplate: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
interface ParameterEditorProps {
|
||||
parameters: ToolParameter[];
|
||||
onChange: (parameters: ToolParameter[]) => void;
|
||||
|
|
@ -165,3 +172,146 @@ export function ParameterEditor({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface PresetParameterEditorProps {
|
||||
parameters: PresetToolParameter[];
|
||||
onChange: (parameters: PresetToolParameter[]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function PresetParameterEditor({
|
||||
parameters,
|
||||
onChange,
|
||||
disabled = false,
|
||||
}: PresetParameterEditorProps) {
|
||||
const addParameter = () => {
|
||||
onChange([
|
||||
...parameters,
|
||||
{ name: "", type: "string", valueTemplate: "", required: true },
|
||||
]);
|
||||
};
|
||||
|
||||
const updateParameter = (
|
||||
index: number,
|
||||
field: keyof PresetToolParameter,
|
||||
value: string | boolean
|
||||
) => {
|
||||
const newParams = [...parameters];
|
||||
newParams[index] = { ...newParams[index], [field]: value };
|
||||
onChange(newParams);
|
||||
};
|
||||
|
||||
const removeParameter = (index: number) => {
|
||||
onChange(parameters.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{parameters.length === 0 && (
|
||||
<div className="text-sm text-muted-foreground py-4 text-center border border-dashed rounded-md">
|
||||
No preset parameters defined. Add one to inject a fixed value or workflow context into the request.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{parameters.map((param, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="border rounded-lg p-4 space-y-3 bg-muted/20"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
Preset Parameter {index + 1}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removeParameter(index)}
|
||||
disabled={disabled}
|
||||
className="h-8 w-8"
|
||||
>
|
||||
<Trash2Icon className="h-4 w-4 text-muted-foreground hover:text-destructive" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Name</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
Key sent to the API, like "phone_number" or "customer_id"
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="e.g., phone_number"
|
||||
value={param.name}
|
||||
onChange={(e) =>
|
||||
updateParameter(index, "name", e.target.value)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Type</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
JSON type to send to the API
|
||||
</Label>
|
||||
<Select
|
||||
value={param.type}
|
||||
onValueChange={(value: ParameterType) =>
|
||||
updateParameter(index, "type", value)
|
||||
}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="string">String</SelectItem>
|
||||
<SelectItem value="number">Number</SelectItem>
|
||||
<SelectItem value="boolean">Boolean</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Value or Template</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
Use a fixed value or a template like {`{{initial_context.phone_number}}`} or {`{{gathered_context.customer_id}}`}
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="e.g., {{initial_context.phone_number}}"
|
||||
value={param.valueTemplate}
|
||||
onChange={(e) =>
|
||||
updateParameter(index, "valueTemplate", e.target.value)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
id={`preset-required-${index}`}
|
||||
checked={param.required}
|
||||
onCheckedChange={(checked) =>
|
||||
updateParameter(index, "required", checked)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Label htmlFor={`preset-required-${index}`} className="text-sm">
|
||||
Required
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={addParameter}
|
||||
className="w-fit"
|
||||
disabled={disabled}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4 mr-1" /> Add Preset Parameter
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue