1- import { For , createEffect , createMemo , on , onCleanup , onMount , Show , Index , type JSX , createSignal } from "solid-js"
1+ import { For , createEffect , createMemo , on , onCleanup , onMount , Show , type JSX , createSignal } from "solid-js"
22import { createStore , produce } from "solid-js/store"
33import { useNavigate } from "@solidjs/router"
44import { useMutation } from "@tanstack/solid-query"
55import { Button } from "@opencode-ai/ui/button"
6- import { FileIcon } from "@opencode-ai/ui/file-icon"
76import { Icon } from "@opencode-ai/ui/icon"
87import { IconButton } from "@opencode-ai/ui/icon-button"
98import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
109import { Dialog } from "@opencode-ai/ui/dialog"
1110import { Spinner } from "@opencode-ai/ui/spinner"
1211import { SessionTurn } from "@opencode-ai/ui/session-turn"
1312import { ScrollView } from "@opencode-ai/ui/scroll-view"
14- import type { AssistantMessage , Message as MessageType , Part , TextPart , UserMessage } from "@opencode-ai/sdk/v2"
13+ import type { AssistantMessage , Message as MessageType , Part , UserMessage } from "@opencode-ai/sdk/v2"
1514import { showToast } from "@opencode-ai/ui/toast"
1615import { Binary } from "@opencode-ai/util/binary"
17- import { getFilename } from "@opencode-ai/util/path"
1816import { collectTimelineScrollMetrics } from "@/pages/session/session-timeline-scroll-anchors"
1917import {
2018 type TimelineScrollControllerResult ,
@@ -29,6 +27,11 @@ import {
2927 shouldMarkTimelineBoundaryGesture ,
3028} from "@/pages/session/session-timeline-scroll-intents"
3129import { createTimelineStaging } from "@/pages/session/session-timeline-staging"
30+ import {
31+ areMessageCommentsEqual ,
32+ extractMessageComments ,
33+ SessionMessageComments ,
34+ } from "@/pages/session/session-message-comments"
3235import { taskDescription } from "@/pages/session/task-description"
3336import { buildTurnMessagesByUserID , emptyAssistantMessages } from "@/pages/session/session-messages"
3437import {
@@ -51,19 +54,9 @@ import { useShellSurface } from "@/context/shell-surface"
5154import { useSync } from "@/context/sync"
5255import { messageAgentColor } from "@/utils/agent"
5356import { sessionTitle } from "@/utils/session-title"
54- import { parseCommentNote , readCommentMetadata } from "@/utils/comment-note"
5557import { makeTimer } from "@solid-primitives/timer"
5658import { webSearchRecoveryToast } from "./websearch-toasts"
5759
58- type MessageComment = {
59- path : string
60- comment : string
61- selection ?: {
62- startLine : number
63- endLine : number
64- }
65- }
66-
6760function isWebSearchToolPart ( part : Part ) : part is Extract < Part , { type : "tool" } > {
6861 return part . type === "tool" && part . tool === "websearch"
6962}
@@ -103,25 +96,6 @@ type TurnChangeDisplay = {
10396 } >
10497}
10598
106- const messageComments = ( parts : Part [ ] ) : MessageComment [ ] =>
107- parts . flatMap ( ( part ) => {
108- if ( part . type !== "text" || ! ( part as TextPart ) . synthetic ) return [ ]
109- const next = readCommentMetadata ( part . metadata ) ?? parseCommentNote ( part . text )
110- if ( ! next ) return [ ]
111- return [
112- {
113- path : next . path ,
114- comment : next . comment ,
115- selection : next . selection
116- ? {
117- startLine : next . selection . startLine ,
118- endLine : next . selection . endLine ,
119- }
120- : undefined ,
121- } ,
122- ]
123- } )
124-
12599export { taskDescription }
126100
127101export function MessageTimeline ( props : {
@@ -929,18 +903,9 @@ export function MessageTimeline(props: {
929903 { ( messageID , index ) => {
930904 const userMessage = createMemo ( ( ) => props . renderedUserMessages [ index ( ) ] )
931905 const active = createMemo ( ( ) => activeMessageID ( ) === messageID )
932- const comments = createMemo ( ( ) => messageComments ( sync . data . part [ messageID ] ?? [ ] ) , [ ] , {
933- equals : ( a , b ) =>
934- a . length === b . length &&
935- a . every (
936- ( c , i ) =>
937- c . path === b [ i ] . path &&
938- c . comment === b [ i ] . comment &&
939- c . selection ?. startLine === b [ i ] . selection ?. startLine &&
940- c . selection ?. endLine === b [ i ] . selection ?. endLine ,
941- ) ,
906+ const comments = createMemo ( ( ) => extractMessageComments ( sync . data . part [ messageID ] ?? [ ] ) , [ ] , {
907+ equals : areMessageCommentsEqual ,
942908 } )
943- const commentCount = createMemo ( ( ) => comments ( ) . length )
944909 return (
945910 < div
946911 id = { props . anchor ( messageID ) }
@@ -954,46 +919,7 @@ export function MessageTimeline(props: {
954919 "contain-intrinsic-size" : active ( ) ? undefined : "auto 500px" ,
955920 } }
956921 >
957- < Show when = { commentCount ( ) > 0 } >
958- < div class = "w-full px-4 md:px-5 pb-2" >
959- < div class = "ml-auto max-w-[82%] overflow-x-auto no-scrollbar" >
960- < div class = "flex w-max min-w-full justify-end gap-2" >
961- < Index each = { comments ( ) } >
962- { ( commentAccessor : ( ) => MessageComment ) => {
963- const comment = createMemo ( ( ) => commentAccessor ( ) )
964- return (
965- < Show when = { comment ( ) } >
966- { ( c ) => (
967- < div class = "shrink-0 max-w-[260px] rounded-[6px] border border-border-weak bg-bg-base px-2.5 py-2" >
968- < div class = "flex items-center gap-1.5 min-w-0 text-h3 text-fg-strong" >
969- < FileIcon
970- node = { { path : c ( ) . path , type : "file" } }
971- class = "size-3.5 shrink-0"
972- />
973- < span class = "truncate" > { getFilename ( c ( ) . path ) } </ span >
974- < Show when = { c ( ) . selection } >
975- { ( selection ) => (
976- < span class = "shrink-0 text-fg-weak" >
977- { selection ( ) . startLine === selection ( ) . endLine
978- ? `:${ selection ( ) . startLine } `
979- : `:${ selection ( ) . startLine } -${ selection ( ) . endLine } ` }
980- </ span >
981- ) }
982- </ Show >
983- </ div >
984- < div class = "pt-1 text-body text-fg-strong whitespace-pre-wrap break-words" >
985- { c ( ) . comment }
986- </ div >
987- </ div >
988- ) }
989- </ Show >
990- )
991- } }
992- </ Index >
993- </ div >
994- </ div >
995- </ div >
996- </ Show >
922+ < SessionMessageComments comments = { comments ( ) } />
997923 < SessionTurn
998924 sessionID = { sessionID ( ) ?? "" }
999925 messageID = { messageID }
0 commit comments