Skip to content

Commit

Permalink
Add Potential Product Names section
Browse files Browse the repository at this point in the history
  • Loading branch information
gruz0 committed Nov 7, 2024
1 parent ae2c92a commit 4704b3a
Show file tree
Hide file tree
Showing 9 changed files with 502 additions and 10 deletions.
42 changes: 42 additions & 0 deletions prompts/00-product-names-analysis.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
You are a branding consultant helping the user come up with potential names for their product. Based on the problem and target audience they described, suggest 5 product names that capture the essence of their product and core functionality.

For each name:
1. Ensure it is short and catchy (ideally 1-3 words).
2. Provide a brief description of what the name means and why it fits the product.
3. Match the tone of the names to the product's target audience (e.g., playful, professional, or neutral).
4. Include a catchy tagline that encapsulates the essence of the product.
5. Provide insight on how the name resonates with the intended target audience.
6. Suggest similar names for users to consider.
7. Briefly describe the branding potential of the name, including aspects like logo potential or marketability.

### Expected Responses:

```json
{
"product_names": [
{
"product_name": "Name1",
"domains": ["name1.com", "name1app.com"],
"why": "A brief description of why this product name is great.",
"tagline": "Catchy tagline that sums up the product.",
"target_audience_insight": "This name appeals to young professionals looking for innovative solutions.",
"similar_names": ["Name1Plus", "Name1Pro"],
"branding_potential": "The name has strong branding potential with a memorable sound and visual appeal."
},
{
"product_name": "Name2",
"domains": ["name2.com", "name2app.com"],
"why": "A brief description of why this product name is great.",
"tagline": "A catchy tagline that captures the product's essence.",
"target_audience_insight": "Appeals to tech-savvy users seeking efficiency.",
"similar_names": ["Name2Lite", "Name2X"],
"branding_potential": "This name could be effectively branded with modern visuals."
}
]
}
```

### Guidelines:
- Friendly Tone: Use a conversational tone, as if you're chatting with a friend.
- Avoid Jargon: Skip formal, sales-like language or buzzwords such as "streamline," "enhance," "tailored," "leverage," "thrilled," etc.
- Ensure the language is clear, practical, and builds confidence in the product's value.
86 changes: 76 additions & 10 deletions src/app/ideas/[id]/IdeaAnalysisReport.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import Link from 'next/link'
import { useRouter } from 'next/navigation'
import React, { useEffect, useState } from 'react'
import FetchingDataMessage from '@/components/FetchingDataMessage'
Expand Down Expand Up @@ -55,6 +56,15 @@ interface Props {
}
differentiationSuggestions: string[]
} | null
productNames: Array<{
productName: string
domains: string[]
why: string
tagline: string
targetAudienceInsight: string
similarNames: string[]
brandingPotential: string
}> | null
}
}

Expand Down Expand Up @@ -614,23 +624,79 @@ export const IdeaAnalysisReport = ({ data }: Props) => {

<div>
<SectionHeader
color="text-gray-600"
color="text-blue-600"
onClick={() => toggleSection('potentialProductNames')}
isExpanded={expandedSections.potentialProductNames}
sectionId="potentialProductNames"
>
This Week: Potential Product Names
Potential Product Names
</SectionHeader>

{expandedSections.potentialProductNames && (
<div id="potentialProductNames">
<SectionDescription>
Here, we brainstorm some catchy names for your product. A good
name can leave a lasting impression and make your product more
memorable. This is a fun part of the process that allows you to
think creatively!
</SectionDescription>
</div>
<>
<div id="potentialProductNames">
<SectionDescription>
Here, we brainstorm some catchy names for your product. A good
name can leave a lasting impression and make your product more
memorable. This is a fun part of the process that allows you to
think creatively!
</SectionDescription>
</div>

{data.productNames !== null ? (
<>
{data.productNames.map((productName, idx) => (
<Section
key={productName.productName}
header={`${idx + 1}. ${productName.productName} - ${productName.tagline}`}
voteable
onUpvote={onUpvote}
onDownvote={onDownvote}
>
<div className="flex flex-col rounded-lg border border-gray-200 bg-gray-50 p-4 pb-0 hover:shadow-lg md:p-6 lg:pb-0">
<Paragraph>
{productName.why} {productName.targetAudienceInsight}
</Paragraph>

<h3 className="mb-2 text-lg font-semibold md:text-xl">
Branding Potential:
</h3>
<Paragraph>{productName.brandingPotential}</Paragraph>

<h3 className="mb-2 text-lg font-semibold md:text-xl">
Potential Domains:
</h3>

<ul className="mb-4 list-disc pl-4">
{productName.domains.map((item, index) => (
<li
key={index}
className="mb-2 pl-1 md:pl-2 md:text-lg"
>
<Link
href={`https://www.namecheap.com/domains/registration/results/?domain=${item}`}
target="_blank"
rel="nofollow noopener noreferrer"
className="text-blue-500 underline hover:text-blue-700"
>
{item}
</Link>
</li>
))}
</ul>

<h3 className="mb-2 text-lg font-semibold md:text-xl">
Similar Product Names:
</h3>
<SimpleUnorderedList items={productName.similarNames} />
</div>
</Section>
))}
</>
) : (
<FetchingDataMessage />
)}
</>
)}
</div>

Expand Down
63 changes: 63 additions & 0 deletions src/idea/adapters/IdeaRepositorySQLite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Repository } from '@/idea/domain/Repository'
import { TargetAudience } from '@/idea/domain/TargetAudience'
import { ValueProposition } from '@/idea/domain/ValueProposition'
import { prisma } from '@/lib/prisma'
import { ProductName } from '../domain/ProductName'
import type { PrismaClient } from '@prisma/client/extension'

