1
1
import * as Sentry from '@sentry/react' ;
2
2
import { speak } from '@wordpress/a11y' ;
3
- import { Spinner } from '@wordpress/components' ;
4
3
import { __ } from '@wordpress/i18n' ;
5
- import { useEffect } from 'react' ;
6
4
import Markdown , { ExtraProps } from 'react-markdown' ;
7
5
import remarkGfm from 'remark-gfm' ;
8
- import stripAnsi from 'strip-ansi' ;
9
- import { useExecuteWPCLI } from '../hooks/use-execute-cli' ;
10
6
import { useSiteDetails } from '../hooks/use-site-details' ;
11
7
import { cx } from '../lib/cx' ;
12
8
import { getIpcApi } from '../lib/get-ipc-api' ;
13
- import Button from './button' ;
14
- import { CopyTextButton } from './copy-text-button' ;
15
- import { ExecuteIcon } from './icons/execute' ;
9
+ import createCodeComponent from './assistant-code-block' ;
16
10
17
- interface ChatMessageProps {
11
+ export interface ChatMessageProps {
18
12
children : React . ReactNode ;
19
13
isUser : boolean ;
20
14
id : string ;
@@ -38,26 +32,6 @@ interface ChatMessageProps {
38
32
isUnauthenticated ?: boolean ;
39
33
}
40
34
41
- interface InlineCLIProps {
42
- output ?: string ;
43
- status ?: 'success' | 'error' ;
44
- time ?: string | null ;
45
- }
46
-
47
- const InlineCLI = ( { output, status, time } : InlineCLIProps ) => (
48
- < div className = "p-3 bg-[#2D3337]" >
49
- < div className = "flex justify-between mb-2 font-sans" >
50
- < span className = { status === 'success' ? 'text-[#63CE68]' : 'text-[#E66D6C]' } >
51
- { status === 'success' ? __ ( 'Success' ) : __ ( 'Error' ) }
52
- </ span >
53
- < span className = "text-gray-400" > { time } </ span >
54
- </ div >
55
- < pre className = "text-white !bg-transparent !m-0 !px-0" >
56
- < code className = "!bg-transparent !mx-0 !px-0 !text-nowrap" > { output } </ code >
57
- </ pre >
58
- </ div >
59
- ) ;
60
-
61
35
export const ChatMessage = ( {
62
36
children,
63
37
id,
@@ -69,84 +43,6 @@ export const ChatMessage = ( {
69
43
updateMessage,
70
44
isUnauthenticated,
71
45
} : ChatMessageProps ) => {
72
- const CodeBlock = ( props : JSX . IntrinsicElements [ 'code' ] & ExtraProps ) => {
73
- const content = String ( props . children ) . trim ( ) ;
74
- const containsWPCommand = / \b w p \s / . test ( content ) ;
75
- const wpCommandCount = ( content . match ( / \b w p \s / g ) || [ ] ) . length ;
76
- const containsSingleWPCommand = wpCommandCount === 1 ;
77
- const containsAngleBrackets = / < .* > / . test ( content ) ;
78
-
79
- const {
80
- cliOutput,
81
- cliStatus,
82
- cliTime,
83
- isRunning,
84
- handleExecute,
85
- setCliOutput,
86
- setCliStatus,
87
- setCliTime,
88
- } = useExecuteWPCLI ( content , projectPath , updateMessage , messageId ) ;
89
-
90
- useEffect ( ( ) => {
91
- if ( blocks ) {
92
- const block = blocks ?. find ( ( block ) => block . codeBlockContent === content ) ;
93
- if ( block ) {
94
- setCliOutput ( block ?. cliOutput ? stripAnsi ( block . cliOutput ) : null ) ;
95
- setCliStatus ( block ?. cliStatus ?? null ) ;
96
- setCliTime ( block ?. cliTime ?? null ) ;
97
- }
98
- }
99
- } , [ cliOutput , content , setCliOutput , setCliStatus , setCliTime ] ) ;
100
-
101
- const { children, className } = props ;
102
- const match = / l a n g u a g e - ( \w + ) / . exec ( className || '' ) ;
103
- const { node, ...propsSansNode } = props ;
104
- return match ? (
105
- < >
106
- < div className = "p-3" >
107
- < code className = { className } { ...propsSansNode } >
108
- { children }
109
- </ code >
110
- </ div >
111
- < div className = "p-3 pt-1 flex justify-start items-center" >
112
- < CopyTextButton
113
- text = { content }
114
- label = { __ ( 'Copy' ) }
115
- copyConfirmation = { __ ( 'Copied!' ) }
116
- showText = { true }
117
- variant = "outlined"
118
- className = "h-auto mr-2 !px-2.5 py-0.5 !p-[6px] font-sans select-none"
119
- iconSize = { 16 }
120
- > </ CopyTextButton >
121
- { containsWPCommand && containsSingleWPCommand && ! containsAngleBrackets && (
122
- < Button
123
- icon = { < ExecuteIcon /> }
124
- onClick = { handleExecute }
125
- disabled = { isRunning }
126
- variant = "outlined"
127
- className = "h-auto mr-2 !px-2.5 py-0.5 font-sans select-none"
128
- >
129
- { cliOutput ? __ ( 'Run again' ) : __ ( 'Run' ) }
130
- </ Button >
131
- ) }
132
- </ div >
133
- { isRunning && (
134
- < div className = "p-3 flex justify-start items-center bg-[#2D3337] text-white" >
135
- < Spinner className = "!text-white [&>circle]:stroke-a8c-gray-60" />
136
- < span className = "ml-2 font-sans" > { __ ( 'Running...' ) } </ span >
137
- </ div >
138
- ) }
139
- { ! isRunning && cliOutput && cliStatus && (
140
- < InlineCLI output = { cliOutput } status = { cliStatus } time = { cliTime } />
141
- ) }
142
- </ >
143
- ) : (
144
- < code className = { className } { ...propsSansNode } >
145
- { children }
146
- </ code >
147
- ) ;
148
- } ;
149
-
150
46
return (
151
47
< div
152
48
className = { cx (
@@ -173,7 +69,16 @@ export const ChatMessage = ( {
173
69
{ typeof children === 'string' ? (
174
70
< div className = "assistant-markdown" >
175
71
< Markdown
176
- components = { { a : Anchor , code : CodeBlock , img : ( ) => null } }
72
+ components = { {
73
+ a : Anchor ,
74
+ code : createCodeComponent ( {
75
+ blocks,
76
+ messageId,
77
+ projectPath,
78
+ updateMessage,
79
+ } ) ,
80
+ img : ( ) => null ,
81
+ } }
177
82
remarkPlugins = { [ remarkGfm ] }
178
83
>
179
84
{ children }
0 commit comments