11import { For , Index , Match , Show , Suspense , Switch , createEffect , createMemo , createSignal , lazy , onCleanup , untrack , type Accessor } from "solid-js"
2- import { ChevronsDownUp , ChevronsUpDown , ExternalLink , FoldVertical , ListStart , Trash } from "lucide-solid"
2+ import { ChevronsDownUp , ChevronsUpDown , ExternalLink , FoldVertical , ListStart , Trash , Volume2 } from "lucide-solid"
33import MessageItem from "./message-item"
44import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
55import type { ClientPart , MessageInfo } from "../types/message"
@@ -18,6 +18,7 @@ import { useSpeech } from "../lib/hooks/use-speech"
1818import SpeechActionButton from "./speech-action-button"
1919import { createFollowScroll } from "../lib/follow-scroll"
2020import type { SessionSearchMatch } from "../lib/session-search"
21+ import ActionOverflowMenu , { type ActionOverflowMenuItem } from "./action-overflow-menu"
2122
2223function DeleteUpToIcon ( ) {
2324 return (
@@ -486,6 +487,12 @@ function ToolCallItem(props: ToolCallItemProps) {
486487 navigateToTaskSession ( location )
487488 }
488489
490+ const goToTaskSession = ( ) => {
491+ const location = taskLocation ( )
492+ if ( ! location ) return
493+ navigateToTaskSession ( location )
494+ }
495+
489496 const handleDeleteMessage = async ( event : MouseEvent ) => {
490497 event . preventDefault ( )
491498 event . stopPropagation ( )
@@ -507,9 +514,7 @@ function ToolCallItem(props: ToolCallItemProps) {
507514 }
508515 }
509516
510- const handleDeleteUpTo = async ( event : MouseEvent ) => {
511- event . preventDefault ( )
512- event . stopPropagation ( )
517+ const deleteUpTo = async ( ) => {
513518 if ( ! props . showDeleteMessage ) return
514519 if ( ! props . onDeleteMessagesUpTo ) return
515520 if ( deletingUpTo ( ) ) return
@@ -522,11 +527,72 @@ function ToolCallItem(props: ToolCallItemProps) {
522527 }
523528 }
524529
530+ const handleDeleteUpTo = async ( event : MouseEvent ) => {
531+ event . preventDefault ( )
532+ event . stopPropagation ( )
533+ await deleteUpTo ( )
534+ }
535+
536+ const actionMenuItems = ( ) : ActionOverflowMenuItem [ ] => {
537+ const items : ActionOverflowMenuItem [ ] = [ ]
538+
539+ if ( taskSessionId ( ) ) {
540+ items . push ( {
541+ key : "go-to-session" ,
542+ label : t ( "messageBlock.tool.goToSession.label" ) ,
543+ icon : < ExternalLink class = "w-3.5 h-3.5" aria-hidden = "true" /> ,
544+ disabled : ! taskLocation ( ) ,
545+ onSelect : goToTaskSession ,
546+ } )
547+ }
548+
549+ if ( props . showDeleteMessage ) {
550+ items . push (
551+ {
552+ key : "delete-up-to" ,
553+ label : t ( "messageItem.actions.deleteMessagesUpTo" ) ,
554+ icon : < DeleteUpToIcon /> ,
555+ disabled : ! props . onDeleteMessagesUpTo || deletingUpTo ( ) ,
556+ destructive : true ,
557+ onMouseEnter : ( ) => props . onDeleteHoverChange ?.( { kind : "deleteUpTo" , messageId : props . messageId } ) ,
558+ onMouseLeave : ( ) => props . onDeleteHoverChange ?.( { kind : "none" } ) ,
559+ onSelect : deleteUpTo ,
560+ } ,
561+ {
562+ key : "delete-message" ,
563+ label : deletingMessage ( ) ? t ( "messageItem.actions.deletingMessage" ) : t ( "messageItem.actions.deleteMessage" ) ,
564+ icon : < Trash class = "w-3.5 h-3.5" aria-hidden = "true" /> ,
565+ disabled : deletingMessage ( ) ,
566+ destructive : true ,
567+ onMouseEnter : ( ) => props . onDeleteHoverChange ?.( { kind : "message" , messageId : props . messageId } ) ,
568+ onMouseLeave : ( ) => props . onDeleteHoverChange ?.( { kind : "none" } ) ,
569+ onSelect : async ( ) => {
570+ if ( deletingMessage ( ) ) return
571+ setDeletingMessage ( true )
572+ try {
573+ await deleteMessage ( props . instanceId , props . sessionId , props . messageId )
574+ } catch ( error ) {
575+ showAlertDialog ( t ( "messageItem.actions.deleteMessageFailedMessage" ) , {
576+ title : t ( "messageItem.actions.deleteMessageFailedTitle" ) ,
577+ detail : error instanceof Error ? error . message : String ( error ) ,
578+ variant : "error" ,
579+ } )
580+ } finally {
581+ setDeletingMessage ( false )
582+ }
583+ } ,
584+ } ,
585+ )
586+ }
587+
588+ return items
589+ }
590+
525591 return (
526592 < Show when = { toolPart ( ) } >
527593 { ( resolvedToolPart ) => (
528594 < div class = "delete-hover-scope" data-delete-part-hover = { isDeleteOverlayActive ( ) ? "true" : undefined } >
529- < div class = "tool-call-header-label" >
595+ < div class = "tool-call-header-label" data-action-overflow = { actionMenuItems ( ) . length > 1 ? "true" : undefined } >
530596 < div class = "tool-call-header-meta" >
531597 < Show when = { props . showDeleteMessage } >
532598 < input
@@ -551,7 +617,7 @@ function ToolCallItem(props: ToolCallItemProps) {
551617 < span class = "tool-name" > { toolName ( ) || t ( "messageBlock.tool.unknown" ) } </ span >
552618 </ div >
553619
554- < div class = "flex items-center gap-0" >
620+ < div class = "tool-call-header-actions flex items-center gap-0" >
555621 < Show when = { taskSessionId ( ) } >
556622 < button
557623 class = "tool-call-header-button"
@@ -593,6 +659,12 @@ function ToolCallItem(props: ToolCallItemProps) {
593659 </ button >
594660 </ Show >
595661 </ div >
662+ < ActionOverflowMenu
663+ items = { actionMenuItems ( ) }
664+ label = { t ( "messageItem.actions.more" ) }
665+ triggerClass = "tool-call-header-button"
666+ minItems = { 2 }
667+ />
596668 </ div >
597669
598670 < Suspense fallback = { < ToolCallFallback /> } >
@@ -1582,6 +1654,73 @@ function ReasoningCard(props: ReasoningCardProps) {
15821654
15831655 const canDeleteMessage = ( ) => Boolean ( props . showDeleteMessage ) && ! deletingMessage ( )
15841656
1657+ const deleteUpTo = async ( ) => {
1658+ if ( ! props . showDeleteMessage ) return
1659+ if ( ! props . onDeleteMessagesUpTo ) return
1660+ if ( deletingUpTo ( ) ) return
1661+
1662+ setDeletingUpTo ( true )
1663+ try {
1664+ await props . onDeleteMessagesUpTo ( props . messageId )
1665+ } finally {
1666+ setDeletingUpTo ( false )
1667+ }
1668+ }
1669+
1670+ const actionMenuItems = ( ) : ActionOverflowMenuItem [ ] => {
1671+ const items : ActionOverflowMenuItem [ ] = [ ]
1672+
1673+ if ( canSpeakReasoning ( ) ) {
1674+ items . push ( {
1675+ key : "speak" ,
1676+ label : speech . buttonTitle ( ) ,
1677+ icon : < Volume2 class = "w-3.5 h-3.5" aria-hidden = "true" /> ,
1678+ onSelect : ( ) => void speech . toggle ( ) ,
1679+ } )
1680+ }
1681+
1682+ if ( props . showDeleteMessage ) {
1683+ items . push (
1684+ {
1685+ key : "delete-up-to" ,
1686+ label : t ( "messageItem.actions.deleteMessagesUpTo" ) ,
1687+ icon : < DeleteUpToIcon /> ,
1688+ disabled : ! props . onDeleteMessagesUpTo || deletingUpTo ( ) ,
1689+ destructive : true ,
1690+ onMouseEnter : ( ) => props . onDeleteHoverChange ?.( { kind : "deleteUpTo" , messageId : props . messageId } ) ,
1691+ onMouseLeave : ( ) => props . onDeleteHoverChange ?.( { kind : "none" } ) ,
1692+ onSelect : deleteUpTo ,
1693+ } ,
1694+ {
1695+ key : "delete-message" ,
1696+ label : deletingMessage ( ) ? t ( "messageItem.actions.deletingMessage" ) : t ( "messageItem.actions.deleteMessage" ) ,
1697+ icon : < Trash class = "w-3.5 h-3.5" aria-hidden = "true" /> ,
1698+ disabled : ! canDeleteMessage ( ) ,
1699+ destructive : true ,
1700+ onMouseEnter : ( ) => props . onDeleteHoverChange ?.( { kind : "message" , messageId : props . messageId } ) ,
1701+ onMouseLeave : ( ) => props . onDeleteHoverChange ?.( { kind : "none" } ) ,
1702+ onSelect : async ( ) => {
1703+ if ( ! canDeleteMessage ( ) ) return
1704+ setDeletingMessage ( true )
1705+ try {
1706+ await deleteMessage ( props . instanceId , props . sessionId , props . messageId )
1707+ } catch ( error ) {
1708+ showAlertDialog ( t ( "messageItem.actions.deleteMessageFailedMessage" ) , {
1709+ title : t ( "messageItem.actions.deleteMessageFailedTitle" ) ,
1710+ detail : error instanceof Error ? error . message : String ( error ) ,
1711+ variant : "error" ,
1712+ } )
1713+ } finally {
1714+ setDeletingMessage ( false )
1715+ }
1716+ } ,
1717+ } ,
1718+ )
1719+ }
1720+
1721+ return items
1722+ }
1723+
15851724 const handleDeleteMessage = async ( event : MouseEvent ) => {
15861725 event . preventDefault ( )
15871726 event . stopPropagation ( )
@@ -1604,16 +1743,7 @@ function ReasoningCard(props: ReasoningCardProps) {
16041743 const handleDeleteUpTo = async ( event : MouseEvent ) => {
16051744 event . preventDefault ( )
16061745 event . stopPropagation ( )
1607- if ( ! props . showDeleteMessage ) return
1608- if ( ! props . onDeleteMessagesUpTo ) return
1609- if ( deletingUpTo ( ) ) return
1610-
1611- setDeletingUpTo ( true )
1612- try {
1613- await props . onDeleteMessagesUpTo ( props . messageId )
1614- } finally {
1615- setDeletingUpTo ( false )
1616- }
1746+ await deleteUpTo ( )
16171747 }
16181748
16191749 return (
@@ -1654,7 +1784,7 @@ function ReasoningCard(props: ReasoningCardProps) {
16541784 </ span >
16551785 </ button >
16561786
1657- < div class = "message-reasoning-actions" >
1787+ < div class = "message-reasoning-actions" data-action-overflow = { actionMenuItems ( ) . length > 1 ? "true" : undefined } >
16581788 < Show when = { canSpeakReasoning ( ) } >
16591789 < SpeechActionButton
16601790 class = "message-action-button"
@@ -1671,7 +1801,7 @@ function ReasoningCard(props: ReasoningCardProps) {
16711801
16721802 < button
16731803 type = "button"
1674- class = "message-action-button"
1804+ class = "message-action-button message-reasoning-primary-action "
16751805 onClick = { ( event ) => {
16761806 event . preventDefault ( )
16771807 event . stopPropagation ( )
@@ -1713,6 +1843,13 @@ function ReasoningCard(props: ReasoningCardProps) {
17131843 </ button >
17141844 </ Show >
17151845
1846+ < ActionOverflowMenu
1847+ items = { actionMenuItems ( ) }
1848+ label = { t ( "messageItem.actions.more" ) }
1849+ triggerClass = "message-action-button"
1850+ minItems = { 2 }
1851+ />
1852+
17161853 < span class = "message-reasoning-time" > { timestamp ( ) } </ span >
17171854 </ div >
17181855 </ div >
0 commit comments