diff --git a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css index 429c62e47592..24578a1a0815 100644 --- a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css +++ b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css @@ -8,19 +8,13 @@ display: flex; align-items: center; justify-content: space-between; - padding: 8px; - border: 1px solid var(--vscode-editor-foreground); + padding: calc(var(--spacing) * 0.5); border-radius: 4px; background: transparent; + border-top: none; } -.buttons-container-transparent { - composes: buttons-container; - border: none; - padding: 4px 0; -} - -.leftInfo { +.left-info { display: flex; align-items: center; gap: 8px; @@ -29,6 +23,7 @@ .stats { color: var(--vscode-descriptionForeground); + padding: calc(var(--spacing) * 0.5); } .buttons { @@ -38,26 +33,28 @@ justify-content: space-between; } -.actionButtons { +.action-buttons { display: flex; gap: 0.25rem; } + .button { display: flex; align-items: center; padding: 3px; height: 20px; - background-color: transparent; cursor: pointer; background: var(--button-icon-background); border-radius: var(--button-icon-corner-radius); color: var(--foreground); } + .button:hover { background: var(--button-icon-hover-background); outline: 1px dotted var(--contrast-active-border); outline-offset: -1px; } + .button:not(:first-child) { margin-left: 0.25rem; } @@ -94,6 +91,7 @@ border-radius: var(--button-icon-corner-radius); color: var(--foreground); } + .copy-button:hover, .insert-button:hover { background: var(--button-icon-hover-background); @@ -116,7 +114,7 @@ margin-right: 0.25rem; } -.fileNameContainer { +.file-name-container { font-size: 12px; color: var(--vscode-descriptionForeground); display: flex; @@ -129,7 +127,6 @@ margin-left: auto; } - .attribution-icon-unavailable { color: var(--hl-orange); } @@ -188,7 +185,6 @@ padding: calc(var(--spacing) * 0.5); overflow-x: auto; border-bottom: none; - margin: 1rem 0; border-radius: 3px; border: 1px solid var(--vscode-input-background); } @@ -207,6 +203,7 @@ color: var(--code-foreground); margin-bottom: 0; } + body[data-vscode-theme-kind='vscode-light'] .content pre, body[data-vscode-theme-kind='vscode-light'] .content pre > code { /* Our syntax highlighter emits colors intended for dark backgrounds only. */ @@ -258,8 +255,3 @@ body[data-vscode-theme-kind='vscode-light'] .content pre > code { padding-inline-start: 2rem; list-style: revert; } - -.file-name-container { - color: var(--vscode-descriptionForeground); - margin-left: auto; -} diff --git a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.story.tsx b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.story.tsx new file mode 100644 index 000000000000..b330f8b17e14 --- /dev/null +++ b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.story.tsx @@ -0,0 +1,169 @@ +import { ps } from '@sourcegraph/cody-shared' +import type { Meta, StoryObj } from '@storybook/react' +import { VSCodeWebview } from '../../storybook/VSCodeStoryDecorator' +import { ChatMessageContent } from './ChatMessageContent' + +const meta: Meta = { + title: 'chat/ChatMessageContent', + component: ChatMessageContent, + + args: { + displayMarkdown: '# Hello\nThis is a test message', + isMessageLoading: false, + humanMessage: null, + copyButtonOnSubmit: () => console.log('Copy button clicked'), + insertButtonOnSubmit: undefined, + smartApplyEnabled: false, + smartApply: undefined, + guardrails: undefined, + }, + + decorators: [VSCodeWebview], +} + +export default meta + +export const Default: StoryObj = {} + +export const WithCodeBlock: StoryObj = { + args: { + displayMarkdown: 'Code Example\n```javascript\nconsole.log("Hello world");\n```', + }, +} + +export const WithCodeBlockNoSmartApply: StoryObj = { + args: { + displayMarkdown: '## Code Example\n```javascript\nconsole.log("Hello world");\n```', + smartApplyEnabled: false, + copyButtonOnSubmit: () => console.log('Copy button clicked'), + insertButtonOnSubmit: () => console.log('Insert button clicked'), + }, + name: 'With Code Block - No Smart Apply (Web Client)', +} + +export const WithCodeBlockWithSmartApply: StoryObj = { + args: { + displayMarkdown: '### Code Example\n```javascript\nconsole.log("Hello world");\n```', + smartApplyEnabled: true, + copyButtonOnSubmit: () => console.log('Copy button clicked'), + insertButtonOnSubmit: () => console.log('Insert button clicked'), + smartApply: { + onSubmit: () => console.log('Smart apply submitted'), + onAccept: () => console.log('Smart apply accepted'), + onReject: () => console.log('Smart apply rejected'), + }, + }, + name: 'With Code Block - With Smart Apply (VS Code)', +} + +export const WithMultipleCodeBlocks: StoryObj = { + args: { + displayMarkdown: `# Multiple Code Blocks +Here's the first code block: +\`\`\`javascript +function hello() { + console.log("Hello world"); +} +\`\`\` + +And here's the second one: +\`\`\`python +def hello(): + print("Hello world") +\`\`\``, + copyButtonOnSubmit: () => console.log('Copy button clicked'), + insertButtonOnSubmit: () => console.log('Insert button clicked'), + }, +} + +export const SmartApplyPending: StoryObj = { + args: { + displayMarkdown: '# Working Example\n```javascript\nconsole.log("Hello world");\n```', + smartApplyEnabled: true, + copyButtonOnSubmit: () => console.log('Copy button clicked'), + insertButtonOnSubmit: () => console.log('Insert button clicked'), + smartApply: { + onSubmit: () => console.log('Smart apply submitted'), + onAccept: () => console.log('Smart apply accepted'), + onReject: () => console.log('Smart apply rejected'), + }, + humanMessage: { + text: ps`Write a hello world example`, + intent: 'chat', + hasInitialContext: { + repositories: false, + files: false, + }, + hasExplicitMentions: false, + rerunWithDifferentContext: () => console.log('Rerun with different context'), + appendAtMention: () => console.log('Append at mention'), + }, + }, + name: 'Smart Apply - Pending State', +} + +export const SmartApplyWorking: StoryObj = { + render: args => { + // Return the component with state and callbacks set up + return + }, + args: { + displayMarkdown: '# Working Example\n```javascript\nconsole.log("Hello world");\n```', + smartApplyEnabled: true, + copyButtonOnSubmit: () => console.log('Copy button clicked'), + insertButtonOnSubmit: () => console.log('Insert button clicked'), + smartApply: { + onSubmit: () => console.log('Smart apply submitted'), + onAccept: () => console.log('Smart apply accepted'), + onReject: () => console.log('Smart apply rejected'), + }, + humanMessage: { + text: ps`Write a hello world example`, + intent: 'chat', + hasInitialContext: { + repositories: false, + files: false, + }, + hasExplicitMentions: false, + rerunWithDifferentContext: () => console.log('Rerun with different context'), + appendAtMention: () => console.log('Append at mention'), + }, + }, + name: 'Smart Apply - Working State', +} + +export const EditIntent: StoryObj = { + args: { + displayMarkdown: + '# Edit Intent Example\n```javascript:hello.js\n+ console.log("Hello world");\n+ \n- // console.log("Hello world");\n```', + copyButtonOnSubmit: () => console.log('Copy button clicked'), + insertButtonOnSubmit: () => console.log('Insert button clicked'), + smartApplyEnabled: true, + smartApply: undefined, + humanMessage: { + text: ps`Write a hello world example`, + intent: 'edit', + hasInitialContext: { + repositories: false, + files: false, + }, + hasExplicitMentions: false, + rerunWithDifferentContext: () => console.log('Rerun with different context'), + appendAtMention: () => console.log('Append at mention'), + }, + }, + name: 'Edit Intent with Preview', +} + +export const Loading: StoryObj = { + args: { + displayMarkdown: '# Loading...', + isMessageLoading: true, + }, +} + +export const WithThinkContent: StoryObj = { + args: { + displayMarkdown: '\nAnalyzing the problem...\n\nHere is the solution.', + }, +} diff --git a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx index b77466628816..cf45a68f8510 100644 --- a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx +++ b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx @@ -126,10 +126,8 @@ export const ChatMessageContent: React.FunctionComponent+${additions}, -${deletions}` stats.className = styles.stats leftInfo.appendChild(stats) - previewElement = buttonContainer1 + previewElement = previewContainer hasPreviewContent = true } } - buttonContainer1.appendChild(leftInfo) + previewContainer.appendChild(leftInfo) - // Create button container 2 for action buttons - const buttonContainer2 = document.createElement('div') - buttonContainer2.className = styles.buttonsContainerTransparent - buttonContainer2.dataset.containerType = 'actions' + const actionsContainer = document.createElement('div') + actionsContainer.className = styles.buttonsContainer + actionsContainer.dataset.containerType = 'actions' if (!copyButtonOnSubmit) { const buttonsContainer = document.createElement('div') buttonsContainer.dataset.containerType = 'buttons' - buttonsContainer.append(buttonContainer1, buttonContainer2) + buttonsContainer.append(previewContainer, actionsContainer) if (hasPreviewContent && previewElement) { - buttonsContainer.prepend(buttonContainer1) + buttonsContainer.prepend(previewContainer) } return buttonsContainer } @@ -162,15 +160,6 @@ export function createButtonsExperimentalUI( const metadataContainer = document.createElement('div') metadataContainer.className = styles.metadataContainer - // Add filename if present - if (codeBlockName && codeBlockName !== 'command') { - const fileNameContainer = document.createElement('div') - fileNameContainer.className = styles.fileNameContainer - fileNameContainer.textContent = getFileName(codeBlockName) - fileNameContainer.title = codeBlockName - metadataContainer.append(fileNameContainer) - } - // Add guardrails if needed if (guardrails) { const container = document.createElement('div') @@ -202,6 +191,15 @@ export function createButtonsExperimentalUI( } } + // Add filename if present + if (codeBlockName && codeBlockName !== 'command') { + const fileNameContainer = document.createElement('div') + fileNameContainer.className = styles.fileNameContainer + fileNameContainer.textContent = getFileName(codeBlockName) + fileNameContainer.title = codeBlockName + metadataContainer.append(fileNameContainer) + } + buttons.appendChild(metadataContainer) if (smartApply && smartApplyState === CodyTaskState.Applied && smartApplyId) { @@ -244,12 +242,12 @@ export function createButtonsExperimentalUI( } } - buttonContainer2.appendChild(buttons) + actionsContainer.appendChild(buttons) // Return a container with both preview and action containers const buttonsContainer = document.createElement('div') buttonsContainer.dataset.containerType = 'buttons' - buttonsContainer.append(buttonContainer1, buttonContainer2) + buttonsContainer.append(previewContainer, actionsContainer) return buttonsContainer }