Skip to content

Commit

Permalink
chore: 🔖 release new app version
Browse files Browse the repository at this point in the history
  • Loading branch information
emiliosheinz authored Apr 3, 2023
2 parents 26893c5 + 4481175 commit 1330dbb
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 105 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ AWS_CLOUD_FRONT_URL=

#OpenAI
OPENAI_API_KEY=

#APIS
# NEXT_PUBLIC_BRISKLY_GENERATE_FLASH_CARDS_API_URL=http://localhost:3333
NEXT_PUBLIC_BRISKLY_GENERATE_FLASH_CARDS_API_URL=https://flashcards-api.briskly.app
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
![Briskly! Your AI powered flashcards app.](/docs/images/banner.png)

## Useful Content

- [Storing Images in S3 from Node Server](https://www.youtube.com/watch?v=eQAIojcArRY&ab)
- [Set up a CloudFront CDN for an S3 Bucket](https://www.youtube.com/watch?v=kbI7kRWAU-w)
58 changes: 43 additions & 15 deletions src/contexts/create-new-deck/create-new-deck.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { useForm } from 'react-hook-form'

import { zodResolver } from '@hookform/resolvers/zod'
import { Visibility } from '@prisma/client'
import type { QueryFunctionContext } from '@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'
import { useSetAtom } from 'jotai'
import { useRouter } from 'next/router'

import { DECK_VISIBILITY_OPTIONS } from '~/constants'
import { env } from '~/env/client.mjs'
import { api, handleApiClientSideError } from '~/utils/api'
import { fullScreenLoaderAtom } from '~/utils/atoms'
import { compress } from '~/utils/image'
Expand Down Expand Up @@ -39,6 +42,25 @@ const uploadImage = async (uploadUrl: string, image?: File) => {
})
}

const fetchAiPoweredCards = async (
context: QueryFunctionContext,
): Promise<Array<CardInput>> => {
const [title, ...topics] = context.queryKey as [string]

const params = new URLSearchParams({ title })
for (const topic of topics) params.append('topics', topic)

const url = `${env.NEXT_PUBLIC_BRISKLY_GENERATE_FLASH_CARDS_API_URL}/ai-powered-flashcards?${params}`

const response = await fetch(url)

if (!response.ok) {
throw response
}

return response.json()
}

const isEditingDeck = (
deck?: DeckWithCardsAndTopics | null,
): deck is DeckWithCardsAndTopics => !!deck
Expand Down Expand Up @@ -73,20 +95,6 @@ export function CreateNewDeckContextProvider(
const getFileUploadConfigMutation =
api.files.getFileUploadConfig.useMutation()

const {
mutate: generateAiPoweredCardsMutation,
isLoading: isGeneratingAiPoweredCards,
isError: hasErrorGeneratingAiPoweredCards,
} = api.cards.generateAiPoweredCards.useMutation({
onSuccess: aiPoweredCards => {
setCards(prevCards => [...prevCards, ...aiPoweredCards])
notify.success('Bip Bop, cards gerados com sucesso. Aproveite 🤖')
},
onError: () => {
notify.error('Ocorreu um erro ao gerar os cards. Tente novamente!')
},
})

/**
* Shared states between creation and edit
*/
Expand Down Expand Up @@ -121,6 +129,26 @@ export function CreateNewDeckContextProvider(
},
})

const {
refetch: generateAiPoweredCardsMutation,
isFetching: isGeneratingAiPoweredCards,
isError: hasErrorGeneratingAiPoweredCards,
} = useQuery(
[createNewDeckForm.getValues().title, ...topics.map(({ title }) => title)],
fetchAiPoweredCards,
{
retry: false,
enabled: false,
onSuccess: aiPoweredCards => {
setCards(prevCards => [...prevCards, ...aiPoweredCards])
notify.success('Bip Bop, cards gerados com sucesso. Aproveite 🤖')
},
onError: () => {
notify.error('Ocorreu um erro ao gerar os cards. Tente novamente!')
},
},
)

