Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/(dashboard)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export default async function Dashboard({
<div className="flex-1 flex flex-col justify-center">
<div className="flex items-center justify-between mb-2">
<label htmlFor="dashboard-objective" className="block text-base font-medium">What do you want to find out?</label>
<Link href="/create">
<Link href="/templates">
<Button variant="ghost" size="sm">
<FileText className="w-4 h-4" />
Templates
Expand Down
4 changes: 4 additions & 0 deletions src/app/api/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ const routeMetadata: MetadataConfig = {
'Create',
'Manage your Harmonica conversations and settings'
),
'/templates': getWithTitleAndDescription(
'Templates',
'Choose from ready-made session templates to get started quickly'
),
'/login': getWithTitleAndDescription('Sign in to Harmonica'),
};

Expand Down
327 changes: 266 additions & 61 deletions src/app/create/MultiStepForm.tsx

Large diffs are not rendered by default.

15 changes: 3 additions & 12 deletions src/app/create/QuestionContainerHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
export function QuestionContainerHeader() {
return (
<div className="space-y-4 mb-8">
<div className="text-center space-y-2">
<h2 className="text-2xl font-semibold">Before we launch your session...</h2>
<p className="text-gray-600">
We need to know what basic information to collect from participants (like name, email, role).
Your actual session questions are already created!
</p>
</div>


<div className="space-y-2">
<h3 className="text-lg font-semibold">What information do you want participants to share?</h3>
<p className="text-sm text-gray-600">
This information can be used to identify responses in your results.
<h2 className="text-2xl font-semibold">Before participants start</h2>
<p className="text-gray-600">
Collect a few basic details like name, email, or role before your conversational survey begins. This helps link each response to the right participant.
</p>
</div>
</div>
Expand Down
15 changes: 6 additions & 9 deletions src/app/create/ShareParticipants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import QuestionModal from './QuestionModal';
import { QuestionContainerHeader } from './QuestionContainerHeader';
import { QuestionContainer } from './QuestionContainer';
import { HRMarkdown } from '@/components/HRMarkdown';
import { Check } from 'lucide-react';

interface ShareParticipantsProps {
onQuestionsUpdate: (questions: QuestionInfo[]) => void;
Expand Down Expand Up @@ -109,15 +110,15 @@ export default function ShareParticipants({
<div className="max-w-6xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Session Preview - Left Column */}
<div className="bg-purple-50 border border-purple-200 rounded-xl p-6 h-fit">
<div className="rounded-xl p-6 h-fit">
<div className="space-y-4">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-purple-400 rounded-full"></div>
<h3 className="text-lg font-semibold text-purple-800">Your Generated Session</h3>
<Check className="w-4 h-4 text-yellow-500" />
<h3 className="text-lg font-medium">Your Generated Session</h3>
</div>

{sessionPreview ? (
<div className="bg-white rounded-lg p-4 border border-purple-100">
<div className="bg-white rounded-lg p-6 border [&_h2]:!text-base [&_h2]:!font-medium">
<HRMarkdown content={sessionPreview} />
</div>
) : (
Expand All @@ -127,15 +128,11 @@ export default function ShareParticipants({
</div>
</div>
)}

<div className="text-xs text-purple-600 bg-purple-100 rounded-lg p-3">
<strong>What you'll get:</strong> Personalized questions, discussion prompts, and activities tailored to your objectives. Participants will engage with this content during the session.
</div>
</div>
</div>

{/* Participant Info Form - Right Column */}
<div className="bg-white rounded-xl shadow p-6">
<div className="bg-yellow-50 rounded-2xl border shadow-sm p-8">
<QuestionContainerHeader />
<QuestionContainer
questions={questions}
Expand Down
8 changes: 1 addition & 7 deletions src/app/create/StepHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
export function StepHeader() {
return (
<div className="flex items-center justify-center mb-6">

<h1 className="font-medium text-sm text-gray-400 px-4 py-2 border rounded-lg shadow-sm">New Session</h1>

</div>
);
return null;
}
9 changes: 4 additions & 5 deletions src/app/create/StepNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,32 @@ export function StepNavigation({
const isLastStep = currentStep === totalSteps;

return (
<div className="flex justify-center gap-8 mt-8 max-w-md mx-auto">
<div className="flex justify-between mt-8 md:min-w-[720px]">
<Button
type="button"
variant="outline"
onClick={onPrevious}
disabled={isFirstStep}
className="flex items-center gap-2"
>
<ChevronLeft className="w-4 h-4" />
Back
</Button>

<Button
type="button"
variant="default"
onClick={onNext}
disabled={isNextDisabled || isLoading}
className="flex items-center gap-2 bg-black hover:bg-gray-800 text-white"
>
{isLoading ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
Loading...
</>
) : (
<>
{nextLabel === 'Generate Session' && <Sparkles className="w-4 h-4" />}
{nextLabel}
<span className="px-1">{nextLabel}</span>
<ChevronRight className="w-4 h-4" />
</>
)}
Expand Down
25 changes: 16 additions & 9 deletions src/app/create/StepProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,29 @@ export function StepProgress({
totalSteps,
isObjectivePrefilled = false
}: StepProgressProps) {
// Always show the actual step numbers (0-4, but display as 1-5)
const displayStep = currentStep + 1;
// Hide entire component on intro step
if (currentStep === 0) {
return null;
}

// Display numbered steps starting at Objective as 1..totalSteps
const displayStep = Math.max(currentStep, 0);
const displayTotal = totalSteps;

// Calculate progress percentage
// Step 0: 5%, Step 1: 25%, Step 2: 45%, Step 3: 65%, Step 4: 85%
const progressPercentage = (currentStep * 20 + 5);
// Intro: minimal progress, then proportional across numbered steps
const progressPercentage = Math.min(100, Math.max(0, (currentStep / totalSteps) * 100));

return (
<div className="w-full max-w-md mx-auto mb-8">
{/* Step indicator */}
<div className="text-center mb-4">
<span className="text-sm font-medium text-muted-foreground">
Step {displayStep} of {displayTotal}
</span>
</div>
{currentStep !== 0 && (
<div className="text-center mb-4">
<span className="text-sm font-medium text-muted-foreground">
Step {displayStep} of {displayTotal}
</span>
</div>
)}

{/* Progress bar */}
<div className="w-full bg-gray-200 rounded-full h-2">
Expand Down
50 changes: 50 additions & 0 deletions src/app/create/creationFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export default function CreationFlow() {
});

const [templateId, setTemplateId] = useState<string | undefined>();
const [targetStep, setTargetStep] = useState<number | undefined>(undefined);

const onFormDataChange = (form: Partial<SessionBuilderData>) => {
setFormData((prevData) => ({ ...prevData, ...form }));
Expand All @@ -105,6 +106,8 @@ export default function CreationFlow() {
// Handle session storage pre-fill data
useEffect(() => {
const prefillData = sessionStorage.getItem('createSessionPrefill');
const skipToReview = sessionStorage.getItem('templateSkipToReview');

if (prefillData) {
try {
const data = JSON.parse(prefillData);
Expand All @@ -113,6 +116,11 @@ export default function CreationFlow() {
onFormDataChange(data);
enabledSteps[1] = true;
setActiveStep('Create');

// If coming from templates, set target step to 3 (Context step)
if (skipToReview === 'true') {
setTargetStep(3);
}
}
// Always clear the data
sessionStorage.removeItem('createSessionPrefill');
Expand All @@ -121,6 +129,11 @@ export default function CreationFlow() {
sessionStorage.removeItem('createSessionPrefill');
}
}

// Clear the template flag
if (skipToReview === 'true') {
sessionStorage.removeItem('templateSkipToReview');
}
}, []);

const handleCreateComplete = async (e: React.FormEvent) => {
Expand Down Expand Up @@ -153,6 +166,35 @@ export default function CreationFlow() {
}
};

const handleQuickStartComplete = async (quickFormData: Partial<SessionBuilderData>) => {
// Quick Start bypasses validation and goes straight to generation
// Ensure formData is updated with Quick Start values
onFormDataChange(quickFormData);

setIsLoading(true);
enabledSteps[1] = true;
setActiveStep('Refine');

try {
// Use the updated formData (which will include quickFormData after state update)
// We need to merge quickFormData with existing formData for getInitialPrompt
const mergedFormData = { ...formData, ...quickFormData };
const responseFullPrompt = await sendApiCall({
target: ApiTarget.Builder,
action: ApiAction.CreatePrompt,
data: mergedFormData,
});
latestFullPromptRef.current = responseFullPrompt.fullPrompt;
getStreamOfSummary({
fullPrompt: responseFullPrompt.fullPrompt,
});
} catch (error) {
setThrowError(() => {
throw error;
});
}
};

const handleEditPrompt = async (editValue: string) => {
console.log('[i] Edit instructions: ', editValue);

Expand Down Expand Up @@ -297,6 +339,8 @@ IMPORTANT:
onValidationError={setHasValidationErrors}
isLoading={isLoading}
onBackToDashboard={() => route.push('/')}
initialStep={targetStep && formData.goal?.trim() && formData.critical?.trim() ? targetStep : undefined}
onQuickStartComplete={handleQuickStartComplete}
/>
),
Refine: isLoading ? (
Expand All @@ -310,6 +354,7 @@ IMPORTANT:
setCurrentVersion={setCurrentVersion}
isEditing={isEditingPrompt}
handleEdit={handleEditPrompt}
handleReplaceFullPrompt={handleReplaceFullPrompt}
/>
),
Share: !isLoading && activeStep === 'Share' && (
Expand Down Expand Up @@ -456,4 +501,9 @@ IMPORTANT:
}
completeStreaming();
}

function handleReplaceFullPrompt(fullPrompt: string) {
latestFullPromptRef.current = fullPrompt;
getStreamOfSummary({ fullPrompt });
}
}
Loading