@@ -55,7 +55,7 @@ import {
5555 extractTitle ,
5656} from './services/sessionHistory'
5757import { generateSessionSummary } from './services/aiService'
58- import { Thread , ViewState , SourceMetadata , SessionMeta , Message } from './types'
58+ import { Thread , ViewState , SourceMetadata , SessionMeta , Message , ThreadType } from './types'
5959
6060const SESSION_ID_PATTERN = / ^ \/ ( [ a - z A - Z 0 - 9 _ - ] { 10 , } ) $ /
6161
@@ -163,8 +163,9 @@ const App: React.FC = () => {
163163 const el = wrapFirstOccurrenceWithThreadAnchor ( root , thread . context , thread . id )
164164 if ( ! el ) continue
165165
166- el . title = `Open thread: ${ thread . snippet } `
167- el . setAttribute ( 'aria-label' , `Open thread: ${ thread . snippet } ` )
166+ const labelPrefix = thread . type === 'comment' ? 'Open note' : 'Open thread'
167+ el . title = `${ labelPrefix } : ${ thread . snippet } `
168+ el . setAttribute ( 'aria-label' , `${ labelPrefix } : ${ thread . snippet } ` )
168169 setThreadAnchorActive ( el , thread . id === threadManager . activeThreadId )
169170 threadAnchorElsRef . current . set ( thread . id , el )
170171 }
@@ -187,6 +188,7 @@ const App: React.FC = () => {
187188 if ( sessionId && session . session && ! session . isLoading ) {
188189 const apiThreads : Thread [ ] = session . session . threads . map ( t => ( {
189190 id : t . id ,
191+ type : ( t . type as ThreadType ) || 'discussion' , // Default to discussion for backward compat
190192 context : t . context ,
191193 snippet : t . snippet ,
192194 createdAt : t . createdAt ,
@@ -334,20 +336,41 @@ const App: React.FC = () => {
334336 const handleExport = ( ) => {
335337 let exportText = markdownContent
336338
339+ // Separate threads by type
340+ const comments = threadManager . threads . filter ( t => t . type === 'comment' )
341+ const discussions = threadManager . threads . filter ( t => t . type !== 'comment' )
342+
337343 if ( quotes . length > 0 ) {
338344 exportText += '\n\n---\n\n# Saved Quotes\n\n'
339345 quotes . forEach ( q => {
340346 exportText += `> "${ q . text } "\n\n`
341347 } )
342348 }
343349
344- if ( threadManager . threads . length > 0 ) {
345- exportText += '\n\n---\n\n# Discussions\n\n'
346- threadManager . threads . forEach ( t => {
350+ // Export personal comments
351+ if ( comments . length > 0 ) {
352+ exportText += '\n\n---\n\n# Personal Notes\n\n'
353+ exportText += '_Notes and commentary on the article_\n\n'
354+ comments . forEach ( c => {
355+ exportText += `## ${ c . snippet } \n\n`
356+ if ( c . context !== 'Entire Document' ) {
357+ exportText += `> **Context**: ${ c . context } \n\n`
358+ }
359+ c . messages . forEach ( m => {
360+ exportText += `${ m . text } \n\n`
361+ } )
362+ exportText += '---\n\n'
363+ } )
364+ }
365+
366+ // Export AI discussions
367+ if ( discussions . length > 0 ) {
368+ exportText += '\n\n---\n\n# AI Discussions\n\n'
369+ discussions . forEach ( t => {
347370 exportText += `## Thread: ${ t . snippet } \n`
348371 exportText += `> **Context**: ${ t . context } \n\n`
349372 t . messages . forEach ( m => {
350- exportText += `**${ m . role === 'user' ? 'User ' : 'AI' } **: ${ m . text } \n\n`
373+ exportText += `**${ m . role === 'user' ? 'You ' : 'AI' } **: ${ m . text } \n\n`
351374 } )
352375 exportText += '---\n\n'
353376 } )
@@ -490,24 +513,31 @@ const App: React.FC = () => {
490513 } )
491514 }
492515
493- const createThread = async ( action : 'discuss' | 'summarize' ) => {
516+ const createThread = async ( action : 'discuss' | 'summarize' | 'comment' ) => {
494517 if ( ! selection ) return
495518
496519 const newThreadId = Date . now ( ) . toString ( )
497520 const snippet =
498521 selection . text . length > 30 ? selection . text . substring ( 0 , 30 ) + '...' : selection . text
499522
523+ // Determine thread type based on action
524+ const threadType : ThreadType = action === 'comment' ? 'comment' : 'discussion'
525+
500526 const anchorEl = contentRef . current
501527 ? wrapCurrentSelectionWithThreadAnchor ( contentRef . current , newThreadId )
502528 : null
503529 if ( anchorEl ) {
504- anchorEl . title = `Open thread: ${ snippet } `
505- anchorEl . setAttribute ( 'aria-label' , `Open thread: ${ snippet } ` )
530+ anchorEl . title = action === 'comment' ? `Open note: ${ snippet } ` : `Open thread: ${ snippet } `
531+ anchorEl . setAttribute (
532+ 'aria-label' ,
533+ action === 'comment' ? `Open note: ${ snippet } ` : `Open thread: ${ snippet } `
534+ )
506535 threadAnchorElsRef . current . set ( newThreadId , anchorEl )
507536 }
508537
509538 const newThread : Thread = {
510539 id : newThreadId ,
540+ type : threadType ,
511541 context : selection . text ,
512542 messages : [ ] ,
513543 createdAt : Date . now ( ) ,
@@ -522,7 +552,7 @@ const App: React.FC = () => {
522552 // Save to API
523553 let apiThreadId = newThreadId
524554 if ( sessionId ) {
525- const savedThreadId = await session . addThread ( selection . text , snippet )
555+ const savedThreadId = await session . addThread ( selection . text , snippet , threadType )
526556 if ( savedThreadId && savedThreadId !== newThreadId ) {
527557 threadManager . updateThreadId ( newThreadId , savedThreadId )
528558 apiThreadId = savedThreadId
@@ -536,6 +566,11 @@ const App: React.FC = () => {
536566 }
537567 }
538568
569+ // For comments, we're done - no AI involvement
570+ if ( action === 'comment' ) {
571+ return
572+ }
573+
539574 if ( action === 'discuss' ) {
540575 return
541576 }
@@ -602,6 +637,7 @@ const App: React.FC = () => {
602637 }
603638 const newThread : Thread = {
604639 id : newThreadId ,
640+ type : 'discussion' , // General threads are always AI discussions
605641 context : 'Entire Document' ,
606642 messages : [ initialUserMsg ] ,
607643 createdAt : Date . now ( ) ,
@@ -642,6 +678,7 @@ const App: React.FC = () => {
642678 const handleSendMessage = async ( text : string ) => {
643679 if ( ! threadManager . activeThreadId || ! threadManager . activeThread ) return
644680
681+ const thread = threadManager . activeThread
645682 const userMessageId = generateId ( )
646683 const userMessage : Message = {
647684 id : userMessageId ,
@@ -660,11 +697,16 @@ const App: React.FC = () => {
660697 }
661698 }
662699
700+ // Skip AI for comment threads - they're personal notes without AI involvement
701+ if ( thread . type === 'comment' ) {
702+ return
703+ }
704+
663705 await sendRequest ( {
664706 threadId : threadManager . activeThreadId ,
665- context : threadManager . activeThread . context ,
707+ context : thread . context ,
666708 markdownContent,
667- messages : [ ...threadManager . activeThread . messages , userMessage ] ,
709+ messages : [ ...thread . messages , userMessage ] ,
668710 userMessage : text ,
669711 mode : 'discuss' ,
670712 } )
@@ -686,14 +728,20 @@ const App: React.FC = () => {
686728 ]
687729
688730 threadManager . updateMessageToThread ( threadId , messageId , newText )
689- threadManager . truncateThreadAfter ( threadId , messageId )
731+ const shouldTruncate = thread . type !== 'comment'
732+ if ( shouldTruncate ) {
733+ threadManager . truncateThreadAfter ( threadId , messageId )
734+ }
690735
691736 if ( sessionId && session . isOwner ) {
692737 await session . updateMessage ( threadId , messageId , newText )
693- await session . truncateThread ( threadId , messageId )
738+ if ( shouldTruncate ) {
739+ await session . truncateThread ( threadId , messageId )
740+ }
694741 }
695742
696- if ( isUserMessage ) {
743+ // Only call AI for discussion threads, not comments
744+ if ( isUserMessage && thread . type !== 'comment' ) {
697745 await sendRequest ( {
698746 threadId,
699747 context : thread . context ,
@@ -720,6 +768,10 @@ const App: React.FC = () => {
720768 if ( ! threadManager . activeThreadId || ! threadManager . activeThread ) return
721769
722770 const currentThread = threadManager . activeThread
771+
772+ // No retry for comment threads - they have no AI responses
773+ if ( currentThread . type === 'comment' ) return
774+
723775 if ( currentThread . messages . length < 2 ) return
724776
725777 const messages = currentThread . messages
0 commit comments