const addTopic = (topic: string) => {
setTopics(topics => [
...topics.filter(({ title }) => title !== topic),
Expand Down Expand Up @@ -282,7 +310,7 @@ export function CreateNewDeckContextProvider(
return
}

generateAiPoweredCardsMutation({ topics, title })
generateAiPoweredCardsMutation()
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/env/schema.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const serverSchema = z.object({
* To expose them to the client, prefix them with `NEXT_PUBLIC_`.
*/
export const clientSchema = z.object({
// NEXT_PUBLIC_CLIENTVAR: z.string(),
NEXT_PUBLIC_BRISKLY_GENERATE_FLASH_CARDS_API_URL: z.string(),
})

/**
Expand All @@ -50,5 +50,6 @@ export const clientSchema = z.object({
* @type {{ [k in keyof z.infer<typeof clientSchema>]: z.infer<typeof clientSchema>[k] | undefined }}
*/
export const clientEnv = {
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
NEXT_PUBLIC_BRISKLY_GENERATE_FLASH_CARDS_API_URL:
process.env.NEXT_PUBLIC_BRISKLY_GENERATE_FLASH_CARDS_API_URL,
}
2 changes: 0 additions & 2 deletions src/server/api/root.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { answerValidationReportsRouter } from './routers/answer-validation-reports'
import { cardsRouter } from './routers/cards'
import { decksRouter } from './routers/decks'
import { filesRouter } from './routers/files'
import { studySessionRouter } from './routers/study-session'
Expand All @@ -16,7 +15,6 @@ export const appRouter = createTRPCRouter({
files: filesRouter,
studySession: studySessionRouter,
user: userRouter,
cards: cardsRouter,
answerValidationReports: answerValidationReportsRouter,
})

Expand Down
11 changes: 0 additions & 11 deletions src/server/api/routers/cards/cards.router.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/server/api/routers/cards/index.ts

This file was deleted.

73 changes: 0 additions & 73 deletions src/utils/openai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,9 @@
* !DO NO USE THIS FILE IN THE CLIENT SIDE
*/
import calculateSimilarity from 'compute-cosine-similarity'
import random from 'lodash/random'
import shuffle from 'lodash/shuffle'
import { Configuration, OpenAIApi } from 'openai'

import { MINIMUM_ACCEPTED_SIMILARITY } from '~/constants'
import type {
CardInput,
TopicInput,
} from '~/contexts/create-new-deck/create-new-deck.types'
import { env } from '~/env/server.mjs'

const configuration = new Configuration({
Expand All @@ -30,9 +24,6 @@ const createEmbedding = (str: string) =>
},
)

const trimAndRemoveDoubleQuotes = (str: string) =>
str.trim().replaceAll('"', '')

/**
* Embed both strings with text-embedding-ada-002 and calculate their distance with cosine similarity
* Reference: https://platform.openai.com/docs/guides/embeddings/limitations-risks
Expand Down Expand Up @@ -66,67 +57,3 @@ export async function verifyIfAnswerIsRight(

return isRight
}

type GenerateFlashCardsParam = {
topics: Array<TopicInput>
title: string
}

export async function generateFlashCards({
topics,
title,
}: GenerateFlashCardsParam): Promise<Array<CardInput>> {
let generatedJsonString: string | undefined

try {
const amountOfCards = 3
const charactersPerSentence = 65

/**
* Selects between 1 and 3 random topics from the array of topics
* and build a string with the topics separated by 'ou'
*/
const joinedTopics = shuffle(topics)
.map(({ title }) => title)
.slice(0, random(1, 3))
.join(' ou ')

/** Build prompt asking OpenAI to generate a csv string */
const prompt = `Levando em conta o contexto ${title}, gere um Array JSON de tamanho ${amountOfCards} com perguntas e respostas curtas e diretas, de no máximo ${charactersPerSentence} caracteres, sobre ${joinedTopics}. [{question: "pergunta", answer: "resposta"}, ...]`

const response = await openai.createChatCompletion(
{
n: 1,
messages: [{ role: 'user', content: prompt }],
temperature: 0.8,
model: 'gpt-3.5-turbo',
max_tokens: amountOfCards * charactersPerSentence,
},
{ timeout: 30_000 },
)

generatedJsonString = response.data.choices[0]?.message?.content

if (!generatedJsonString) {
throw new Error('Não foi possível gerar as perguntas e respostas.')
}

const generatedJson = JSON.parse(generatedJsonString)

const cards: Array<CardInput> = generatedJson.map(
({ question, answer }: { question: string; answer: string }) => ({
question: trimAndRemoveDoubleQuotes(question),
validAnswers: trimAndRemoveDoubleQuotes(answer),
isAiPowered: true,
}),
)

return cards
} catch (error) {
/**
* Added to improve error tracking on log monitoring tools
*/
console.error(error, generatedJsonString)
throw error
}
}
2 changes: 1 addition & 1 deletion src/utils/validators/card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const CardInputSchema = z.object({
.refine(
(answers: string) =>
answers.split(';').length <= MAX_VALID_ANSWERS_PER_CARD,
`Um Card não pode ter mais que ${10} respostas válidas`,
`Um Card não pode ter mais que ${MAX_VALID_ANSWERS_PER_CARD} respostas válidas`,
)
.refine(
(answers: string) =>
Expand Down

1 comment on commit 1330dbb

@vercel
Copy link

@vercel vercel bot commented on 1330dbb Apr 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

briskly – ./

briskly-emiliosheinz.vercel.app
briskly-git-main-emiliosheinz.vercel.app
briskly.app

Please sign in to comment.