mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
Merge pull request #20 from dograh-hq/chore/ux-improvements
feat: UX improvements for onboarding
This commit is contained in:
commit
d39a8111a6
11 changed files with 97 additions and 51 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -7,3 +7,4 @@ __pycache__
|
|||
/run/
|
||||
infrastructure/
|
||||
nginx/
|
||||
prd/
|
||||
|
|
@ -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
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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'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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
9
ui/src/constants/workflowRunModes.ts
Normal file
9
ui/src/constants/workflowRunModes.ts
Normal 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];
|
||||
|
|
@ -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[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue