@@ -1647,6 +1647,9 @@ function ToolsBlockView({
16471647 chatId ? state . modelOrderByChatId . get ( chatId ) : undefined ,
16481648 ) ;
16491649 const setModelOrder = useModelOrderStore ( ( state ) => state . setModelOrder ) ;
1650+ const setCurrentVisualOrder = useModelOrderStore (
1651+ ( state ) => state . setCurrentVisualOrder ,
1652+ ) ;
16501653
16511654 // Track which models finished streaming and in what order, so we can sort
16521655 // finished models to the left (first finished = leftmost slot) when no
@@ -1770,37 +1773,55 @@ function ToolsBlockView({
17701773 }
17711774 } , [ toolsBlock . chatMessages , minimizedModels , onMinimize ] ) ;
17721775
1773- const activeMessages = [ ...toolsBlock . chatMessages ]
1774- . filter ( ( m ) => ! minimizedModels . has ( m . model ) )
1775- . sort ( ( a , b ) => {
1776- // Respect explicit drag-and-drop ordering if the user has set one.
1777- if ( customOrder ) {
1778- const aIdx = customOrder . indexOf ( a . model ) ;
1779- const bIdx = customOrder . indexOf ( b . model ) ;
1780- if ( aIdx !== - 1 && bIdx !== - 1 ) return aIdx - bIdx ;
1781- if ( aIdx !== - 1 ) return - 1 ;
1782- if ( bIdx !== - 1 ) return 1 ;
1783- }
1784- // Default: finished (idle) models go left of still-streaming/loading
1785- // models, sorted by completion order (first finished = leftmost slot).
1786- const aIdle = a . state === "idle" ;
1787- const bIdle = b . state === "idle" ;
1788- if ( aIdle !== bIdle ) return aIdle ? - 1 : 1 ;
1789- if ( aIdle && bIdle ) {
1790- const aFinishIdx = finishedModelsOrder . indexOf ( a . model ) ;
1791- const bFinishIdx = finishedModelsOrder . indexOf ( b . model ) ;
1792- if ( aFinishIdx !== - 1 && bFinishIdx !== - 1 )
1793- return aFinishIdx - bFinishIdx ;
1794- }
1795- return getDisplayName ( a . model ) . localeCompare (
1796- getDisplayName ( b . model ) ,
1797- ) ;
1798- } ) ;
1776+ const activeMessages = useMemo (
1777+ ( ) =>
1778+ [ ...toolsBlock . chatMessages ]
1779+ . filter ( ( m ) => ! minimizedModels . has ( m . model ) )
1780+ . sort ( ( a , b ) => {
1781+ // Respect explicit drag-and-drop ordering if the user has set one.
1782+ if ( customOrder ) {
1783+ const aIdx = customOrder . indexOf ( a . model ) ;
1784+ const bIdx = customOrder . indexOf ( b . model ) ;
1785+ if ( aIdx !== - 1 && bIdx !== - 1 ) return aIdx - bIdx ;
1786+ if ( aIdx !== - 1 ) return - 1 ;
1787+ if ( bIdx !== - 1 ) return 1 ;
1788+ }
1789+ // Default: finished (idle) models go left of still-streaming/loading
1790+ // models, sorted by completion order (first finished = leftmost slot).
1791+ const aIdle = a . state === "idle" ;
1792+ const bIdle = b . state === "idle" ;
1793+ if ( aIdle !== bIdle ) return aIdle ? - 1 : 1 ;
1794+ if ( aIdle && bIdle ) {
1795+ const aFinishIdx = finishedModelsOrder . indexOf ( a . model ) ;
1796+ const bFinishIdx = finishedModelsOrder . indexOf ( b . model ) ;
1797+ if ( aFinishIdx !== - 1 && bFinishIdx !== - 1 )
1798+ return aFinishIdx - bFinishIdx ;
1799+ }
1800+ return getDisplayName ( a . model ) . localeCompare (
1801+ getDisplayName ( b . model ) ,
1802+ ) ;
1803+ } ) ,
1804+ [
1805+ toolsBlock . chatMessages ,
1806+ minimizedModels ,
1807+ customOrder ,
1808+ finishedModelsOrder ,
1809+ getDisplayName ,
1810+ ] ,
1811+ ) ;
17991812 const toolsItemOrder = useMemo (
18001813 ( ) => activeMessages . map ( ( m ) => m . model ) ,
18011814 [ activeMessages ] ,
18021815 ) ;
18031816
1817+ // Keep the store in sync with the current visual order so cmd+number
1818+ // keybindings can index into the same order the user sees on screen.
1819+ useEffect ( ( ) => {
1820+ if ( chatId ) {
1821+ setCurrentVisualOrder ( chatId , toolsItemOrder ) ;
1822+ }
1823+ } , [ chatId , toolsItemOrder , setCurrentVisualOrder ] ) ;
1824+
18041825 const handleAddModel = ( modelId : string ) => {
18051826 void ( async ( ) => {
18061827 try {
@@ -1889,6 +1910,7 @@ function ToolsBlockView({
18891910 key = { message . model }
18901911 layout
18911912 layoutId = { `tools-col-${ message . model } -${ messageSetId } ` }
1913+ data-tools-message-id = { message . id }
18921914 >
18931915 < SortableColumnItem
18941916 id = { message . model }
@@ -2414,6 +2436,9 @@ export default function MultiChat() {
24142436 const customCompareOrder = useModelOrderStore ( ( state ) =>
24152437 chatId ? state . modelOrderByChatId . get ( chatId ) : undefined ,
24162438 ) ;
2439+ const currentVisualOrder = useModelOrderStore ( ( state ) =>
2440+ chatId ? state . currentVisualOrderByChatId . get ( chatId ) : undefined ,
2441+ ) ;
24172442 const sortedCompareMessages = useMemo ( ( ) => {
24182443 if ( ! currentCompareBlock ) return [ ] ;
24192444 return [ ...currentCompareBlock . messages ] . sort ( ( a , b ) => {
@@ -2662,31 +2687,67 @@ export default function MultiChat() {
26622687 if ( e . metaKey && / ^ [ 1 - 8 ] $ / . test ( e . key ) ) {
26632688 // cmd + 1-8: select message at index
26642689 e . preventDefault ( ) ;
2665- if ( currentMessageSet ?. selectedBlockType !== "compare" ) {
2666- console . warn (
2667- "skipping cmd+1-8 because we're not in compare mode" ,
2668- ) ;
2669- return ;
2670- }
2671- // Get message at index (1-based)
26722690 const index = parseInt ( e . key ) - 1 ;
2673- if (
2674- ! currentCompareBlock ||
2675- sortedCompareMessages . length <= index
2676- ) {
2677- console . warn (
2678- `couldn't select message at ${ index } from cmd+${ index + 1 } ` ,
2691+ if ( currentMessageSet ?. selectedBlockType === "compare" ) {
2692+ if (
2693+ ! currentCompareBlock ||
2694+ sortedCompareMessages . length <= index
2695+ ) {
2696+ console . warn (
2697+ `couldn't select message at ${ index } from cmd+${ index + 1 } ` ,
2698+ ) ;
2699+ return ;
2700+ }
2701+ selectMessage . mutate ( {
2702+ chatId : chatId ! ,
2703+ messageSetId : currentMessageSet . id ,
2704+ messageId : sortedCompareMessages [ index ] . id ,
2705+ blockType : "compare" ,
2706+ } ) ;
2707+ } else if ( currentMessageSet ?. selectedBlockType === "tools" ) {
2708+ // Use the resolved visual order so cmd+1 selects the
2709+ // leftmost column regardless of streaming completion order.
2710+ const allMsgs = currentMessageSet . toolsBlock . chatMessages ;
2711+ const orderedMsgs = currentVisualOrder
2712+ ? currentVisualOrder
2713+ . map ( ( id ) => allMsgs . find ( ( m ) => m . model === id ) )
2714+ . filter ( ( m ) => m !== undefined )
2715+ : allMsgs ;
2716+ if ( orderedMsgs . length <= index ) {
2717+ console . warn (
2718+ `couldn't select message at ${ index } from cmd+${ index + 1 } ` ,
2719+ ) ;
2720+ return ;
2721+ }
2722+ selectMessage . mutate ( {
2723+ chatId : chatId ! ,
2724+ messageSetId : currentMessageSet . id ,
2725+ messageId : orderedMsgs [ index ] . id ,
2726+ blockType : "tools" ,
2727+ } ) ;
2728+ }
2729+ } else if (
2730+ ( e . metaKey || e . ctrlKey ) &&
2731+ e . shiftKey &&
2732+ e . key === " "
2733+ ) {
2734+ // cmd/ctrl + shift + space: scroll selected chat column into view
2735+ e . preventDefault ( ) ;
2736+ const selectedMsg =
2737+ currentMessageSet ?. toolsBlock ?. chatMessages . find (
2738+ ( m ) => m . selected ,
26792739 ) ;
2680- return ;
2740+ if ( selectedMsg ) {
2741+ document
2742+ . querySelector (
2743+ `[data-tools-message-id="${ selectedMsg . id } "]` ,
2744+ )
2745+ ?. scrollIntoView ( {
2746+ behavior : "smooth" ,
2747+ inline : "nearest" ,
2748+ block : "nearest" ,
2749+ } ) ;
26812750 }
2682- const message = sortedCompareMessages [ index ] ;
2683-
2684- selectMessage . mutate ( {
2685- chatId : chatId ! ,
2686- messageSetId : currentMessageSet . id ,
2687- messageId : message . id ,
2688- blockType : "compare" ,
2689- } ) ;
26902751 } else if ( e . metaKey && e . key === "s" && ! e . shiftKey ) {
26912752 e . preventDefault ( ) ;
26922753 if ( ! currentMessageSet ) return ;
@@ -2746,6 +2807,7 @@ export default function MultiChat() {
27462807 setVisionModeEnabled ,
27472808 // nextTools,
27482809 handleToggleVisionMode ,
2810+ currentVisualOrder ,
27492811 ] ) ;
27502812
27512813 const scrollToLatestMessageSet = useCallback ( ( ) => {
0 commit comments