Merge branch 'main' into main
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent, act } from '@testing-library/react'
|
||||
import { render as rtlRender, screen, fireEvent, act } from '@testing-library/react'
|
||||
import { AiPanel } from './AiPanel'
|
||||
import { UNSUPPORTED_INLINE_PASTE_MESSAGE } from './InlineWikilinkInput'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import type { VaultEntry } from '../types'
|
||||
import { queueAiPrompt } from '../utils/aiPromptBridge'
|
||||
import { bindVaultConfigStore, getVaultConfig, resetVaultConfigStore } from '../utils/vaultConfigStore'
|
||||
@@ -67,6 +68,10 @@ const makeEntry = (overrides: Partial<VaultEntry> = {}): VaultEntry => ({
|
||||
...overrides,
|
||||
})
|
||||
|
||||
function render(ui: Parameters<typeof rtlRender>[0]) {
|
||||
return rtlRender(ui, { wrapper: TooltipProvider })
|
||||
}
|
||||
|
||||
describe('AiPanel', () => {
|
||||
beforeEach(() => {
|
||||
mockMessages = []
|
||||
@@ -127,7 +132,7 @@ describe('AiPanel', () => {
|
||||
}]
|
||||
|
||||
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Power User' }))
|
||||
fireEvent.click(screen.getByRole('radio', { name: 'Power User' }))
|
||||
|
||||
expect(getVaultConfig().ai_agent_permission_mode).toBe('power_user')
|
||||
expect(save).toHaveBeenLastCalledWith(expect.objectContaining({
|
||||
@@ -143,8 +148,26 @@ describe('AiPanel', () => {
|
||||
|
||||
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Vault Safe' })).toBeDisabled()
|
||||
expect(screen.getByRole('button', { name: 'Power User' })).toBeDisabled()
|
||||
expect(screen.getByRole('radio', { name: 'Vault Safe' })).toBeDisabled()
|
||||
expect(screen.getByRole('radio', { name: 'Power User' })).toBeDisabled()
|
||||
})
|
||||
|
||||
it('renders the permission mode toggle with high contrast selected state and explanatory tooltip', async () => {
|
||||
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
|
||||
|
||||
expect(screen.getByTestId('ai-permission-mode-toggle')).toHaveClass('border', 'bg-muted')
|
||||
|
||||
const safeMode = screen.getByRole('radio', { name: 'Vault Safe' })
|
||||
const powerUserMode = screen.getByRole('radio', { name: 'Power User' })
|
||||
expect(safeMode).toHaveAttribute('aria-checked', 'true')
|
||||
expect(safeMode).toHaveClass('bg-background', 'text-foreground', 'shadow-xs')
|
||||
expect(powerUserMode).toHaveClass('text-muted-foreground')
|
||||
|
||||
fireEvent.focus(safeMode)
|
||||
|
||||
expect(await screen.findByTestId('ai-permission-mode-tooltip')).toHaveTextContent(
|
||||
'Vault Safe keeps agents to file, search, and edit tools. Power User also allows local shell commands for this vault.',
|
||||
)
|
||||
})
|
||||
|
||||
it('renders data-testid ai-panel', () => {
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Robot, X, PaperPlaneRight, Plus, Link } from '@phosphor-icons/react'
|
||||
import { Copy } from 'lucide-react'
|
||||
import { AiMessage } from './AiMessage'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ActionTooltip } from '@/components/ui/action-tooltip'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { WikilinkChatInput } from './WikilinkChatInput'
|
||||
import { extractInlineWikilinkReferences } from './inlineWikilinkText'
|
||||
import {
|
||||
@@ -14,6 +16,10 @@ import type { AiAgentReadiness } from '../lib/aiAgents'
|
||||
import type { NoteReference } from '../utils/ai-context'
|
||||
import type { VaultEntry } from '../types'
|
||||
|
||||
const AI_PERMISSION_MODE_TOOLTIP = {
|
||||
label: 'Vault Safe keeps agents to file, search, and edit tools. Power User also allows local shell commands for this vault.',
|
||||
}
|
||||
|
||||
interface AiPanelHeaderProps {
|
||||
agentLabel: string
|
||||
agentReadiness: AiAgentReadiness
|
||||
@@ -210,30 +216,43 @@ function AiPermissionModeToggle({
|
||||
onChange: (mode: AiAgentPermissionMode) => void
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className="grid rounded-md bg-muted"
|
||||
style={{ gridTemplateColumns: '1fr 1fr', gap: 2, padding: 2 }}
|
||||
role="group"
|
||||
aria-label="AI agent permission mode"
|
||||
data-testid="ai-permission-mode-toggle"
|
||||
>
|
||||
{(['safe', 'power_user'] as const).map((mode) => {
|
||||
const selected = value === mode
|
||||
return (
|
||||
<Button
|
||||
key={mode}
|
||||
type="button"
|
||||
size="xs"
|
||||
variant={selected ? 'secondary' : 'ghost'}
|
||||
disabled={disabled}
|
||||
aria-pressed={selected}
|
||||
onClick={() => onChange(mode)}
|
||||
>
|
||||
{AI_AGENT_PERMISSION_MODE_LABELS[mode].control}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<TooltipProvider>
|
||||
<div
|
||||
className="inline-flex w-full rounded-md border border-border bg-muted p-1"
|
||||
role="radiogroup"
|
||||
aria-label="AI agent permission mode"
|
||||
data-testid="ai-permission-mode-toggle"
|
||||
>
|
||||
{(['safe', 'power_user'] as const).map((mode) => {
|
||||
const selected = value === mode
|
||||
return (
|
||||
<ActionTooltip
|
||||
key={mode}
|
||||
copy={AI_PERMISSION_MODE_TOOLTIP}
|
||||
side="bottom"
|
||||
contentTestId="ai-permission-mode-tooltip"
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
role="radio"
|
||||
aria-checked={selected}
|
||||
disabled={disabled}
|
||||
className={
|
||||
selected
|
||||
? 'h-7 flex-1 border border-border bg-background text-foreground shadow-xs hover:bg-background'
|
||||
: 'h-7 flex-1 text-muted-foreground hover:text-foreground'
|
||||
}
|
||||
onClick={() => onChange(mode)}
|
||||
>
|
||||
{AI_AGENT_PERMISSION_MODE_LABELS[mode].control}
|
||||
</Button>
|
||||
</ActionTooltip>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user