11"use client"
22
3- import { useState } from "react"
3+ import { useState , useEffect } from "react"
44import {
55 DropdownMenu ,
66 DropdownMenuContent ,
77 DropdownMenuItem ,
88 DropdownMenuTrigger ,
9+ DropdownMenuSeparator ,
10+ DropdownMenuLabel ,
911} from "../../../components/ui/dropdown-menu"
12+ import { ArrowUpRight } from "lucide-react"
1013import { KeyboardIcon } from "../../../components/ui/icons"
1114import { DiscordIcon } from "../../../icons"
1215import { useSetAtom } from "jotai"
1316import { agentsSettingsDialogOpenAtom , agentsSettingsDialogActiveTabAtom } from "../../../lib/atoms"
1417
18+ interface ReleaseHighlight {
19+ version : string
20+ title : string
21+ }
22+
23+ function parseFirstHighlight ( content : string ) : string {
24+ const lines = content . split ( "\n" )
25+ let inFeatures = false
26+ for ( const line of lines ) {
27+ if ( / ^ # # # \s + F e a t u r e s / i. test ( line ) ) {
28+ inFeatures = true
29+ continue
30+ }
31+ if ( inFeatures && / ^ # # # ? \s + / . test ( line ) ) break
32+ if ( inFeatures ) {
33+ const bold = line . match ( / ^ [ - * ] \s + \* \* ( .+ ?) \* \* / )
34+ if ( bold ) return bold [ 1 ]
35+ const plain = line . match ( / ^ [ - * ] \s + ( .+ ) / )
36+ if ( plain ) return plain [ 1 ] . replace ( / \[ ( [ ^ \] ] + ) \] \( [ ^ ) ] + \) / g, "$1" ) . trim ( )
37+ }
38+ }
39+ return "Bug fixes & improvements"
40+ }
41+
1542interface AgentsHelpPopoverProps {
1643 children : React . ReactNode
1744 open ?: boolean
@@ -28,13 +55,48 @@ export function AgentsHelpPopover({
2855 const [ internalOpen , setInternalOpen ] = useState ( false )
2956 const setSettingsDialogOpen = useSetAtom ( agentsSettingsDialogOpenAtom )
3057 const setSettingsActiveTab = useSetAtom ( agentsSettingsDialogActiveTabAtom )
58+ const [ highlights , setHighlights ] = useState < ReleaseHighlight [ ] > ( [ ] )
3159
32- // Use controlled state if provided, otherwise use internal state
3360 const open = controlledOpen ?? internalOpen
3461 const setOpen = controlledOnOpenChange ?? setInternalOpen
3562
63+ useEffect ( ( ) => {
64+ let cancelled = false
65+ window . desktopApi
66+ . signedFetch ( "https://21st.dev/api/changelog/desktop?per_page=3" )
67+ . then ( ( result ) => {
68+ if ( cancelled ) return
69+ const data = result . data as {
70+ releases ?: Array < { version ?: string ; content ?: string } >
71+ }
72+ if ( data ?. releases ) {
73+ const items : ReleaseHighlight [ ] = [ ]
74+ for ( const release of data . releases ) {
75+ if ( release . version ) {
76+ items . push ( { version : release . version , title : parseFirstHighlight ( release . content || "" ) } )
77+ }
78+ }
79+ setHighlights ( items )
80+ }
81+ } )
82+ . catch ( ( ) => { } )
83+ return ( ) => {
84+ cancelled = true
85+ }
86+ } , [ ] )
87+
3688 const handleCommunityClick = ( ) => {
37- window . open ( "https://discord.gg/8ektTZGnj4" , "_blank" )
89+ window . desktopApi . openExternal ( "https://discord.gg/8ektTZGnj4" )
90+ }
91+
92+ const handleChangelogClick = ( ) => {
93+ window . desktopApi . openExternal ( "https://1code.dev/agents/changelog" )
94+ }
95+
96+ const handleReleaseClick = ( version : string ) => {
97+ window . desktopApi . openExternal (
98+ `https://1code.dev/agents/changelog#${ version } ` ,
99+ )
38100 }
39101
40102 const handleKeyboardShortcutsClick = ( ) => {
@@ -46,7 +108,7 @@ export function AgentsHelpPopover({
46108 return (
47109 < DropdownMenu open = { open } onOpenChange = { setOpen } >
48110 < DropdownMenuTrigger asChild > { children } </ DropdownMenuTrigger >
49- < DropdownMenuContent side = "top" align = "start" className = "w-36 " >
111+ < DropdownMenuContent side = "top" align = "start" className = "w-56 " >
50112 < DropdownMenuItem onClick = { handleCommunityClick } className = "gap-2" >
51113 < DiscordIcon className = "h-3.5 w-3.5 text-muted-foreground shrink-0" />
52114 < span className = "flex-1" > Discord</ span >
@@ -61,6 +123,40 @@ export function AgentsHelpPopover({
61123 < span className = "flex-1" > Shortcuts</ span >
62124 </ DropdownMenuItem >
63125 ) }
126+
127+ { highlights . length > 0 && (
128+ < >
129+ < DropdownMenuSeparator />
130+ < div className = "mx-1 px-1.5 pt-1.5 pb-0.5 text-xs text-muted-foreground" >
131+ What's new
132+ </ div >
133+ { highlights . map ( ( item , i ) => (
134+ < DropdownMenuItem
135+ key = { item . version }
136+ onClick = { ( ) => handleReleaseClick ( item . version ) }
137+ className = "gap-0 items-stretch min-h-0 px-2 py-0"
138+ >
139+ < div className = "flex flex-col items-center w-3 shrink-0" >
140+ { i === 0 ? < div className = "h-[11px]" /> : < div className = "w-px h-[11px] border-l border-dashed border-muted-foreground/30" /> }
141+ < div className = "w-1.5 h-1.5 rounded-full border border-muted-foreground/40 shrink-0" />
142+ < div className = "w-px flex-1 border-l border-dashed border-muted-foreground/30" />
143+ </ div >
144+ < span className = "text-xs text-muted-foreground leading-tight py-1.5 pl-2 line-clamp-2" >
145+ { item . title }
146+ </ span >
147+ </ DropdownMenuItem >
148+ ) ) }
149+ < DropdownMenuItem onClick = { handleChangelogClick } className = "gap-0 items-stretch min-h-0 px-2 py-0" >
150+ < div className = "flex flex-col items-center w-3 shrink-0" >
151+ < div className = "w-px h-[11px] border-l border-dashed border-muted-foreground/30" />
152+ < div className = "w-1.5 h-1.5 rounded-full bg-foreground shrink-0" />
153+ < div className = "w-px flex-1 border-l border-dashed border-muted-foreground/30" />
154+ </ div >
155+ < span className = "flex-1 text-xs pl-2 py-1.5" > Full changelog</ span >
156+ < ArrowUpRight className = "h-3 w-3 text-muted-foreground shrink-0 self-center" />
157+ </ DropdownMenuItem >
158+ </ >
159+ ) }
64160 </ DropdownMenuContent >
65161 </ DropdownMenu >
66162 )
0 commit comments