diff --git a/frontend/src/components/ai/AIChatComposerActions.tsx b/frontend/src/components/ai/AIChatComposerActions.tsx new file mode 100644 index 0000000..14dc92f --- /dev/null +++ b/frontend/src/components/ai/AIChatComposerActions.tsx @@ -0,0 +1,152 @@ +import React from 'react'; +import { Button, Tooltip } from 'antd'; +import { CodeOutlined, PictureOutlined, SendOutlined, StopOutlined, TableOutlined } from '@ant-design/icons'; + +import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme'; + +interface AIChatComposerActionsProps { + variant: 'legacy' | 'v2'; + input: string; + draftImageCount: number; + sending: boolean; + darkMode: boolean; + textColor: string; + mutedColor: string; + overlayTheme: OverlayWorkbenchTheme; + fileInputRef: React.RefObject; + onImageUpload: React.ChangeEventHandler; + onOpenContext: () => void; + onOpenSlashMenu?: () => void; + onSend: () => void; + onStop: () => void; +} + +const buttonIconStyle = { fontSize: 16 }; + +const AIChatComposerActions: React.FC = ({ + variant, + input, + draftImageCount, + sending, + darkMode, + textColor, + mutedColor, + overlayTheme, + fileInputRef, + onImageUpload, + onOpenContext, + onOpenSlashMenu, + onSend, + onStop, +}) => { + const canSend = input.trim().length > 0 || draftImageCount > 0; + const isV2 = variant === 'v2'; + const legacyIconButtonStyle: React.CSSProperties = { + color: overlayTheme.mutedText, + border: 'none', + background: 'transparent', + padding: '0 4px', + height: 26, + }; + const v2IconButtonStyle: React.CSSProperties = { + color: overlayTheme.mutedText, + border: 'none', + background: 'transparent', + }; + + return ( +
+ + + + ) : ( + + )} +
+ ); +}; + +export default AIChatComposerActions; diff --git a/frontend/src/components/ai/AIChatInput.notice.test.tsx b/frontend/src/components/ai/AIChatInput.notice.test.tsx index f07b62f..f275b81 100644 --- a/frontend/src/components/ai/AIChatInput.notice.test.tsx +++ b/frontend/src/components/ai/AIChatInput.notice.test.tsx @@ -188,6 +188,15 @@ describe('AIChatInput notice layout', () => { expect(sendButton).not.toContain('disabled'); }); + it('keeps v2 composer action controls available after rendering the input', () => { + const markup = renderAIChatInput({ input: 'select 1' }); + + expect(markup).toContain('gn-v2-ai-input-actions'); + expect(markup).toContain('aria-label="picture"'); + expect(markup).toContain('aria-label="table"'); + expect(markup).toContain('aria-label="code"'); + }); + it('keeps the legacy composer free of v2-only layout classes by default', () => { const markup = renderAIChatInput({ isV2Ui: false, input: 'select 1' }); diff --git a/frontend/src/components/ai/AIChatInput.tsx b/frontend/src/components/ai/AIChatInput.tsx index 763e376..49d1f0a 100644 --- a/frontend/src/components/ai/AIChatInput.tsx +++ b/frontend/src/components/ai/AIChatInput.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Input, Tooltip, Button } from 'antd'; -import { CodeOutlined, DatabaseOutlined, SendOutlined, StopOutlined, TableOutlined, PictureOutlined } from '@ant-design/icons'; +import { Input, Tooltip } from 'antd'; +import { DatabaseOutlined } from '@ant-design/icons'; import { useStore } from '../../store'; import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme'; import type { AIComposerNotice, AIComposerNoticeAction } from '../../utils/aiComposerNotice'; @@ -11,6 +11,7 @@ import AIContextSelectorModal from './AIContextSelectorModal'; import AISlashCommandMenu from './AISlashCommandMenu'; import AIChatComposerNotice from './AIChatComposerNotice'; import AIChatComposerStatus from './AIChatComposerStatus'; +import AIChatComposerActions from './AIChatComposerActions'; import AIChatAttachmentStrip from './AIChatAttachmentStrip'; import AIChatContextPreview from './AIChatContextPreview'; import AIChatProviderModelSelect from './AIChatProviderModelSelect'; @@ -241,66 +242,21 @@ export const AIChatInput: React.FC = ({ )} -
- - - - ) : ( - - )} -
+ @@ -394,60 +350,22 @@ export const AIChatInput: React.FC = ({ autoSize={{ minRows: 1, maxRows: 8 }} style={{ color: textColor, width: '100%', padding: 0, resize: 'none' }} /> -
- - - - ) : ( - - )} -
+