type UpdateFn = (idea: Idea) => Idea
Expand Down Expand Up @@ -121,6 +122,27 @@ export class IdeaRepositorySQLite implements Repository {
},
})
}

const productNames = updatedIdea.getProductNames()
if (productNames) {
await prisma.ideaContent.upsert({
where: {
ideaId_key: {
ideaId: id,
key: 'product_names',
},
},
create: {
ideaId: id,
key: 'product_names',
value: JSON.stringify(productNames),
},
update: {
value: JSON.stringify(productNames),
updatedAt: new Date(),
},
})
}
})
}

Expand Down Expand Up @@ -286,4 +308,45 @@ export class IdeaRepositorySQLite implements Repository {
data.differentiationSuggestions
)
}

async getProductNamesByIdeaId(ideaId: string): Promise<ProductName[] | null> {
const productNamesModel = await prisma.ideaContent.findUnique({
where: {
ideaId_key: {
ideaId: ideaId,
key: 'product_names',
},
},
})

if (!productNamesModel) {
return null
}

interface productName {
productName: string
domains: string[]
why: string
tagline: string
targetAudienceInsight: string
similarNames: string[]
brandingPotential: string
}

type productNames = productName[]

const data = JSON.parse(productNamesModel.value) as productNames

return data.map((product) =>
ProductName.New(
product.productName,
product.domains,
product.why,
product.tagline,
product.targetAudienceInsight,
product.similarNames,
product.brandingPotential
)
)
}
}
122 changes: 122 additions & 0 deletions src/idea/adapters/OpenAIService/PotentialNamesEvaluator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import OpenAI from 'openai'
import { z } from 'zod'
import { getPromptContent } from '@/lib/prompts'

interface PotentialName {
productName: string
domains: string[]
why: string
tagline: string
targetAudienceInsight: string
similarNames: string[]
brandingPotential: string
}

type Evaluation = PotentialName[]

interface TargetAudience {
segment: string
description: string
challenges: string[]
}

const ResponseSchema = z.object({
product_names: z.array(
z.object({
product_name: z.string(),
domains: z.array(z.string()),
why: z.string(),
tagline: z.string(),
target_audience_insight: z.string(),
similar_names: z.array(z.string()),
branding_potential: z.string(),
})
),
})

export class PotentialNamesEvaluator {
private readonly openai: OpenAI

constructor(apiKey: string) {
this.openai = new OpenAI({
apiKey: apiKey,
})
}

async evaluatePotentialNames(
problem: string,
marketExistence: string,
targetAudiences: TargetAudience[]
): Promise<Evaluation> {
const promptContent = getPromptContent('00-product-names-analysis')

if (!promptContent) {
throw new Error('Prompt content not found')
}

const response = await this.openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: [
{
type: 'text',
text: promptContent.trim(),
},
],
},
{
role: 'user',
content: [
{
type: 'text',
text: `Here is the problem my product aims to solve: """
${problem.trim()}"""
Also I have a market existence research: """
${marketExistence.trim()}"""
Here are my segments: """
${targetAudiences
.map((targetAudience, idx) => {
let content = ''
content += `Segment ${idx + 1}: ${targetAudience.segment}\n`
content += `Description: ${targetAudience.description}\n`
content += `Challenges:\n${targetAudience.challenges.join('; ')}\n\n`
return content
})
.join('\n\n')}
"""`,
},
],
},
],
// For most factual use cases such as data extraction, and truthful Q&A, the temperature of 0 is best.
// https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api
temperature: 0.7,
max_tokens: 2000,
response_format: {
type: 'json_object',
},
})

// TODO: Store response.usage for better analysis

const content = response.choices[0].message.content ?? ''

const analysis = ResponseSchema.parse(JSON.parse(content))

return analysis.product_names.map((product) => ({
productName: product.product_name,
domains: product.domains,
why: product.why,
tagline: product.tagline,
targetAudienceInsight: product.target_audience_insight,
similarNames: product.similar_names,
brandingPotential: product.branding_potential,
}))
}
}
Loading

0 comments on commit 4704b3a

Please sign in to comment.