parent
44d3724880
commit
58f12af2d7
6 changed files with 187 additions and 37 deletions
@ -0,0 +1,53 @@ |
|||||||
|
svg text tspan { |
||||||
|
-webkit-font-smoothing: antialiased; |
||||||
|
-moz-osx-font-smoothing: grayscale; |
||||||
|
text-rendering: optimizeSpeed; |
||||||
|
} |
||||||
|
|
||||||
|
svg > g[data-type='topic'], |
||||||
|
svg > g[data-type='subtopic'], |
||||||
|
svg > g > g[data-type='link-item'], |
||||||
|
svg > g[data-type='button'] { |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
svg > g[data-type='topic']:hover > rect { |
||||||
|
fill: #d6d700; |
||||||
|
} |
||||||
|
|
||||||
|
svg > g[data-type='subtopic']:hover > rect { |
||||||
|
fill: #f3c950; |
||||||
|
} |
||||||
|
svg > g[data-type='button']:hover { |
||||||
|
opacity: 0.8; |
||||||
|
} |
||||||
|
|
||||||
|
svg .done rect { |
||||||
|
fill: #cbcbcb !important; |
||||||
|
} |
||||||
|
|
||||||
|
svg .done text, |
||||||
|
svg .skipped text { |
||||||
|
text-decoration: line-through; |
||||||
|
} |
||||||
|
|
||||||
|
svg > g[data-type='topic'].learning > rect + text, |
||||||
|
svg > g[data-type='topic'].done > rect + text { |
||||||
|
fill: black; |
||||||
|
} |
||||||
|
|
||||||
|
svg > g[data-type='subtipic'].done > rect + text, |
||||||
|
svg > g[data-type='subtipic'].learning > rect + text { |
||||||
|
fill: #cbcbcb; |
||||||
|
} |
||||||
|
|
||||||
|
svg .learning rect { |
||||||
|
fill: #dad1fd !important; |
||||||
|
} |
||||||
|
svg .learning text { |
||||||
|
text-decoration: underline; |
||||||
|
} |
||||||
|
|
||||||
|
svg .skipped rect { |
||||||
|
fill: #496b69 !important; |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
import { useRef, useState, type FormEvent } from 'react'; |
||||||
|
import './GenerateRoadmap.css'; |
||||||
|
import { httpPost } from '../../lib/http'; |
||||||
|
import { useToast } from '../../hooks/use-toast'; |
||||||
|
import { generateRoadmapFromJSON } from '../../../editor/utils/roadmap-generator'; |
||||||
|
import { renderFlowJSON } from '../../../editor/renderer/renderer'; |
||||||
|
import { replaceChildren } from '../../lib/dom'; |
||||||
|
|
||||||
|
export function GenerateRoadmap() { |
||||||
|
const roadmapContainerRef = useRef<HTMLDivElement>(null); |
||||||
|
|
||||||
|
const toast = useToast(); |
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false); |
||||||
|
const [roadmapName, setRoadmapName] = useState(''); |
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { |
||||||
|
e.preventDefault(); |
||||||
|
setIsLoading(true); |
||||||
|
|
||||||
|
const { response, error } = await httpPost( |
||||||
|
`${import.meta.env.PUBLIC_API_URL}/v1-generate-roadmap`, |
||||||
|
{ |
||||||
|
title: roadmapName, |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
if (error || !response) { |
||||||
|
setIsLoading(false); |
||||||
|
toast.error(error?.message || 'Something went wrong'); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const { nodes, edges } = generateRoadmapFromJSON(response as any); |
||||||
|
const svg = await renderFlowJSON({ nodes, edges }); |
||||||
|
if (roadmapContainerRef?.current) { |
||||||
|
replaceChildren(roadmapContainerRef?.current, svg); |
||||||
|
} |
||||||
|
setIsLoading(false); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<section className="container grid grid-cols-[280px,1fr]"> |
||||||
|
<form |
||||||
|
className="h-full space-y-4 border-r px-4 py-10" |
||||||
|
onSubmit={handleSubmit} |
||||||
|
> |
||||||
|
<div className="flex w-full flex-col"> |
||||||
|
<label |
||||||
|
htmlFor="roadmap-name" |
||||||
|
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]' |
||||||
|
> |
||||||
|
Roadmap Title |
||||||
|
</label> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
name="roadmap-name" |
||||||
|
id="roadmap-name" |
||||||
|
className="mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1" |
||||||
|
required |
||||||
|
placeholder="Frontend" |
||||||
|
value={roadmapName} |
||||||
|
onInput={(e) => |
||||||
|
setRoadmapName((e.target as HTMLInputElement).value) |
||||||
|
} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<button |
||||||
|
type="submit" |
||||||
|
disabled={isLoading} |
||||||
|
className="inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-none focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400" |
||||||
|
> |
||||||
|
{isLoading ? 'Please wait...' : 'Generate'} |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
|
||||||
|
<div ref={roadmapContainerRef} className="px-4 py-10" /> |
||||||
|
</section> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
--- |
||||||
|
import { GenerateRoadmap } from '../../components/GenerateRoadmap/GenerateRoadmap'; |
||||||
|
import AccountLayout from '../../layouts/AccountLayout.astro'; |
||||||
|
--- |
||||||
|
|
||||||
|
<AccountLayout title='Roadmap AI'> |
||||||
|
<GenerateRoadmap client:load /> |
||||||
|
</AccountLayout> |
Loading…
Reference in new issue