@@ -75,6 +75,7 @@ struct PanelView: View {
7575 // Find-in-results state
7676 @State private var showResultsFindBar : Bool = false
7777 @State private var resultsFindQuery : String = " "
78+ @State private var resultsFindQueryDebounced : String = " "
7879 @State private var resultsFindMatches : [ UUID ] = [ ]
7980 @State private var resultsFindIndex : Int = 0
8081 @State private var resultsHighlightIDs : Set < UUID > = [ ]
@@ -357,14 +358,16 @@ struct PanelView: View {
357358 showResultsFindBar. toggle ( )
358359 if showResultsFindBar {
359360 prevTranscriptViewMode = model. transcriptViewMode
360- model. transcriptViewMode = 3
361- computeResultsFindMatches ( )
361+ // Prefer rendered transcript for search to enable in-text highlighting
362+ model. transcriptViewMode = 1
363+ // Do not run search until Enter is pressed
364+ resultsFindQueryDebounced = " "
362365 resultsFindFocused = true
363366 }
364367 }
365368
366- private func computeResultsFindMatches( ) {
367- let q = resultsFindQuery . trimmingCharacters ( in: . whitespacesAndNewlines)
369+ private func computeResultsFindMatches( for qIn : String ? = nil ) {
370+ let q = ( qIn ?? resultsFindQueryDebounced ) . trimmingCharacters ( in: . whitespacesAndNewlines)
368371 guard !q. isEmpty else { resultsFindMatches = [ ] ; resultsFindIndex = 0 ; resultsActiveID = nil ; return }
369372 var ids : [ UUID ] = [ ]
370373 for e in model. conversation where e. role == . assistant {
@@ -404,6 +407,10 @@ struct PanelView: View {
404407
405408 private func closeResultsFindBar( ) {
406409 showResultsFindBar = false
410+ // Restore prior transcript view mode after closing find, if any
411+ if let prev = prevTranscriptViewMode { model. transcriptViewMode = prev }
412+ prevTranscriptViewMode = nil
413+ resultsFindQueryDebounced = " "
407414 resultsFindQuery = " "
408415 resultsFindMatches = [ ]
409416 resultsFindIndex = 0
@@ -643,7 +650,11 @@ struct PanelView: View {
643650 if showResultsFindBar {
644651 HStack ( spacing: 8 ) {
645652 Image ( systemName: " magnifyingglass " )
646- TextField ( " Find in session " , text: $resultsFindQuery, onCommit: { computeResultsFindMatches ( ) ; jumpToResult ( at: 0 ) } )
653+ TextField ( " Find in session " , text: $resultsFindQuery, onCommit: {
654+ resultsFindQueryDebounced = resultsFindQuery
655+ computeResultsFindMatches ( for: resultsFindQueryDebounced)
656+ jumpToResult ( at: 0 )
657+ } )
647658 . textFieldStyle ( . roundedBorder)
648659 . frame ( width: 200 )
649660 . focused ( $resultsFindFocused)
@@ -712,7 +723,7 @@ struct PanelView: View {
712723 model: model,
713724 highlightIDs: resultsHighlightIDs,
714725 activeID: resultsActiveID,
715- searchQuery: ( showResultsFindBar && !resultsFindQuery . trimmingCharacters ( in: . whitespacesAndNewlines) . isEmpty) ? resultsFindQuery : nil ,
726+ searchQuery: ( showResultsFindBar && !resultsFindQueryDebounced . trimmingCharacters ( in: . whitespacesAndNewlines) . isEmpty) ? resultsFindQueryDebounced : nil ,
716727 onConfirmPlugin: { task in confirmPluginTask ( task) } ,
717728 onCancelPlugin: { task in
718729 model. updatePluginTask ( id: task. id, status: . canceled)
@@ -724,20 +735,39 @@ struct PanelView: View {
724735 }
725736 )
726737 } else if model. transcriptViewMode == 2 {
727- Text ( model. transcriptRawAssistantMarkdown ( ) )
728- . textSelection ( . enabled)
729- . font ( . system( size: model. uiMonoPointSize, design: . monospaced) )
730- . multilineTextAlignment ( . leading)
731- . fixedSize ( horizontal: false , vertical: true )
732- . padding ( . horizontal, 8 )
738+ let raw = model. transcriptRawAssistantMarkdown ( )
739+ if showResultsFindBar && !resultsFindQueryDebounced. trimmingCharacters ( in: . whitespacesAndNewlines) . isEmpty {
740+ Text ( PanelView . highlightedAll ( text: raw, query: resultsFindQueryDebounced) )
741+ . textSelection ( . enabled)
742+ . font ( . system( size: model. uiMonoPointSize, design: . monospaced) )
743+ . multilineTextAlignment ( . leading)
744+ . fixedSize ( horizontal: false , vertical: true )
745+ . padding ( . horizontal, 8 )
746+ } else {
747+ Text ( raw)
748+ . textSelection ( . enabled)
749+ . font ( . system( size: model. uiMonoPointSize, design: . monospaced) )
750+ . multilineTextAlignment ( . leading)
751+ . fixedSize ( horizontal: false , vertical: true )
752+ . padding ( . horizontal, 8 )
753+ }
733754 } else {
734- // Plain text transcript (user + assistant), single block for multi-line selection
735- Text ( model. transcriptPlainTextAll ( ) )
736- . textSelection ( . enabled)
737- . font ( . system( size: model. uiBodyPointSize) )
738- . multilineTextAlignment ( . leading)
739- . fixedSize ( horizontal: false , vertical: true )
740- . padding ( . horizontal, 8 )
755+ let plain = model. transcriptPlainTextAll ( )
756+ if showResultsFindBar && !resultsFindQueryDebounced. trimmingCharacters ( in: . whitespacesAndNewlines) . isEmpty {
757+ Text ( PanelView . highlightedAll ( text: plain, query: resultsFindQueryDebounced) )
758+ . textSelection ( . enabled)
759+ . font ( . system( size: model. uiBodyPointSize) )
760+ . multilineTextAlignment ( . leading)
761+ . fixedSize ( horizontal: false , vertical: true )
762+ . padding ( . horizontal, 8 )
763+ } else {
764+ Text ( plain)
765+ . textSelection ( . enabled)
766+ . font ( . system( size: model. uiBodyPointSize) )
767+ . multilineTextAlignment ( . leading)
768+ . fixedSize ( horizontal: false , vertical: true )
769+ . padding ( . horizontal, 8 )
770+ }
741771 }
742772 }
743773 . frame ( maxWidth: . infinity, alignment: . leading)
@@ -2598,7 +2628,7 @@ private extension PanelView {
25982628 markdown: item. assistant. text,
25992629 scale: model. uiTextScaleFactor,
26002630 onCopy: { model. showToast = . init( message: " Copied " ) } ,
2601- highlightQuery: nil ,
2631+ highlightQuery: searchQuery ,
26022632 currentLine: ( item. assistant. id == lastAssistantId ? model. ttsApproxLine : nil )
26032633 )
26042634 }
@@ -3242,7 +3272,7 @@ private extension PanelView {
32423272 } ( ) , systemImage: " keyboard " )
32433273 Label ( {
32443274 let s = KeyboardLayoutDisplay . combo ( cmd: true , shift: true , keyCode: UInt16 ( kVK_ANSI_Semicolon) )
3245- return " Speed 1x/1.5x/2x — " + s
3275+ return " Speed 1x/1.2x/1. 5x/2x — " + s
32463276 } ( ) , systemImage: " keyboard " )
32473277 Label ( " Stop TTS — ⌘⎋ " , systemImage: " keyboard " )
32483278 }
@@ -3750,7 +3780,7 @@ private struct TTSControlsView: View, Equatable {
37503780 }
37513781 } . buttonStyle ( . plain) . help ( tooltipsEnabled ? " Play/Pause TTS " : " " )
37523782 Button ( action: onForward15) { Image ( systemName: " goforward.15 " ) } . buttonStyle ( . plain) . help ( tooltipsEnabled ? " Forward 15s (⇧⌘.) " : " " )
3753- Button ( action: onToggleRate) { Text ( rateIndex == 0 ? " 1x " : ( rateIndex == 1 ? " 1.5x " : " 2x " ) ) } . buttonStyle ( . plain) . help ( tooltipsEnabled ? " Toggle speed (⇧⌘;) " : " " )
3783+ Button ( action: onToggleRate) { Text ( rateIndex == 0 ? " 1x " : ( rateIndex == 1 ? " 1.2x " : ( rateIndex == 2 ? " 1. 5x" : " 2x " ) ) ) } . buttonStyle ( . plain) . help ( tooltipsEnabled ? " Toggle speed (⇧⌘;) " : " " )
37543784 if progressActive {
37553785 HStack ( spacing: 4 ) {
37563786 ProgressView ( ) . controlSize ( . small) . scaleEffect ( 0.75 ) . frame ( width: 14 , height: 14 )
@@ -3783,7 +3813,12 @@ private struct MarkdownContainer: View, Equatable {
37833813 var onCopy : ( ) -> Void
37843814 var highlightQuery : String ? = nil
37853815 var currentLine : Int ? = nil
3786- static func == ( l: MarkdownContainer , r: MarkdownContainer ) -> Bool { l. markdown == r. markdown && l. scale == r. scale && l. currentLine == r. currentLine }
3816+ static func == ( l: MarkdownContainer , r: MarkdownContainer ) -> Bool {
3817+ l. markdown == r. markdown &&
3818+ l. scale == r. scale &&
3819+ l. currentLine == r. currentLine &&
3820+ l. highlightQuery == r. highlightQuery
3821+ }
37873822 var body : some View {
37883823 let hasQuery = ( highlightQuery? . isEmpty == false )
37893824 let sanitized = sanitizeMarkdown ( markdown)
@@ -3814,8 +3849,7 @@ private struct MarkdownContainer: View, Equatable {
38143849 if idx >= 0 && idx < lines. count {
38153850 let s = lines [ idx]
38163851 if !s. trimmingCharacters ( in: . whitespaces) . isEmpty {
3817- // Add a clear audible marker and bold for visibility
3818- lines [ idx] = " 🔊 ** " + s + " ** "
3852+ lines [ idx] = " 🔊 _ " + s + " _ "
38193853 base = lines. joined ( separator: " \n " )
38203854 }
38213855 }
@@ -4064,6 +4098,26 @@ extension PanelView {
40644098 }
40654099 return attr
40664100 }
4101+
4102+ static func highlightedAll( text: String , query: String ) -> AttributedString {
4103+ if query. isEmpty { return AttributedString ( text) }
4104+ var attr = AttributedString ( text)
4105+ let lower = text. lowercased ( )
4106+ let ql = query. lowercased ( )
4107+ var searchStart = lower. startIndex
4108+ while let r = lower. range ( of: ql, options: [ . caseInsensitive, . diacriticInsensitive] , range: searchStart..< lower. endIndex) {
4109+ let start = lower. distance ( from: lower. startIndex, to: r. lowerBound)
4110+ let length = lower. distance ( from: r. lowerBound, to: r. upperBound)
4111+ let cappedStart = max ( 0 , min ( start, text. count) )
4112+ let cappedEnd = max ( cappedStart, min ( start + length, text. count) )
4113+ let s = attr. index ( attr. startIndex, offsetByCharacters: cappedStart)
4114+ let e = attr. index ( attr. startIndex, offsetByCharacters: cappedEnd)
4115+ attr [ s..< e] . backgroundColor = . yellow. opacity ( 0.35 )
4116+ attr [ s..< e] . foregroundColor = . primary
4117+ searchStart = r. upperBound
4118+ }
4119+ return attr
4120+ }
40674121private var settingsPopover : some View {
40684122 VStack ( spacing: 0 ) {
40694123 WindowCaptureOptOut ( allowCapture: model. allowScreenCapture)
0 commit comments