1- import { Send , Sparkles , Code , AtSign , Smile , GitPullRequest , FileText , Plus , Minus , MessageSquare , Bookmark , Share2 , MoreVertical , X , CheckCircle , Clock , AlertCircle , ExternalLink , GitMerge , Hash , Paperclip , FileUp , Image as ImageIcon , Link2 } from "lucide-react" ;
1+ import { Send , Sparkles , Code , AtSign , Smile , GitPullRequest , FileText , Plus , Minus , MessageSquare , Bookmark , Share2 , MoreVertical , X , CheckCircle , Clock , AlertCircle , ExternalLink , GitMerge , Hash , Paperclip , FileUp , Image as ImageIcon , Link2 , CircleDot , CircleCheck , CircleMinus } from "lucide-react" ;
22import { useEffect , useRef , useState , type ChangeEvent } from "react" ;
33import { createFileMessageAttachment , createLinkMessageAttachment , createLinkMessageAttachmentFromText , messageAttachmentGroups , messageAttachmentTypeLabels , type MessageAttachment , type MessageAttachmentType } from "./messageAttachments" ;
44import { TypingIndicator } from "./TypingIndicator" ;
55import { EmojiPicker } from "./EmojiPicker" ;
66import { MessageReactions , toggleMessageReaction , type MessageReaction } from "./MessageReactions" ;
77import { MessageAttachmentCard } from "./MessageAttachmentCard" ;
88
9+ export interface IssueLabel {
10+ name : string ;
11+ color : string ;
12+ }
13+
14+ export interface IssueHistoryEvent {
15+ id : string ;
16+ actor : string ;
17+ action : string ;
18+ time : string ;
19+ eventType : 'created' | 'assigned' | 'labeled' | 'commented' | 'status_changed' ;
20+ }
21+
922interface Message {
1023 id : number ;
1124 user : string ;
1225 text : string ;
1326 time : string ;
14- type ?: 'text' | 'code' | 'system' | 'pr' ;
27+ type ?: 'text' | 'code' | 'system' | 'pr' | 'issue' ;
1528 code ?: string ;
1629 language ?: string ;
1730 mentions ?: string [ ] ;
31+ // PR fields
1832 prNumber ?: number ;
1933 prStatus ?: 'open' | 'merged' | 'closed' | 'completed' ;
2034 prTitle ?: string ;
@@ -29,6 +43,17 @@ interface Message {
2943 aiRisk ?: 'Low' | 'Medium' | 'High' ;
3044 passed ?: number ;
3145 labels ?: string [ ] ;
46+ // Issue fields
47+ issueNumber ?: number ;
48+ issueTitle ?: string ;
49+ issueStatus ?: 'open' | 'closed' | 'in_progress' ;
50+ issueAuthor ?: string ;
51+ issueLabels ?: IssueLabel [ ] ;
52+ issuePriority ?: 'high' | 'medium' | 'low' ;
53+ issueType ?: string ;
54+ issueAssignees ?: string [ ] ;
55+ issueBody ?: string ;
56+ issueHistory ?: IssueHistoryEvent [ ] ;
3257 attachments ?: MessageAttachment [ ] ;
3358}
3459
@@ -39,6 +64,7 @@ interface ChatPanelProps {
3964 showAISummary ?: boolean ;
4065 onMergePR ?: ( messageId : number ) => void ;
4166 onReviewPR ?: ( prData : any ) => void ;
67+ onViewIssue ?: ( issueData : any ) => void ;
4268 onOpenThread ?: ( message : any ) => void ;
4369 isRepository ?: boolean ;
4470}
@@ -56,7 +82,19 @@ const shareChannels = [
5682 { id : "design" , label : "디자인" }
5783] ;
5884
59- export function ChatPanel ( { title, messages, onSendMessage, showAISummary = true , onMergePR, onReviewPR, onOpenThread, isRepository = false } : ChatPanelProps ) {
85+ const issueStatusConfig = {
86+ open : { label : '열림' , color : '#22C55E' , icon : CircleDot } ,
87+ in_progress : { label : '진행 중' , color : 'var(--neon-cyan)' , icon : Clock } ,
88+ closed : { label : '닫힘' , color : 'var(--muted)' , icon : CircleCheck } ,
89+ } ;
90+
91+ const issuePriorityConfig = {
92+ high : { label : 'High' , color : '#FF6B6B' } ,
93+ medium : { label : 'Medium' , color : '#F59E0B' } ,
94+ low : { label : 'Low' , color : '#22C55E' } ,
95+ } ;
96+
97+ export function ChatPanel ( { title, messages, onSendMessage, showAISummary = true , onMergePR, onReviewPR, onViewIssue, onOpenThread, isRepository = false } : ChatPanelProps ) {
6098 const [ message , setMessage ] = useState ( '' ) ;
6199 const [ showCodeBlock , setShowCodeBlock ] = useState ( false ) ;
62100 const [ activeTab , setActiveTab ] = useState < 'all' | 'pending' | 'completed' > ( 'all' ) ;
@@ -584,6 +622,181 @@ export function ChatPanel({ title, messages, onSendMessage, showAISummary = true
584622 </ div >
585623 ) }
586624 </ div >
625+ ) : msg . type === 'issue' ? (
626+ < div className = "relative" >
627+ < div
628+ role = "button"
629+ tabIndex = { 0 }
630+ onClick = { ( ) => onViewIssue ?.( msg ) }
631+ onKeyDown = { ( e ) => {
632+ if ( e . key === 'Enter' || e . key === ' ' ) {
633+ e . preventDefault ( ) ;
634+ onViewIssue ?.( msg ) ;
635+ }
636+ } }
637+ className = "rounded-xl overflow-hidden transition-all hover:translate-y-[-1px]"
638+ style = { {
639+ background : 'rgba(11, 22, 40, 0.85)' ,
640+ border : '1px solid rgba(34, 197, 94, 0.22)' ,
641+ boxShadow : '0 4px 16px rgba(0, 0, 0, 0.3)' ,
642+ cursor : 'pointer'
643+ } }
644+ >
645+ { /* Header */ }
646+ < div className = "px-4 py-2.5 flex items-center justify-between" style = { {
647+ background : 'rgba(5, 11, 20, 0.5)' ,
648+ borderBottom : '1px solid rgba(34, 197, 94, 0.14)'
649+ } } >
650+ < div className = "flex items-center gap-2" >
651+ < CircleDot size = { 14 } style = { { color : '#22C55E' } } />
652+ < span className = "font-mono tracking-tight" style = { {
653+ fontSize : '11px' ,
654+ fontWeight : 800 ,
655+ color : 'var(--muted)'
656+ } } >
657+ GitHub Issues
658+ </ span >
659+ </ div >
660+ < div className = "flex items-center gap-1.5 flex-wrap justify-end" >
661+ { msg . issueLabels ?. map ( ( label , idx ) => (
662+ < span key = { idx } className = "px-2 py-0.5 rounded-md tracking-tight" style = { {
663+ background : `${ label . color } 22` ,
664+ border : `1px solid ${ label . color } 66` ,
665+ fontSize : '10px' ,
666+ fontWeight : 800 ,
667+ color : label . color
668+ } } >
669+ { label . name }
670+ </ span >
671+ ) ) }
672+ </ div >
673+ </ div >
674+
675+ { /* Title */ }
676+ < div className = "px-4 py-3" style = { {
677+ borderBottom : '1px solid rgba(34, 197, 94, 0.10)'
678+ } } >
679+ < h4 className = "m-0 mb-2 tracking-tight" style = { {
680+ fontSize : '15px' ,
681+ fontWeight : 950 ,
682+ color : 'var(--white)' ,
683+ lineHeight : '1.4'
684+ } } >
685+ #{ msg . issueNumber } { msg . issueTitle }
686+ </ h4 >
687+ < div className = "flex items-center gap-2 flex-wrap" >
688+ { msg . issueStatus && ( ( ) => {
689+ const cfg = issueStatusConfig [ msg . issueStatus ] ;
690+ const Icon = cfg . icon ;
691+ return (
692+ < span className = "px-2 py-0.5 rounded-md flex items-center gap-1" style = { {
693+ background : `${ cfg . color } 22` ,
694+ border : `1px solid ${ cfg . color } 44` ,
695+ fontSize : '10px' ,
696+ fontWeight : 900 ,
697+ color : cfg . color
698+ } } >
699+ < Icon size = { 10 } />
700+ { cfg . label }
701+ </ span >
702+ ) ;
703+ } ) ( ) }
704+ < span className = "tracking-tight" style = { {
705+ fontSize : '11px' ,
706+ fontWeight : 700 ,
707+ color : 'var(--muted)'
708+ } } >
709+ { msg . time }
710+ </ span >
711+ < span className = "tracking-tight" style = { {
712+ fontSize : '11px' ,
713+ fontWeight : 700 ,
714+ color : 'var(--muted)'
715+ } } >
716+ 작성자 { msg . issueAuthor || msg . user }
717+ </ span >
718+ </ div >
719+ </ div >
720+
721+ { /* Actions */ }
722+ < div className = "px-4 py-3 flex items-center gap-2 flex-wrap" >
723+ { msg . issuePriority && (
724+ < span className = "px-2.5 py-1 rounded-md tracking-tight" style = { {
725+ background : `${ issuePriorityConfig [ msg . issuePriority ] . color } 22` ,
726+ border : `1px solid ${ issuePriorityConfig [ msg . issuePriority ] . color } 44` ,
727+ fontSize : '10px' ,
728+ fontWeight : 900 ,
729+ color : issuePriorityConfig [ msg . issuePriority ] . color
730+ } } >
731+ 우선순위: { issuePriorityConfig [ msg . issuePriority ] . label }
732+ </ span >
733+ ) }
734+ { msg . issueType && (
735+ < span className = "px-2.5 py-1 rounded-md tracking-tight" style = { {
736+ background : 'rgba(234, 247, 255, 0.07)' ,
737+ border : '1px solid rgba(234, 247, 255, 0.14)' ,
738+ fontSize : '10px' ,
739+ fontWeight : 900 ,
740+ color : 'var(--muted)'
741+ } } >
742+ { msg . issueType }
743+ </ span >
744+ ) }
745+ { msg . issueAssignees && msg . issueAssignees . length > 0 && (
746+ < span className = "tracking-tight" style = { {
747+ fontSize : '11px' ,
748+ fontWeight : 700 ,
749+ color : 'var(--muted)'
750+ } } >
751+ 담당자: { msg . issueAssignees . join ( ', ' ) }
752+ </ span >
753+ ) }
754+ < button
755+ onClick = { ( e ) => {
756+ e . stopPropagation ( ) ;
757+ onViewIssue ?.( msg ) ;
758+ } }
759+ className = "ml-auto px-3 py-1.5 rounded-md border-0 tracking-tight transition-all flex items-center gap-1.5"
760+ style = { {
761+ background : 'rgba(34, 197, 94, 0.12)' ,
762+ border : '1px solid rgba(34, 197, 94, 0.28)' ,
763+ color : '#22C55E' ,
764+ fontSize : '11px' ,
765+ fontWeight : 900 ,
766+ cursor : 'pointer'
767+ } }
768+ >
769+ 이슈 열기
770+ </ button >
771+ </ div >
772+ </ div >
773+ { hoveredMessageId === msg . id && (
774+ < div className = "absolute top-2 right-2 flex items-center gap-1 px-2 py-1 rounded-lg" style = { {
775+ background : 'rgba(11, 22, 40, 0.95)' ,
776+ border : '1px solid rgba(32, 227, 255, 0.3)' ,
777+ boxShadow : '0 4px 12px rgba(0, 0, 0, 0.5)'
778+ } } >
779+ < button
780+ onClick = { ( ) => onOpenThread ?.( msg ) }
781+ className = "w-7 h-7 rounded border-0 flex items-center justify-center transition-all hover:bg-[rgba(32,227,255,0.15)]"
782+ style = { { background : 'transparent' , color : 'var(--muted)' , cursor : 'pointer' } }
783+ title = "답글"
784+ >
785+ < MessageSquare size = { 14 } />
786+ </ button >
787+ < button className = "w-7 h-7 rounded border-0 flex items-center justify-center transition-all hover:bg-[rgba(32,227,255,0.15)]" style = { {
788+ background : 'transparent' , color : 'var(--muted)' , cursor : 'pointer'
789+ } } title = "북마크" >
790+ < Bookmark size = { 14 } />
791+ </ button >
792+ < button className = "w-7 h-7 rounded border-0 flex items-center justify-center transition-all hover:bg-[rgba(32,227,255,0.15)]" style = { {
793+ background : 'transparent' , color : 'var(--muted)' , cursor : 'pointer'
794+ } } title = "더보기" >
795+ < MoreVertical size = { 14 } />
796+ </ button >
797+ </ div >
798+ ) }
799+ </ div >
587800 ) : msg . type === 'code' && msg . code ? (
588801 < div className = "relative" >
589802 < div className = "rounded-xl overflow-hidden" style = { {
0 commit comments