Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions wework/src/components/chat/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export interface ChatInputProps {
onCompositionEnd?: () => void
onSubmit: (valueOverride?: string, options?: ChatSubmitOptions) => void | Promise<void>
disabled: boolean
pluginPickerIconOnly?: boolean
submitDisabled?: boolean
error?: string | null
disabledReason?: string
Expand Down Expand Up @@ -515,6 +516,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(function Ch
onCompositionEnd,
onSubmit,
disabled,
pluginPickerIconOnly = false,
submitDisabled = false,
error,
disabledReason,
Expand Down Expand Up @@ -818,6 +820,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(function Ch
<ProjectChatComposer
ref={composerRef}
{...composerProps}
pluginPickerIconOnly={pluginPickerIconOnly}
models={controls.models}
selectedModel={controls.selectedModel}
activeModel={controls.activeModel}
Expand Down
29 changes: 18 additions & 11 deletions wework/src/components/chat/composer/AddContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import type { CloudProject } from '@/api/deliveries'
import { useTranslation } from '@/hooks/useTranslation'
import { Tooltip } from '@/components/ui/tooltip'
import type { ComposerCloudMentionCandidate } from './composerMentionCandidates'
import { useAnchoredPortalMenu } from './useAnchoredPortalMenu'
import { useOutsideClick } from './useOutsideClick'
Expand Down Expand Up @@ -272,18 +273,24 @@ export function AddContextMenu({
</div>,
document.body
)}
<button
ref={triggerRef}
type="button"
data-testid="add-context-button"
onClick={() => !disabled && setOpen(current => !current)}
disabled={disabled}
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full p-0 text-text-primary hover:bg-background/70 disabled:cursor-not-allowed disabled:opacity-50"
aria-expanded={open}
aria-label={t('workbench.add_context', '添加上下文')}
<Tooltip
label={t('workbench.add_context', '添加上下文')}
align="start"
testId="composer-add-context-tooltip"
>
<Plus className="h-5 w-5" strokeWidth={1.5} />
</button>
<button
ref={triggerRef}
type="button"
data-testid="add-context-button"
onClick={() => !disabled && setOpen(current => !current)}
disabled={disabled}
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full p-0 text-text-primary hover:bg-background/70 disabled:cursor-not-allowed disabled:opacity-50"
aria-expanded={open}
aria-label={t('workbench.add_context', '添加上下文')}
>
<Plus className="h-5 w-5" strokeWidth={1.5} />
</button>
</Tooltip>
</div>
)
}
50 changes: 50 additions & 0 deletions wework/src/components/chat/composer/ComposerToolbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,54 @@ describe('ComposerToolbar', () => {
expect(screen.getByTestId('composer-toolbar')).toHaveAttribute('data-compact', 'true')
expect(screen.getByTestId('quick-phrase-layout')).toHaveTextContent('icon')
})

it('collapses the plugin picker trigger to an icon once a conversation has started', () => {
vi.stubGlobal('ResizeObserver', ResizeObserverMock)
vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({
width: 600,
height: 32,
top: 0,
right: 600,
bottom: 32,
left: 0,
x: 0,
y: 0,
toJSON: () => ({}),
})

const { rerender } = render(
<ComposerToolbar
canSend={false}
models={[]}
selectedModel={null}
selectedModelOptions={{}}
isModelSelectionReady
onSelectModel={vi.fn()}
onSelectModelOption={vi.fn()}
onFileSelect={vi.fn()}
onQuickPhraseSelect={vi.fn()}
onSubmit={vi.fn()}
/>
)

expect(screen.getByTestId('composer-plugin-picker-button')).toHaveClass('h-8')

rerender(
<ComposerToolbar
canSend={false}
models={[]}
selectedModel={null}
selectedModelOptions={{}}
isModelSelectionReady
pluginPickerIconOnly
onSelectModel={vi.fn()}
onSelectModelOption={vi.fn()}
onFileSelect={vi.fn()}
onQuickPhraseSelect={vi.fn()}
onSubmit={vi.fn()}
/>
)

expect(screen.getByTestId('composer-plugin-picker-button')).toHaveClass('h-7')
})
})
69 changes: 45 additions & 24 deletions wework/src/components/chat/composer/ComposerToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { CloudProject } from '@/api/deliveries'
import { ActionMenu } from '@/components/common/ActionMenu'
import type { ComposerSubmitOptions } from './ComposerTextarea'
import { useTranslation } from '@/hooks/useTranslation'
import { Tooltip } from '@/components/ui/tooltip'
import type { LocalDeviceApp, ModelOptions, RuntimeContextUsage, UnifiedModel } from '@/types/api'
import { AddContextMenu } from './AddContextMenu'
import type { ComposerCloudMentionCandidate } from './composerMentionCandidates'
Expand All @@ -18,6 +19,7 @@ import type { QuickPhrase } from '@/tauri/appPreferences'
interface ComposerToolbarProps {
canSend: boolean
disabled?: boolean
pluginPickerIconOnly?: boolean
models: UnifiedModel[]
selectedModel: UnifiedModel | null
activeModel?: UnifiedModel | null
Expand Down Expand Up @@ -61,6 +63,7 @@ const NARROW_MODEL_SELECTOR_MAX_WIDTH = 160
export function ComposerToolbar({
canSend,
disabled = false,
pluginPickerIconOnly = false,
models,
selectedModel,
activeModel,
Expand Down Expand Up @@ -146,7 +149,7 @@ export function ComposerToolbar({
<QuickPhraseMenu disabled={disabled} onSelect={onQuickPhraseSelect} />
<PluginPickerMenu
disabled={disabled}
iconOnly={compact}
iconOnly={compact || pluginPickerIconOnly}
onListLocalApps={onListLocalApps}
/>
{leadingContext}
Expand Down Expand Up @@ -194,25 +197,37 @@ export function ComposerToolbar({
<PopoutWorkspaceMenu {...projectWorkMenuContext} disabled={disabled} />
) : null}
{isStreaming && !canSend ? (
<button
type="button"
data-testid="pause-response-button"
onClick={onPause}
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[#1f1f1f] p-0 text-white hover:bg-[#333]"
aria-label={t('workbench.pause_response', '暂停回复')}
<Tooltip
label={t('workbench.pause_response', '暂停回复')}
align="end"
testId="composer-pause-tooltip"
>
<span className="h-3.5 w-3.5 rounded-sm bg-current" aria-hidden="true" />
</button>
) : isStreaming && canSend ? (
<div className="flex items-center rounded-full bg-[#1f1f1f] text-white">
<button
type="submit"
data-testid={sendButtonTestId}
className="flex h-8 w-8 items-center justify-center rounded-l-full hover:bg-[#333]"
aria-label={t('workbench.send_after_turn', '当前回复结束后发送')}
type="button"
data-testid="pause-response-button"
onClick={onPause}
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[#1f1f1f] p-0 text-white hover:bg-[#333]"
aria-label={t('workbench.pause_response', '暂停回复')}
>
<ArrowUp className="h-4 w-4" />
<span className="h-3.5 w-3.5 rounded-sm bg-current" aria-hidden="true" />
</button>
</Tooltip>
) : isStreaming && canSend ? (
<div className="flex items-center rounded-full bg-[#1f1f1f] text-white">
<Tooltip
label={t('workbench.send_after_turn', '当前回复结束后发送')}
align="end"
testId="composer-send-after-turn-tooltip"
>
<button
type="submit"
data-testid={sendButtonTestId}
className="flex h-8 w-8 items-center justify-center rounded-l-full hover:bg-[#333]"
aria-label={t('workbench.send_after_turn', '当前回复结束后发送')}
>
<ArrowUp className="h-4 w-4" />
</button>
</Tooltip>
<ActionMenu
ariaLabel={t('workbench.choose_send_mode', '选择发送方式')}
testId="send-mode-menu-button"
Expand Down Expand Up @@ -262,15 +277,21 @@ export function ComposerToolbar({
/>
</div>
) : (
<button
type="submit"
data-testid={sendButtonTestId}
disabled={!canSend}
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[#1f1f1f] p-0 text-white disabled:cursor-not-allowed disabled:bg-text-muted/45 disabled:text-background"
aria-label={t('workbench.send_message', '发送消息')}
<Tooltip
label={t('workbench.send_message', '发送消息')}
align="end"
testId="composer-send-tooltip"
>
<ArrowUp className="h-4 w-4" />
</button>
<button
type="submit"
data-testid={sendButtonTestId}
disabled={!canSend}
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[#1f1f1f] p-0 text-white disabled:cursor-not-allowed disabled:bg-text-muted/45 disabled:text-background"
aria-label={t('workbench.send_message', '发送消息')}
>
<ArrowUp className="h-4 w-4" />
</button>
</Tooltip>
)}
</div>
</div>
Expand Down
14 changes: 14 additions & 0 deletions wework/src/components/chat/composer/PluginPickerMenu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ describe('PluginPickerMenu', () => {
window.removeEventListener(SHOW_PLUGIN_TRIAL_GUIDE_EVENT, onShowGuide)
})

test('renders a single icon trigger when iconOnly is set', async () => {
const onListLocalApps = vi.fn().mockResolvedValue([githubApp, superpowersApp, echoIdApp])

render(<PluginPickerMenu iconOnly onListLocalApps={onListLocalApps} />)

const trigger = screen.getByTestId('composer-plugin-picker-button')
expect(trigger).toHaveClass('h-7', 'w-7', 'rounded-lg')
expect(trigger).not.toHaveTextContent('插件')
expect(screen.queryByTestId(/composer-plugin-preview-icon-/)).not.toBeInTheDocument()

await userEvent.click(trigger)
expect(await screen.findByTestId('composer-plugin-picker-item-github')).toBeInTheDocument()
})

test('orders available plugins by usage count then recent selection', async () => {
recordPluginUsage('EchoID')
recordPluginUsage('EchoID')
Expand Down
Loading
Loading