-
Notifications
You must be signed in to change notification settings - Fork 30
Add UI for dynamic code block to execute Assistant wp-cli commands #214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
3b4b651
Update Assistant Markdown styles to use class instead of id
derekblank 1c77bad
Remove unneccessary container div
derekblank a3eba8e
Merge branch 'fix/markdown-updates' into feat/dynamic-assistant-code-…
derekblank 5f146df
Add UI for Assistant dynamic code blocks
derekblank 5964848
Update todo notes
derekblank 178842f
Remove inline from from Markdown render
derekblank d6baaeb
Remove duplicate pre tag when parsing code blocks
derekblank ef34a80
Update styling for non-Markdown code blocks
derekblank 981a20e
Update Copy Text button handler
derekblank 2b67bda
Update ActionButton handlers to use a second label when actioned
derekblank 8c26996
Add secondIcon to ActionButton
derekblank 5ac1502
Update Run button handler
derekblank c6dfe6a
Update ActionButton styles to use tertiary variant
derekblank 80d195d
Add a Spinner component to InlineCLI block
derekblank 12289a0
Update isRunning state
derekblank d36263b
Merge trunk and resolve conflicts
derekblank 7ab76fc
Use natural margins for Assistant Markdown headings
derekblank ce1b83e
Refactor markdown code components to CodeBlock
derekblank 19202ac
Fix Markdown code block typing
derekblank b821649
Merge remote-tracking branch 'origin' into feat/dynamic-assistant-cod…
derekblank bac4d2a
Fix unauthenticated view HTML syntax
derekblank a0bcac3
Disable Run button for code block for now
65313ac
Replace spinner with wordpress components
sejas 6e48014
Translate error and success
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
import { Spinner } from '@wordpress/components'; | ||
import { createInterpolateElement } from '@wordpress/element'; | ||
import { Icon, external } from '@wordpress/icons'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { Icon, external, copy } from '@wordpress/icons'; | ||
import { useI18n } from '@wordpress/react-i18n'; | ||
import React, { useState, useEffect, useRef } from 'react'; | ||
import Markdown from 'react-markdown'; | ||
import Markdown, { ExtraProps } from 'react-markdown'; | ||
import { useAssistant } from '../hooks/use-assistant'; | ||
import { useAssistantApi } from '../hooks/use-assistant-api'; | ||
import { useAuth } from '../hooks/use-auth'; | ||
|
@@ -12,6 +14,7 @@ import { getIpcApi } from '../lib/get-ipc-api'; | |
import { AIInput } from './ai-input'; | ||
import { MessageThinking } from './assistant-thinking'; | ||
import Button from './button'; | ||
import { ExecuteIcon } from './icons/execute'; | ||
|
||
interface ContentTabAssistantProps { | ||
selectedSite: SiteDetails; | ||
|
@@ -23,25 +26,150 @@ interface MessageProps { | |
className?: string; | ||
} | ||
|
||
export const Message = ( { children, isUser, className }: MessageProps ) => ( | ||
<div className={ cx( 'flex mt-4', isUser ? 'justify-end' : 'justify-start', className ) }> | ||
<div | ||
className={ cx( | ||
'inline-block p-3 rounded-sm border border-gray-300 lg:max-w-[70%] select-text whitespace-pre-wrap', | ||
! isUser && 'bg-white' | ||
) } | ||
> | ||
{ typeof children === 'string' ? ( | ||
<div className="assistant-markdown"> | ||
<Markdown>{ children }</Markdown> | ||
</div> | ||
) : ( | ||
children | ||
) } | ||
interface InlineCLIProps { | ||
output: string; | ||
status: 'success' | 'error'; | ||
time: string; | ||
} | ||
Comment on lines
+29
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This interface is likely to change based on the final shape of the wp-cli implementation in #203 -- just chose some probable params for UI demonstration purposes. |
||
|
||
const InlineCLI = ( { output, status, time }: InlineCLIProps ) => ( | ||
<div className="p-3 bg-[#2D3337]"> | ||
<div className="flex justify-between mb-2 font-sans"> | ||
<span className={ status === 'success' ? 'text-[#63CE68]' : 'text-[#E66D6C]' }> | ||
{ status === 'success' ? __( 'Success' ) : __( 'Error' ) } | ||
</span> | ||
<span className="text-gray-400">{ time }</span> | ||
</div> | ||
<pre className="text-white !bg-transparent !m-0 !px-0"> | ||
<code className="!bg-transparent !mx-0 !px-0">{ output }</code> | ||
</pre> | ||
</div> | ||
); | ||
|
||
const ActionButton = ( { | ||
primaryLabel, | ||
secondaryLabel, | ||
icon, | ||
onClick, | ||
timeout, | ||
disabled, | ||
}: { | ||
primaryLabel: string; | ||
secondaryLabel: string; | ||
icon: JSX.Element; | ||
onClick: () => void; | ||
timeout?: number; | ||
disabled?: boolean; | ||
} ) => { | ||
const [ buttonLabel, setButtonLabel ] = useState( primaryLabel ); | ||
|
||
const handleClick = () => { | ||
onClick(); | ||
setButtonLabel( secondaryLabel ); | ||
if ( timeout ) { | ||
setTimeout( () => { | ||
setButtonLabel( primaryLabel ); | ||
}, timeout ); | ||
} | ||
}; | ||
|
||
return ( | ||
<Button | ||
onClick={ handleClick } | ||
variant="tertiary" | ||
className="mr-2 font-sans select-none" | ||
disabled={ disabled } | ||
> | ||
{ icon } | ||
<span className="ml-1">{ buttonLabel }</span> | ||
</Button> | ||
); | ||
}; | ||
|
||
export const Message = ( { children, isUser, className }: MessageProps ) => { | ||
const [ cliOutput, setCliOutput ] = useState< string | null >( null ); | ||
const [ cliStatus, setCliStatus ] = useState< 'success' | 'error' | null >( null ); | ||
const [ cliTime, setCliTime ] = useState< string | null >( null ); | ||
const [ isRunning, setIsRunning ] = useState( false ); | ||
|
||
const handleExecute = () => { | ||
setIsRunning( true ); | ||
setTimeout( () => { | ||
setCliOutput( | ||
`Installing Jetpack...\nUnpacking the package...\nInstalling the plugin...\nPlugin installed successfully.\nActivating 'jetpack'...\nPlugin 'jetpack' activated.\nSuccess: Installed 1 of 1 plugins.` | ||
); | ||
setCliStatus( 'success' ); | ||
setCliTime( 'Completed in 2.3 seconds' ); | ||
setIsRunning( false ); | ||
}, 2300 ); | ||
}; | ||
|
||
const CodeBlock = ( props: JSX.IntrinsicElements[ 'code' ] & ExtraProps ) => { | ||
const { children, className } = props; | ||
const match = /language-(\w+)/.exec( className || '' ); | ||
const content = String( children ).trim(); | ||
|
||
return match ? ( | ||
<> | ||
<div className="p-3"> | ||
<code className={ className } { ...props }> | ||
{ children } | ||
</code> | ||
</div> | ||
<div className="p-3 mt-1 flex justify-start items-center"> | ||
<ActionButton | ||
primaryLabel={ __( 'Copy' ) } | ||
secondaryLabel={ __( 'Copied' ) } | ||
icon={ <Icon icon={ copy } size={ 16 } /> } | ||
onClick={ () => getIpcApi().copyText( content ) } | ||
timeout={ 2000 } | ||
/> | ||
{ /* <ActionButton | ||
primaryLabel={ __( 'Run' ) } | ||
secondaryLabel={ __( 'Run Again' ) } | ||
icon={ <ExecuteIcon /> } | ||
onClick={ handleExecute } | ||
disabled={ isRunning } */ } | ||
</div> | ||
{ isRunning && ( | ||
<div className="p-3 flex justify-start items-center bg-[#2D3337] text-white"> | ||
<Spinner className="!text-white [&>circle]:stroke-a8c-gray-60" /> | ||
<span className="ml-2 font-sans">{ __( 'Running...' ) }</span> | ||
</div> | ||
) } | ||
{ ! isRunning && cliOutput && cliStatus && cliTime && ( | ||
<InlineCLI output={ cliOutput } status={ cliStatus } time={ cliTime } /> | ||
) } | ||
</> | ||
) : ( | ||
<div className="p-3"> | ||
<code className={ className } { ...props }> | ||
{ children } | ||
</code> | ||
</div> | ||
); | ||
}; | ||
|
||
return ( | ||
<div className={ cx( 'flex mt-4', isUser ? 'justify-end' : 'justify-start', className ) }> | ||
<div | ||
className={ cx( | ||
'inline-block p-3 rounded-sm border border-gray-300 lg:max-w-[70%] select-text whitespace-pre-wrap', | ||
! isUser && 'bg-white' | ||
) } | ||
> | ||
{ typeof children === 'string' ? ( | ||
<div className="assistant-markdown"> | ||
<Markdown components={ { code: CodeBlock } }>{ children }</Markdown> | ||
derekblank marked this conversation as resolved.
Show resolved
Hide resolved
|
||
</div> | ||
) : ( | ||
children | ||
) } | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export function ContentTabAssistant( { selectedSite }: ContentTabAssistantProps ) { | ||
const { messages, addMessage, clearMessages } = useAssistant( selectedSite.name ); | ||
const { fetchAssistant, isLoading: isAssistantThinking } = useAssistantApi(); | ||
|
@@ -115,9 +243,11 @@ export function ContentTabAssistant( { selectedSite }: ContentTabAssistantProps | |
|
||
const renderUnauthenticatedView = () => ( | ||
<Message className="w-full" isUser={ false }> | ||
<p className="mb-1.5 a8c-label-semibold">{ __( 'Hold up!' ) }</p> | ||
<p>{ __( 'You need to log in to your WordPress.com account to use the assistant.' ) }</p> | ||
<p className="mb-1.5"> | ||
<div className="mb-3 a8c-label-semibold">{ __( 'Hold up!' ) }</div> | ||
<div className="mb-1"> | ||
{ __( 'You need to log in to your WordPress.com account to use the assistant.' ) } | ||
</div> | ||
<div className="mb-1"> | ||
{ createInterpolateElement( | ||
__( "If you don't have an account yet, <a>create one for free</a>." ), | ||
{ | ||
|
@@ -133,10 +263,10 @@ export function ContentTabAssistant( { selectedSite }: ContentTabAssistantProps | |
), | ||
} | ||
) } | ||
</p> | ||
<p className="mb-3"> | ||
</div> | ||
<div className="mb-3"> | ||
{ __( 'Every account gets 200 prompts included for free each month.' ) } | ||
</p> | ||
</div> | ||
<Button variant="primary" onClick={ authenticate }> | ||
{ __( 'Log in to WordPress.com' ) } | ||
<Icon className="ltr:ml-1 rtl:mr-1 rtl:scale-x-[-1]" icon={ external } size={ 21 } /> | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export const ExecuteIcon = () => ( | ||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<path | ||
d="M2.6667 11.8535H13.3334V12.8535H2.6667V11.8535ZM2.64648 5.5404L4.62626 7.52018L2.64648 9.49996L3.35359 10.2071L5.68692 7.87374L6.04048 7.52018L5.68692 7.16663L3.35359 4.83329L2.64648 5.5404Z" | ||
fill="currentColor" | ||
/> | ||
</svg> | ||
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leverage
tertiary
for InlineCLI ActionButtons. (tertiary
is currently unused as a<Button />
variant intrunk
.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noted these new tertiary styles modified the existing usage of tertiary in the add site modal. We might consider consolidating button styles (i.e., ask the design team if we can reduce the number of button styles used) or rename this new style. WDYT?
studio/src/components/add-site.tsx
Line 140 in 216154a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dcalhoun Good catch! I think we should use a different name for the buttons in code blocks as they're a bit of a special use case. I don't anticipate on re-using those dark styles anywhere else in the app at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, let's update these buttons to use the same styling as the
Add site
button in the sidebar (outlined, not filled). That helps bring more consistency to the button styles in the app. (Figma)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For posterity, I drafted #243 addressing the button styling.