Merge pull request #20 from dograh-hq/chore/ux-improvements

feat: UX improvements for onboarding
This commit is contained in:
Abhishek 2025-10-04 11:06:01 +05:30 committed by GitHub
commit d39a8111a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 97 additions and 51 deletions

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ __pycache__
/run/
infrastructure/
nginx/
prd/

View file

@ -3,3 +3,4 @@ ruff==0.11.3
pytest==8.3.5
pytest-asyncio==0.26.0
pre-commit==4.2.0
watchfiles==1.1.0

View file

@ -157,6 +157,8 @@ class AzureLLMService(BaseLLMConfiguration):
# Dograh LLM Service
class DograhLLMModel(str, Enum):
DEFAULT = "default"
FAST = "fast"
ACCURATE = "accurate"
@register_llm
@ -279,6 +281,8 @@ class OpenAITTSService(BaseTTSConfiguration):
# Dograh TTS Service
class DograhVoice(str, Enum):
DEFAULT = "default"
JOEY = "joey"
RACHEL = "rachel"
class DograhTTSModel(str, Enum):

View file

@ -3,7 +3,7 @@
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { createWorkflowFromTemplateApiV1WorkflowCreateTemplatePost } from '@/client/sdk.gen';
import { createWorkflowFromTemplateApiV1WorkflowCreateTemplatePost, createWorkflowRunApiV1WorkflowWorkflowIdRunsPost } from '@/client/sdk.gen';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader } from '@/components/ui/card';
import {
@ -16,8 +16,10 @@ import {
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { WORKFLOW_RUN_MODES } from '@/constants/workflowRunModes';
import { useAuth } from '@/lib/auth';
import logger from '@/lib/logger';
import { getRandomId } from '@/lib/utils';
export default function CreateWorkflowPage() {
const router = useRouter();
@ -72,8 +74,34 @@ export default function CreateWorkflowPage() {
}
};
const handleModalContinue = () => {
if (workflowId) {
const handleModalContinue = async () => {
if (!workflowId || !user) return;
try {
const accessToken = await getAccessToken();
const workflowRunName = `WR-${getRandomId()}`;
// Create a workflow run
const response = await createWorkflowRunApiV1WorkflowWorkflowIdRunsPost({
path: {
workflow_id: Number(workflowId),
},
body: {
mode: WORKFLOW_RUN_MODES.SMALL_WEBRTC, // Same mode as "Web Call" button
name: workflowRunName
},
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
// Navigate to the workflow run page
if (response.data?.id) {
router.push(`/workflow/${workflowId}/run/${response.data.id}`);
}
} catch (err) {
logger.error(`Error creating workflow run: ${err}`);
// Fallback to workflow page if run creation fails
router.push(`/workflow/${workflowId}`);
}
};
@ -222,16 +250,13 @@ export default function CreateWorkflowPage() {
<DialogDescription asChild>
<div className="text-base mt-4 space-y-3 text-gray-700 dark:text-gray-300">
<p>
A starter template has been generated for your use case, with some randomised data and sample actions.
A voice agent workflow has been generated for your use case, with some artificial data and sample actions.
</p>
<p>
The voice bot is pre-set to communicate in English with an American accent.
</p>
<p>
You&apos;re encouraged to first test the bot and then modify it to your specific requirements and location (currency/accent etc).
</p>
<p className="pt-2 text-sm">
Feel free to join our Slack channel for any queries and star us on GitHub.
Next steps would be to test the voice bot using web call, and then modify it to suit your use case.
</p>
</div>
</DialogDescription>
@ -241,7 +266,7 @@ export default function CreateWorkflowPage() {
onClick={handleModalContinue}
className="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 font-semibold"
>
Continue to Workflow
Start Web Call
<svg className="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>

View file

@ -5,12 +5,9 @@ import {
Panel,
ReactFlow,
} from "@xyflow/react";
import { ArrowLeft } from 'lucide-react';
import Link from 'next/link';
import WorkflowLayout from '@/app/workflow/WorkflowLayout';
import { FlowEdge, FlowNode, NodeType } from "@/components/flow/types";
import { Button } from '@/components/ui/button';
import { WorkflowConfigurations } from '@/types/workflow-configurations';
import AddNodePanel from "../../../components/flow/AddNodePanel";
@ -75,15 +72,6 @@ function RenderWorkflow({ initialWorkflowName, workflowId, initialFlow, initialT
saveWorkflowConfigurations
} = useWorkflowState({ initialWorkflowName, workflowId, initialFlow, initialTemplateContextVariables, initialWorkflowConfigurations });
const backButton = (
<Link href="/workflow">
<Button variant="outline" size="sm" className="flex items-center gap-1">
<ArrowLeft className="h-4 w-4" />
Workflows
</Button>
</Link>
);
const headerActions = (
<WorkflowHeader
workflowValidationErrors={workflowValidationErrors}
@ -98,7 +86,7 @@ function RenderWorkflow({ initialWorkflowName, workflowId, initialFlow, initialT
return (
<WorkflowProvider value={{ saveWorkflow }}>
<WorkflowLayout headerActions={headerActions} backButton={backButton} showFeaturesNav={false}>
<WorkflowLayout headerActions={headerActions} showFeaturesNav={false}>
<div className="h-[calc(100vh-80px)]">
<ReactFlow
nodes={nodes}

View file

@ -12,6 +12,7 @@ import { OnboardingTooltip } from '@/components/onboarding/OnboardingTooltip';
import { Button } from "@/components/ui/button";
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { WORKFLOW_RUN_MODES } from '@/constants/workflowRunModes';
import { useOnboarding } from '@/context/OnboardingContext';
import { useUserConfig } from "@/context/UserConfigContext";
import { useAuth } from '@/lib/auth';
@ -203,7 +204,7 @@ const WorkflowHeader = ({ isDirty, workflowName, rfInstance, onRun, workflowId,
if (!hasSeenTooltip('web_call')) {
markTooltipSeen('web_call');
}
onRun("smallwebrtc"); // Don't change the mode since its defined in the database enum
onRun(WORKFLOW_RUN_MODES.SMALL_WEBRTC);
}}
disabled={hasValidationErrors}
>

View file

@ -94,7 +94,7 @@ const BrowserCall = ({ workflowId, workflowRunId, accessToken, initialContextVar
<>
<Card className="w-full max-w-4xl mx-auto">
<CardHeader>
<CardTitle>Agent Run</CardTitle>
<CardTitle>Call Voice Agent</CardTitle>
</CardHeader>
<CardContent>

View file

@ -73,7 +73,6 @@ export const AudioControls = ({
<div className="flex flex-col items-center justify-center space-y-6 p-8">
{!connectionActive ? (
<>
<p className="text-sm text-gray-600">Ready to start your call</p>
<button
onClick={start}
disabled={isStarting}

View file

@ -1,17 +1,19 @@
'use client';
import { ArrowLeft, FileText, Video } from 'lucide-react';
import { FileText, Video } from 'lucide-react';
import Link from 'next/link';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import BrowserCall from '@/app/workflow/[workflowId]/run/[runId]/BrowserCall';
import WorkflowLayout from '@/app/workflow/WorkflowLayout';
import { getWorkflowRunApiV1WorkflowWorkflowIdRunsRunIdGet } from '@/client/sdk.gen';
import { MediaPreviewButtons, MediaPreviewDialog } from '@/components/MediaPreviewDialog';
import { OnboardingTooltip } from '@/components/onboarding/OnboardingTooltip';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { useOnboarding } from '@/context/OnboardingContext';
import { useAuth } from '@/lib/auth';
import { downloadFile } from '@/lib/files';
@ -30,6 +32,8 @@ export default function WorkflowRunPage() {
const auth = useAuth();
const [workflowRun, setWorkflowRun] = useState<WorkflowRunResponse | null>(null);
const [accessToken, setAccessToken] = useState<string | null>(null);
const { hasSeenTooltip, markTooltipSeen } = useOnboarding();
const customizeButtonRef = useRef<HTMLButtonElement>(null);
// Redirect if not authenticated
useEffect(() => {
@ -76,23 +80,6 @@ export default function WorkflowRunPage() {
fetchWorkflowRun();
}, [params.workflowId, params.runId, auth]);
const backButton = (
<div className="flex gap-2">
<Link href={`/workflow/${params.workflowId}`}>
<Button variant="outline" size="sm" className="flex items-center gap-1">
<ArrowLeft className="h-4 w-4" />
Workflow
</Button>
</Link>
<Link href={`/workflow/${params.workflowId}/runs`}>
<Button variant="outline" size="sm" className="flex items-center gap-1">
<ArrowLeft className="h-4 w-4" />
Workflow Runs
</Button>
</Link>
</div>
);
let returnValue = null;
if (isLoading) {
@ -123,12 +110,31 @@ export default function WorkflowRunPage() {
<div className="w-full max-w-4xl space-y-6">
<Card className="border-gray-100">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle className="text-2xl">Agent Run Completed</CardTitle>
<div className="h-8 w-8 bg-green-100 rounded-full flex items-center justify-center">
<svg className="h-5 w-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
<div className="flex items-center gap-4">
<CardTitle className="text-2xl">Agent Run Completed</CardTitle>
<div className="h-8 w-8 bg-green-100 rounded-full flex items-center justify-center">
<svg className="h-5 w-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
</div>
</div>
<Link href={`/workflow/${params.workflowId}`}>
<Button
ref={customizeButtonRef}
variant="outline"
className="gap-2"
onClick={() => {
if (!hasSeenTooltip('customize_workflow')) {
markTooltipSeen('customize_workflow');
}
}}
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
Customize Workflow
</Button>
</Link>
</CardHeader>
<CardContent>
<p className="text-gray-600 mb-8">Your voice agent run has been completed successfully. You can preview or download the transcript and recording.</p>
@ -207,9 +213,21 @@ export default function WorkflowRunPage() {
}
return (
<WorkflowLayout backButton={backButton}>
<WorkflowLayout>
{returnValue}
{dialog}
{/* Onboarding Tooltip for Customize Workflow */}
{workflowRun?.is_completed && (
<OnboardingTooltip
title='Customize Your Workflow'
targetRef={customizeButtonRef}
message="Edit your workflow to adjust the voice agent's behavior, add new steps, or modify the conversation flow."
onDismiss={() => markTooltipSeen('customize_workflow')}
showNext={false}
isVisible={!hasSeenTooltip('customize_workflow')}
/>
)}
</WorkflowLayout>
);
}

View file

@ -0,0 +1,9 @@
/**
* Workflow run mode constants
* These modes determine how a workflow run is executed
*/
export const WORKFLOW_RUN_MODES = {
SMALL_WEBRTC: 'smallwebrtc',
} as const;
export type WorkflowRunMode = typeof WORKFLOW_RUN_MODES[keyof typeof WORKFLOW_RUN_MODES];

View file

@ -2,7 +2,7 @@
import { createContext, useContext, useEffect, useState } from 'react';
export type TooltipKey = 'web_call'; // Add more tooltip keys as needed
export type TooltipKey = 'web_call' | 'customize_workflow'; // Add more tooltip keys as needed
interface OnboardingState {
seenTooltips: TooltipKey[];