@@ -11,6 +11,7 @@ import { useEffect, useState, useRef } from "react";
1111import WordCard from "@/components/WordCard" ;
1212import { useUser } from "@clerk/nextjs" ;
1313import SmoothFadeLayout from "@/components/SmoothFadePageTransition" ;
14+ import { greetings } from "../consts" ;
1415
1516const notifySuccess = ( ) => toast . success ( "Word added successfully!" ) ;
1617const notifyError = ( message : string ) => toast . error ( `${ message } ` ) ;
@@ -27,31 +28,26 @@ export default function AddWord() {
2728
2829 const [ word , setWord ] = useState ( "" ) ;
2930 const [ meaning , setMeaning ] = useState ( "" ) ;
31+ const [ trigger , setTrigger ] = useState ( "" ) ;
3032 const [ examples , setExamples ] = useState < string [ ] > ( [ ] ) ;
3133
3234 const [ debouncedWord , setDebouncedWord ] = useState ( word ) ;
3335 const [ AIDebouncedWord , setAIDebouncedWord ] = useState ( word ) ;
3436
3537 const [ meaningExists , setMeaningExists ] = useState ( false ) ;
3638 const [ meaningLoading , setMeaningLoading ] = useState ( false ) ;
39+ const [ triggerLoading , setTriggerLoading ] = useState ( false ) ;
3740 const [ examplesLoading , setExamplesLoading ] = useState ( false ) ;
3841 const [ greeting , setGreeting ] = useState ( "" ) ;
3942
40- const greetings = [
41- "Discovered a new word? Let's define it!" ,
42- "Found a word no one knows? Add it here!" ,
43- "Help us grow the dictionary! Submit your word." ,
44- "Got a rare word? Share its meaning!" ,
45- "What did you come across?" ,
46- ] ;
47-
4843 useEffect ( ( ) => {
4944 const random = greetings [ Math . floor ( Math . random ( ) * greetings . length ) ] ;
5045 setGreeting ( random ) ;
5146 } , [ ] ) ;
5247
5348 useEffect ( ( ) => {
5449 setMeaning ( "" ) ;
50+ setTrigger ( "" ) ;
5551 setExamples ( [ ] ) ;
5652
5753 const convexSearch = setTimeout ( ( ) => {
@@ -80,7 +76,7 @@ export default function AddWord() {
8076
8177 if ( res . status === 429 )
8278 throw new Error (
83- "Rate limit exceeded. Please wait a moment before trying again."
79+ "Rate limit exceeded. Please wait a moment before trying again." ,
8480 ) ;
8581 if ( ! res . ok ) throw new Error ( "Failed to generate meaning" ) ;
8682 const data = await res . json ( ) ;
@@ -92,6 +88,31 @@ export default function AddWord() {
9288 }
9389 } ;
9490
91+ const fetchTrigger = async ( ) => {
92+ try {
93+ setTriggerLoading ( true ) ;
94+ const res = await fetch ( "/api/generate-trigger" , {
95+ method : "POST" ,
96+ headers : {
97+ "Content-Type" : "application/json" ,
98+ } ,
99+ body : JSON . stringify ( { word : AIDebouncedWord } ) ,
100+ } ) ;
101+
102+ if ( res . status === 429 )
103+ throw new Error (
104+ "Rate limit exceeded. Please wait a moment before trying again." ,
105+ ) ;
106+ if ( ! res . ok ) throw new Error ( "Failed to generate trigger." ) ;
107+ const data = await res . json ( ) ;
108+ setTrigger ( data . trigger ) ;
109+ } catch ( error ) {
110+ notifyError ( ( error as Error ) . message ) ;
111+ } finally {
112+ setTriggerLoading ( false ) ;
113+ }
114+ } ;
115+
95116 const fetchExamples = async ( ) => {
96117 try {
97118 setExamplesLoading ( true ) ;
@@ -105,7 +126,7 @@ export default function AddWord() {
105126
106127 if ( res . status === 429 )
107128 throw new Error (
108- "Rate limit exceeded. Please wait a moment before trying again."
129+ "Rate limit exceeded. Please wait a moment before trying again." ,
109130 ) ;
110131 if ( ! res . ok ) throw new Error ( "Failed to generate examples" ) ;
111132 const data = await res . json ( ) ;
@@ -117,10 +138,48 @@ export default function AddWord() {
117138 }
118139 } ;
119140
141+ const fetchDerviation = async ( ) => {
142+ try {
143+ const res = await fetch (
144+ `https://api.datamuse.com/words?sp=${ encodeURIComponent ( word . slice ( 0 , 6 ) ) } *&md=p&max=100` ,
145+ ) ;
146+ if ( ! res . ok ) throw new Error ( "Failed to fetch derivations" ) ;
147+ const data = await res . json ( ) ;
148+
149+ const base = word . toLowerCase ( ) ;
150+ const baseRoot = base . slice ( 0 , 5 ) ;
151+
152+ // keep only clean derivations
153+ const derivations = data . filter ( ( w ) => {
154+ const wLower = w . word . toLowerCase ( ) ;
155+
156+ const valid =
157+ wLower . startsWith ( baseRoot ) && // same root
158+ ! wLower . includes ( " " ) && // ignore phrases
159+ wLower !== base && // not the same word
160+ ! / [ ^ a - z A - Z ] / . test ( wLower ) && // remove words with weird chars
161+ ( wLower . includes ( "ly" ) ||
162+ wLower . includes ( "ness" ) ||
163+ wLower . includes ( "est" ) ||
164+ wLower . includes ( "ate" ) ||
165+ wLower . includes ( "acy" ) ||
166+ wLower . includes ( "ous" ) ) ; // common derivational suffixes
167+
168+ return valid ;
169+ } ) ;
170+
171+ console . log ( derivations ) ;
172+ } catch ( error ) {
173+ notifyError ( ( error as Error ) . message ) ;
174+ }
175+ } ;
176+
120177 useEffect ( ( ) => {
121178 if ( ! AIDebouncedWord || meaningExists ) return ;
122179
180+ fetchDerviation ( ) ;
123181 fetchMeaning ( ) ;
182+ fetchTrigger ( ) ;
124183 fetchExamples ( ) ;
125184 } , [ AIDebouncedWord ] ) ;
126185
@@ -137,7 +196,7 @@ export default function AddWord() {
137196
138197 const getWord = useQuery (
139198 api . words . getWordByName ,
140- debouncedWord ? { word : debouncedWord } : "skip"
199+ debouncedWord ? { word : debouncedWord } : "skip" ,
141200 ) ;
142201
143202 const isNumeric = ( n : string ) => {
@@ -153,14 +212,15 @@ export default function AddWord() {
153212 if ( alreadyExists ) return notifyError ( "Word already exists" ) ;
154213 if ( meaning . trim ( ) . length === 0 || examples . length === 0 )
155214 return notifyError (
156- "Sit Tight while AI generates the meaning and examples!"
215+ "Sit Tight while AI generates the meaning and examples!" ,
157216 ) ;
158217 const capitalizedWord = word . charAt ( 0 ) . toUpperCase ( ) + word . slice ( 1 ) ;
159218 try {
160219 void createWord ( {
161220 owner : user ?. id || "anonymous" ,
162221 word : capitalizedWord ,
163222 meaning : meaning ,
223+ trigger : trigger ,
164224 examples : examples ,
165225 } ) ;
166226 void updateCount ( ) ;
@@ -218,6 +278,7 @@ export default function AddWord() {
218278 const handleRegenerateMeaning = async ( type : string ) => {
219279 try {
220280 if ( type === "Meaning" ) await fetchMeaning ( ) ;
281+ else if ( type === "Trigger" ) await fetchTrigger ( ) ;
221282 else await fetchExamples ( ) ;
222283 } catch ( err ) {
223284 notifyError ( ( err as Error ) . message ) ;
@@ -283,6 +344,36 @@ export default function AddWord() {
283344 < p > Generating definition...</ p >
284345 </ div >
285346 ) }
347+
348+ { trigger . length > 0 && (
349+ < section >
350+ < div className = "mt-5 flex items-center justify-between" >
351+ < label className = "block font-medium text-sm text-gray-700 dark:text-gray-400" >
352+ Trigger (generated by AI)
353+ </ label >
354+ < Button
355+ type = "button"
356+ className = "bg-opacity-0 rounded-md cursor-pointer hover:bg-white transition duration-350 text-gray-500 hover:text-gray-700"
357+ onClick = { ( ) => handleRegenerateMeaning ( "Trigger" ) }
358+ >
359+ < RefreshCcwIcon
360+ size = { 18 }
361+ className = { meaningLoading ? "animate-spin" : "" }
362+ />
363+ </ Button >
364+ </ div >
365+ < div className = "border-dashed border-2 rounded-md px-3 py-2 bg-white dark:bg-black text-sm flex-1" >
366+ < span > { trigger } </ span >
367+ </ div >
368+ </ section >
369+ ) }
370+ { triggerLoading && (
371+ < div className = "flex items-center mt-5 text-gray-500" >
372+ < LoaderPinwheel className = "animate-spin mr-2" color = "#6a7282" />
373+ < p > Generating trigger...</ p >
374+ </ div >
375+ ) }
376+
286377 { examples . length > 0 && (
287378 < section >
288379 < div className = "flex items-center justify-between mt-5" >
@@ -342,6 +433,7 @@ export default function AddWord() {
342433 < WordCard
343434 word = { getWord ?. word || "" }
344435 meaning = { getWord ?. meaning || "" }
436+ trigger = { getWord ?. trigger || "" }
345437 examples = { getWord ?. examples || [ ] }
346438 isOwner = { isOwner ( getWord ?. owner || "" ) }
347439 />
0 commit comments